diff --git a/package-lock.json b/package-lock.json index c2ebab6..e5ed9c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,26 @@ { "name": "@aggregion/yaan", - "version": "0.6.0", + "version": "0.7.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@aggregion/yaan", - "version": "0.6.0", + "version": "0.7.3", "license": "MIT", "dependencies": { "@oclif/command": "1.8.7", "@oclif/config": "1.18.1", "@oclif/errors": "1.3.5", + "@oclif/help": "^1.0.1", "@oclif/plugin-help": "3.2.10", + "@types/fs-extra": "^9.0.13", "ajv": "8.8.2", + "fs-extra": "^10.1.0", "glob": "7.2.0", + "lodash": "^4.17.21", "object-path": "0.11.8", + "path": "^0.12.7", "tslib": "1.14.1", "yaml": "1.10.2" }, @@ -27,7 +32,8 @@ "@oclif/dev-cli": "^1.26.6", "@types/jest": "^27.0.3", "@types/jest-expect-message": "^1.0.3", - "@types/node": "^10.17.60", + "@types/lodash": "^4.14.183", + "@types/node": "^14.18.11", "@types/object-path": "^0.11.1", "@types/uuid": "^8.3.3", "@typescript-eslint/eslint-plugin": "^5.5.0", @@ -1278,6 +1284,20 @@ "node": ">=8.10.0" } }, + "node_modules/@oclif/dev-cli/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, "node_modules/@oclif/dev-cli/node_modules/tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", @@ -1299,6 +1319,99 @@ "node": ">=8.0.0" } }, + "node_modules/@oclif/errors/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@oclif/help": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@oclif/help/-/help-1.0.1.tgz", + "integrity": "sha512-8rsl4RHL5+vBUAKBL6PFI3mj58hjPCp2VYyXD4TAa7IMStikFfOH2gtWmqLzIlxAED2EpD0dfYwo9JJxYsH7Aw==", + "dependencies": { + "@oclif/config": "1.18.2", + "@oclif/errors": "1.3.5", + "chalk": "^4.1.2", + "indent-string": "^4.0.0", + "lodash": "^4.17.21", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "widest-line": "^3.1.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@oclif/help/node_modules/@oclif/config": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@oclif/config/-/config-1.18.2.tgz", + "integrity": "sha512-cE3qfHWv8hGRCP31j7fIS7BfCflm/BNZ2HNqHexH+fDrdF2f1D5S8VmXWLC77ffv3oDvWyvE9AZeR0RfmHCCaA==", + "dependencies": { + "@oclif/errors": "^1.3.3", + "@oclif/parser": "^3.8.0", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-wsl": "^2.1.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@oclif/help/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@oclif/help/node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@oclif/help/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/@oclif/help/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@oclif/linewrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@oclif/linewrap/-/linewrap-1.0.0.tgz", @@ -1499,6 +1612,14 @@ "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", "dev": true }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -1582,6 +1703,12 @@ "@types/node": "*" } }, + "node_modules/@types/lodash": { + "version": "4.14.183", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.183.tgz", + "integrity": "sha512-UXavyuxzXKMqJPEpFPri6Ku5F9af6ZJXUneHhvQJxavrEjuHkFp2YnDWHcxJiG7hk8ZkWqjcyNeW1s/smZv5cw==", + "dev": true + }, "node_modules/@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -1595,10 +1722,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true + "version": "14.18.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.11.tgz", + "integrity": "sha512-zCoCEMA+IPpsRkyCFBqew5vGb7r8RSiB3uwdu/map7uwLAfu1MTazW26/pUDWoNnF88vJz4W3U56i5gtXNqxGg==" }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -2533,6 +2659,20 @@ "node": ">= 6" } }, + "node_modules/bootprint/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, "node_modules/bootstrap": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz", @@ -3075,6 +3215,20 @@ "node": ">=8.0.0" } }, + "node_modules/cli-ux/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, "node_modules/cli-ux/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -3728,6 +3882,20 @@ "url": "https://en.liberapay.com/bootprint.js" } }, + "node_modules/customize-write-files/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, "node_modules/data-urls": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", @@ -5825,9 +5993,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -5836,7 +6004,7 @@ "micromatch": "^4.0.4" }, "engines": { - "node": ">=8" + "node": ">=8.6.0" } }, "node_modules/fast-json-stable-stringify": { @@ -6048,16 +6216,35 @@ "dev": true }, "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dependencies": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=12" + } + }, + "node_modules/fs-extra/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" } }, "node_modules/fs.realpath": { @@ -9586,6 +9773,15 @@ "which": "bin/which" } }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, "node_modules/path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", @@ -9767,6 +9963,14 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -11792,12 +11996,25 @@ "node": ">=0.10.0" } }, + "node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dependencies": { + "inherits": "2.0.3" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "node_modules/util/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -13041,6 +13258,17 @@ "tslib": "^2.0.3" }, "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", @@ -13059,6 +13287,82 @@ "indent-string": "^4.0.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, + "@oclif/help": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@oclif/help/-/help-1.0.1.tgz", + "integrity": "sha512-8rsl4RHL5+vBUAKBL6PFI3mj58hjPCp2VYyXD4TAa7IMStikFfOH2gtWmqLzIlxAED2EpD0dfYwo9JJxYsH7Aw==", + "requires": { + "@oclif/config": "1.18.2", + "@oclif/errors": "1.3.5", + "chalk": "^4.1.2", + "indent-string": "^4.0.0", + "lodash": "^4.17.21", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "widest-line": "^3.1.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "@oclif/config": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@oclif/config/-/config-1.18.2.tgz", + "integrity": "sha512-cE3qfHWv8hGRCP31j7fIS7BfCflm/BNZ2HNqHexH+fDrdF2f1D5S8VmXWLC77ffv3oDvWyvE9AZeR0RfmHCCaA==", + "requires": { + "@oclif/errors": "^1.3.3", + "@oclif/parser": "^3.8.0", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-wsl": "^2.1.1", + "tslib": "^2.0.0" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==" + }, + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } } }, "@oclif/linewrap": { @@ -13241,6 +13545,14 @@ "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", "dev": true }, + "@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "requires": { + "@types/node": "*" + } + }, "@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -13324,6 +13636,12 @@ "@types/node": "*" } }, + "@types/lodash": { + "version": "4.14.183", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.183.tgz", + "integrity": "sha512-UXavyuxzXKMqJPEpFPri6Ku5F9af6ZJXUneHhvQJxavrEjuHkFp2YnDWHcxJiG7hk8ZkWqjcyNeW1s/smZv5cw==", + "dev": true + }, "@types/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", @@ -13337,10 +13655,9 @@ "dev": true }, "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true + "version": "14.18.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.11.tgz", + "integrity": "sha512-zCoCEMA+IPpsRkyCFBqew5vGb7r8RSiB3uwdu/map7uwLAfu1MTazW26/pUDWoNnF88vJz4W3U56i5gtXNqxGg==" }, "@types/normalize-package-data": { "version": "2.4.1", @@ -13973,6 +14290,17 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } } } }, @@ -14418,6 +14746,17 @@ "tslib": "^2.0.0" }, "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -14953,6 +15292,19 @@ "deep-aplus": "^1.0.4", "fs-extra": "^8.1.0", "stream-compare": "^2.0.0" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } } }, "data-urls": { @@ -16539,9 +16891,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -16730,13 +17082,29 @@ "dev": true }, "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "requires": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "dependencies": { + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + } } }, "fs.realpath": { @@ -19457,6 +19825,15 @@ } } }, + "path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "requires": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", @@ -19586,6 +19963,11 @@ } } }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -21145,6 +21527,21 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + } + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 4e8aed8..1c7b3ea 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "@oclif/dev-cli": "^1.26.6", "@types/jest": "^27.0.3", "@types/jest-expect-message": "^1.0.3", - "@types/node": "^10.17.60", + "@types/lodash": "^4.14.183", + "@types/node": "^14.18.11", "@types/object-path": "^0.11.1", "@types/uuid": "^8.3.3", "@typescript-eslint/eslint-plugin": "^5.5.0", @@ -77,10 +78,15 @@ "@oclif/command": "1.8.7", "@oclif/config": "1.18.1", "@oclif/errors": "1.3.5", + "@oclif/help": "^1.0.1", "@oclif/plugin-help": "3.2.10", + "@types/fs-extra": "^9.0.13", "ajv": "8.8.2", + "fs-extra": "^10.1.0", "glob": "7.2.0", + "lodash": "^4.17.21", "object-path": "0.11.8", + "path": "^0.12.7", "tslib": "1.14.1", "yaml": "1.10.2" } diff --git a/src/commands/export/c4.ts b/src/commands/export/c4.ts new file mode 100644 index 0000000..ac78e5f --- /dev/null +++ b/src/commands/export/c4.ts @@ -0,0 +1,43 @@ +import { Command, flags } from '@oclif/command'; +import { C4Docs } from '../../exporters/c4/c4'; +import { YAAN } from '../../yaan/yaan'; + +export default class C4Plantuml extends Command { + static description = 'Export to C4 PlantUML'; + + static examples = [ + `$ yaan export c4 -p presentation path-to-my-yaan-project path-to-result-directory`, + ]; + + static flags = { + help: flags.help({ char: 'h' }), + presentation: flags.string({ + char: 'p', + description: 'path to relative project presentation to export', + required: true, + }), + }; + + static args = [{ name: 'projectPath' }, { name: 'resultPath' }]; + + async run() { + try { + const { args, flags } = this.parse(C4Plantuml); + if (args.projectPath && args.resultPath) { + const yaan = new YAAN(); + const project = yaan.loadProjectFromDir(args.projectPath); + const c4 = new C4Docs(project, flags.presentation); + + await c4.print(args.resultPath); + } else { + this.error( + 'Please specify path to project directory and path to write result', + ); + return -1; + } + } catch (e) { + console.error(e); + this.error(e as Error); + } + } +} diff --git a/src/commands/export/split.ts b/src/commands/export/split.ts new file mode 100644 index 0000000..f5b3def --- /dev/null +++ b/src/commands/export/split.ts @@ -0,0 +1,43 @@ +import { Command, flags } from '@oclif/command'; +import { SplitDocs } from '../../exporters/split/splitUml'; +import { YAAN } from '../../yaan/yaan'; + +export default class SplitPlantuml extends Command { + static description = 'Export to PlantUMLs splitted by components'; + + static examples = [ + `$ yaan export split -p presentation path-to-my-yaan-project path-to-result-directory`, + ]; + + static flags = { + help: flags.help({ char: 'h' }), + presentation: flags.string({ + char: 'p', + description: 'path to relative project presentation to export', + required: true, + }), + }; + + static args = [{ name: 'projectPath' }, { name: 'resultPath' }]; + + async run() { + try { + const { args, flags } = this.parse(SplitPlantuml); + if (args.projectPath && args.resultPath) { + const yaan = new YAAN(); + const project = yaan.loadProjectFromDir(args.projectPath); + const split = new SplitDocs(project, flags.presentation); + + await split.print(args.resultPath); + } else { + this.error( + 'Please specify path to project directory and path to write result', + ); + return -1; + } + } catch (e) { + console.error(e); + this.error(e as Error); + } + } +} diff --git a/src/exporters/c4/c4.ts b/src/exporters/c4/c4.ts new file mode 100644 index 0000000..20a1de9 --- /dev/null +++ b/src/exporters/c4/c4.ts @@ -0,0 +1,198 @@ +import _ from 'lodash'; +import path from 'path'; +import fs from 'fs-extra'; +import { ProjectContainer } from '../../yaan/types'; +import { PlantUml } from '../plantUml/plantUml'; +import { PlantUmlObject } from '../plantUml/plantUmlObject'; +import { PlantUmlGroup } from '../plantUml/plantUmlGroup'; +import { PlantUmlComponentGroup } from '../plantUml/plantUmlComponentGroup'; +import { PlantUmlDeploymentGroup } from '../plantUml/plantUmlDeploymentGroup'; + +interface ContainerLevel { + data: PlantUmlObject; + components: PlantUmlObject[]; +} + +interface SystemLevel { + data: PlantUmlObject; + containers: ContainerLevel[]; +} + +interface C4Uml { + title: string; + context: PlantUmlObject; + systems: SystemLevel[]; +} + +export class C4Docs { + public readonly docC4: C4Uml; + constructor( + private readonly project: ProjectContainer, + private readonly presentation: string, + ) { + const plantUml = new PlantUml(project, presentation); + + const systems: PlantUmlObject[] = []; + for (const puml of plantUml.children[0].children) { + if (puml.children.length > 0) { + systems.push(_.cloneDeep(puml)); + } + } + const contextPuml = plantUml; + const systemsPuml: Record[] = []; + for (const system of systems) { + for (const sysItem of system.children) { + systemsPuml.push({ + data: sysItem, + containers: [], + }); + } + } + for (const systemPuml of systemsPuml) { + if (systemPuml.data.children.length > 0) { + for (const container of systemPuml.data.children) { + systemPuml.containers.push({ + data: _.cloneDeep(container), + components: [], + }); + if (container.children.length > 0) { + for (const component of container.children) { + systemPuml.containers[ + systemPuml.containers.length - 1 + ].components.push(_.cloneDeep(component)); + } + container.children.length = 0; + } + } + systemPuml.data.children.length = 0; + } + } + + this.docC4 = { + title: presentation, + context: contextPuml, + systems: systemsPuml as SystemLevel[], + }; + } + + private _getGroupInfo(groupObject: PlantUmlObject): { + title: string; + description?: string; + } { + const defaultInfo = `${this.presentation}-group#${groupObject.id}`; + if (groupObject instanceof PlantUmlGroup) { + return { + title: groupObject.title || defaultInfo, + }; + } else if (groupObject instanceof PlantUmlComponentGroup) { + return { + title: groupObject.title || defaultInfo, + description: groupObject.group.title, + }; + } else if (groupObject instanceof PlantUmlDeploymentGroup) { + return { + title: groupObject.deploymentGroup.title || defaultInfo, + description: groupObject.deploymentGroup.solution, + }; + } + return { title: defaultInfo }; + } + + public async print(resultPath: string): Promise { + const resPath = path.join(resultPath, this.docC4.title); + await fs.emptyDir(resPath); + await fs.outputFile(path.join(resPath, 'context.md'), this.docC4.title); + await fs.outputFile( + path.join(resPath, 'context.puml'), + `${this.header}${this.docC4.context.print()}${this.footer}`, + ); + + for (const system of this.docC4.systems) { + const { title, description } = this._getGroupInfo(system.data); + const sysPath = path.join(resPath, title); + await fs.outputFile( + path.join(sysPath, 'system.md'), + description || title, + ); + await fs.outputFile( + path.join(sysPath, 'system.puml'), + `${this.header}${system.data.print()}${this.footer}`, + ); + for (const container of system.containers) { + const { title, description } = this._getGroupInfo( + container.data, + ); + const contPath = path.join(sysPath, title); + await fs.outputFile( + path.join(contPath, 'container.md'), + description || title, + ); + await fs.outputFile( + path.join(contPath, 'container.puml'), + `${this.header}${container.data.print()}${this.footer}`, + ); + for (const component of container.components) { + const { title, description } = + this._getGroupInfo(component); + const compPath = path.join(contPath, title); + await fs.outputFile( + path.join(compPath, 'component.md'), + description || title, + ); + await fs.outputFile( + path.join(compPath, 'component.puml'), + `${this.header}${component.print()}${this.footer}`, + ); + } + } + } + } + + protected get header(): string { + return ` + @startuml + !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4.puml + !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Deployment.puml + !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + !define FONTAWESOME https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/font-awesome-5 + !define FONTAWESOME1 https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/font-awesome + !define DEVICONS https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons2 + !include FONTAWESOME/server.puml + !include FONTAWESOME/envelope.puml + !include FONTAWESOME1/microchip.puml + !include FONTAWESOME1/hdd_o.puml + !include FONTAWESOME1/lock.puml + !include DEVICONS/kubernetes.puml + + AddElementTag("hiddenGroup", $bgColor = "transparent", $borderColor="transparent") + AddElementTag("visibleGroup", $bgColor = "transparent", $fontColor="#3B3A2F") + AddElementTag("organization", $bgColor = "#FAF8C9", $shadowing="true", $shape=RoundedBoxShape()) + AddElementTag("software", $bgColor = "#BAB995") + AddElementTag("infra", $bgColor = "#BAB995") + AddElementTag("fallback", $bgColor="#c0c0c0") + + AddRelTag("fallback", $textColor="#c0c0c0", $lineColor="#438DD5") + AddRelTag("uses-external", $textColor="#ff0000", $lineColor="#ff0000", $lineStyle=BoldLine()) + AddRelTag("uses-internal", $textColor="#3B3A2F", $lineColor="#3B3A2F", $lineStyle=DashedLine()) + AddRelTag("deployed-on", $textColor="#3B3A2F", $lineColor="#3B3A2F", $lineStyle=DashedLine()) + AddRelTag("clustered-on", $textColor="#3B3A2F", $lineColor="#3B3A2F", $lineStyle=DashedLine()) + + AddElementTag("deploymentGroup", $bgColor="#BAB995") + AddElementTag("deployment", $shadowing = true, $bgColor = "transparent") + AddElementTag("server", $shadowing = true, $bgColor="#E0DFB4") + AddElementTag("kubernetesCluster", $shadowing = true) + AddElementTag("hidden", $shadowing = false, $shape = EightSidedShape(), $bgColor="#444444") + + + WithoutPropertyHeader() + + `; + } + + protected get footer(): string { + return ` + SHOW_LEGEND() + @enduml + `; + } +} diff --git a/src/exporters/split/plantUmlComponent.ts b/src/exporters/split/plantUmlComponent.ts new file mode 100644 index 0000000..b496679 --- /dev/null +++ b/src/exporters/split/plantUmlComponent.ts @@ -0,0 +1,35 @@ +import { PlantUmlObject } from './plantUmlObject'; +import { SolutionComponent } from '../../yaan/schemas/solution'; + +export class PlantUmlComponent extends PlantUmlObject { + constructor( + public readonly id: string, + public readonly component: SolutionComponent, + ) { + super(id); + } + + private getContainerFigure() { + switch (this.component.kind) { + case 'db': + return 'ContainerDb'; + case 'queue': + return 'ContainerQueue'; + default: + return 'Container'; + } + } + + protected get header(): string { + return ` + ${this.getContainerFigure()}("${this.id}", "${ + this.component.title || '' + }", "${this.component.description || 'Component'}", "", "") { + `; + } + + protected get footer(): string { + return ` + }`; + } +} diff --git a/src/exporters/split/plantUmlComponentGroup.ts b/src/exporters/split/plantUmlComponentGroup.ts new file mode 100644 index 0000000..e743d8c --- /dev/null +++ b/src/exporters/split/plantUmlComponentGroup.ts @@ -0,0 +1,11 @@ +import { SolutionComponentGroup } from '../../yaan/schemas/solution'; +import { GroupVisibility, PlantUmlGroup } from './plantUmlGroup'; + +export class PlantUmlComponentGroup extends PlantUmlGroup { + constructor( + public readonly id: string, + public readonly group: SolutionComponentGroup, + ) { + super(id, GroupVisibility.HiddenIfEmpty, group.title); + } +} diff --git a/src/exporters/split/plantUmlComponentPort.ts b/src/exporters/split/plantUmlComponentPort.ts new file mode 100644 index 0000000..33d8bbb --- /dev/null +++ b/src/exporters/split/plantUmlComponentPort.ts @@ -0,0 +1,37 @@ +import { PlantUmlObject } from './plantUmlObject'; +import { + SolutionPort, + SolutionPortDetailed, +} from '../../yaan/schemas/solution'; + +export class PlantUmlComponentPort extends PlantUmlObject { + constructor( + public readonly id: string, + public readonly port: SolutionPort, + public readonly portName: string, + ) { + super(id); + } + + protected get header(): string { + const port: SolutionPortDetailed = + typeof this.port === 'object' + ? this.port + : { + number: this.port, + description: '', + protocol: 'TCP', + }; + return ` + AddProperty("${port.protocol || 'TCP'}", "${port.number}") + Deployment_Node("${this.id}", "${ + this.portName || port.description + }", "Port", "${port.description || ''}") { + `; + } + + protected get footer(): string { + return ` + }`; + } +} diff --git a/src/exporters/split/plantUmlDeployment.ts b/src/exporters/split/plantUmlDeployment.ts new file mode 100644 index 0000000..ffcc0ad --- /dev/null +++ b/src/exporters/split/plantUmlDeployment.ts @@ -0,0 +1,26 @@ +import { PlantUmlObject } from './plantUmlObject'; +import { Deployment } from '../../yaan/schemas/deployment'; + +export class PlantUmlDeployment extends PlantUmlObject { + constructor( + public readonly id: string, + public readonly deployment: Deployment, + public readonly showDetails: boolean, + ) { + super(id); + } + + protected get header(): string { + return ` + Deployment_Node("${this.id}", "${ + this.deployment.title || '' + }", "Deployment", "${ + this.deployment.description || '' + }", "", $tags="deployment${!this.showDetails ? '+hidden' : ''}"){ + `; + } + + protected get footer(): string { + return '}'; + } +} diff --git a/src/exporters/split/plantUmlDeploymentGroup.ts b/src/exporters/split/plantUmlDeploymentGroup.ts new file mode 100644 index 0000000..cf14b7f --- /dev/null +++ b/src/exporters/split/plantUmlDeploymentGroup.ts @@ -0,0 +1,26 @@ +import { PlantUmlObject } from './plantUmlObject'; +import { DeploymentGroup } from '../../yaan/schemas/deployment'; + +export class PlantUmlDeploymentGroup extends PlantUmlObject { + constructor( + public readonly id: string, + public readonly deploymentGroup: DeploymentGroup, + public readonly showDetails: boolean, + ) { + super(id); + } + + protected get header(): string { + return ` + Deployment_Node("${this.id}", "${ + this.deploymentGroup.title || ' ' + }", "Deployment group", "", $tags="deploymentGroup${ + !this.showDetails ? ',hidden' : '' + }"){ + `; + } + + protected get footer(): string { + return '}'; + } +} diff --git a/src/exporters/split/plantUmlGroup.ts b/src/exporters/split/plantUmlGroup.ts new file mode 100644 index 0000000..81603b3 --- /dev/null +++ b/src/exporters/split/plantUmlGroup.ts @@ -0,0 +1,39 @@ +import { PlantUmlObject } from './plantUmlObject'; + +export enum GroupVisibility { + Hidden, + HiddenIfEmpty, + Visible, +} + +export class PlantUmlGroup extends PlantUmlObject { + private isC1Leaf = false; + constructor( + public readonly id: string, + public readonly visibility: GroupVisibility = GroupVisibility.Hidden, + public readonly title?: string, + public readonly tags?: string[], + ) { + super(id); + } + + protected get header(): string { + const hidden = this.visibility === GroupVisibility.Hidden; + + return ` + Deployment_Node("${this.id || 'unknown'}", "${ + (!hidden && this.title) || ' ' + }", $tags="${this.tags ? this.tags.join('+') + '+' : ''}${ + hidden ? 'hiddenGroup' : this.isC1Leaf ? 'c1Leaf' : 'visibleGroup' + }"){ + `; + } + + protected get footer(): string { + return '}'; + } + + public setC1Leaf(isLeaf: boolean) { + this.isC1Leaf = isLeaf; + } +} diff --git a/src/exporters/split/plantUmlKubernetesCluster.ts b/src/exporters/split/plantUmlKubernetesCluster.ts new file mode 100644 index 0000000..b24d415 --- /dev/null +++ b/src/exporters/split/plantUmlKubernetesCluster.ts @@ -0,0 +1,43 @@ +import { PlantUmlObject } from './plantUmlObject'; +import { KubernetesCluster } from '../../yaan/schemas/kubernetesCluster'; + +export class PlantUmlKubernetesCluster extends PlantUmlObject { + constructor( + public readonly id: string, + public readonly cluster: KubernetesCluster, + public readonly showDetails: boolean, + ) { + super(id); + } + + private renderProps(): string { + if (!this.showDetails) { + return ''; + } + const props = []; + if (this.cluster.distribution) { + props.push( + `AddProperty("Distribution", "${this.cluster.distribution}")`, + ); + } + if (this.cluster.version) { + props.push(`AddProperty("Version", "${this.cluster.version}")`); + } + return props.join('\n'); + } + + protected get header(): string { + return ` + ${this.renderProps()} + Deployment_Node("${this.id}", "${ + this.showDetails ? this.cluster.title : '***' + }", "Kubernetes Cluster", "", $sprite=kubernetes, $tags="kubernetesCluster${ + !this.showDetails ? ',hidden' : '' + }"){ + `; + } + + protected get footer(): string { + return '}'; + } +} diff --git a/src/exporters/split/plantUmlLayout.ts b/src/exporters/split/plantUmlLayout.ts new file mode 100644 index 0000000..282f38d --- /dev/null +++ b/src/exporters/split/plantUmlLayout.ts @@ -0,0 +1,28 @@ +import { PlantUmlObject } from './plantUmlObject'; + +export enum LayoutDirection { + Up = 'U', + Down = 'D', + Right = 'R', + Left = 'L', +} + +export class PlantUmlLayout extends PlantUmlObject { + constructor( + public readonly fromKey: string, + public readonly toKey: string, + public readonly direction: LayoutDirection, + ) { + super(''); + } + + protected get header(): string { + return ` + Lay${'_' + this.direction}("${this.fromKey}", "${this.toKey}") + `; + } + + protected get footer(): string { + return ''; + } +} diff --git a/src/exporters/split/plantUmlObject.ts b/src/exporters/split/plantUmlObject.ts new file mode 100644 index 0000000..63f3b5c --- /dev/null +++ b/src/exporters/split/plantUmlObject.ts @@ -0,0 +1,18 @@ +export abstract class PlantUmlObject { + public readonly children: PlantUmlObject[] = []; + public parents: string[] = []; + + constructor(public readonly id: string) {} + + protected abstract get header(): string; + + protected abstract get footer(): string; + + public print(): string { + return ` + ${this.header} + ${this.children.map((c) => c.print()).join('\n')} + ${this.footer} + `; + } +} diff --git a/src/exporters/split/plantUmlOrganization.ts b/src/exporters/split/plantUmlOrganization.ts new file mode 100644 index 0000000..6232769 --- /dev/null +++ b/src/exporters/split/plantUmlOrganization.ts @@ -0,0 +1,34 @@ +import { PlantUmlObject } from './plantUmlObject'; +import { Organization } from '../../yaan/schemas/organization'; + +export class PlantUmlOrganization extends PlantUmlObject { + constructor( + public readonly id: string, + public readonly organization: Organization, + ) { + super(id); + } + + private renderProps(): string { + const props = []; + if (this.organization.description) { + props.push( + `AddProperty("Description", "${this.organization.description}")`, + ); + } + return props.join('\n'); + } + + protected get header(): string { + return ` + ${this.renderProps()} + Deployment_Node("${this.id}", "${ + this.organization.title + }", "Organization", "", $tags="organization"){ + `; + } + + protected get footer(): string { + return '}'; + } +} diff --git a/src/exporters/split/plantUmlPresentation.ts b/src/exporters/split/plantUmlPresentation.ts new file mode 100644 index 0000000..d855369 --- /dev/null +++ b/src/exporters/split/plantUmlPresentation.ts @@ -0,0 +1,19 @@ +import { PlantUmlObject } from './plantUmlObject'; +import { Presentation } from '../../yaan/schemas/presentation'; + +export class PlantUmlPresentation extends PlantUmlObject { + constructor( + public readonly id: string, + public readonly presentation: Presentation, + ) { + super(id); + } + + protected get header(): string { + return `Deployment_Node("${this.id}", "${this.presentation.title}", ""){`; + } + + protected get footer(): string { + return '}'; + } +} diff --git a/src/exporters/split/plantUmlRelation.ts b/src/exporters/split/plantUmlRelation.ts new file mode 100644 index 0000000..1e0f7c4 --- /dev/null +++ b/src/exporters/split/plantUmlRelation.ts @@ -0,0 +1,43 @@ +import { PlantUmlObject } from './plantUmlObject'; + +export enum RelationDirection { + None = '', + Up = 'U', + Down = 'D', + Right = 'R', + Left = 'L', +} + +export class PlantUmlRelation extends PlantUmlObject { + constructor( + public readonly fromKey: string, + public readonly toKey: string, + public readonly type: string, + public readonly direction: RelationDirection = RelationDirection.None, + public readonly title?: string, + ) { + super(''); + } + + protected get header(): string { + return ` + Rel${this.direction ? '_' + this.direction : ''}("${this.fromKey}", "${ + this.toKey + }", "${this.title || ' '}", $tags="${this.type}") + `; + } + + protected get footer(): string { + return ''; + } + + public getParams() { + return { + fromKey: this.fromKey, + toKey: this.toKey, + type: this.type, + direction: this.direction, + title: this.title, + }; + } +} diff --git a/src/exporters/split/plantUmlServer.ts b/src/exporters/split/plantUmlServer.ts new file mode 100644 index 0000000..cf9dc94 --- /dev/null +++ b/src/exporters/split/plantUmlServer.ts @@ -0,0 +1,192 @@ +import { PlantUmlObject } from './plantUmlObject'; +import { + Server, + ServerCpu, + ServerDisk, + ServerFirewallPortsRule, + ServerFirewallRule, +} from '../../yaan/schemas/server'; + +export class PlantUmlServer extends PlantUmlObject { + constructor( + public readonly id: string, + public readonly server: Server, + public readonly showDetails: boolean, + ) { + super(id); + } + + private renderCpus() { + if (!this.showDetails) { + return ''; + } + if (this.server.hardware?.cpus) { + const cpusStr = this.server.hardware?.cpus + .map( + (cpu: ServerCpu, i: number) => ` + Deployment_Node("${this.id}/cpus/${i}", "CPU #${i}", "${ + cpu.model || '' + }", "${cpu.cores ? cpu.cores + ' cores' : ''}"){ + } + `, + ) + .join('\n'); + return ` + Deployment_Node("${this.id}/cpus", "CPUs", "", "", "microchip"){ + ${cpusStr} + } + `; + } + return ''; + } + + private renderDisks() { + if (!this.showDetails) { + return ''; + } + if (this.server.hardware?.disks) { + const str = this.server.hardware?.disks + .map( + (disk: ServerDisk, i: number) => ` + Deployment_Node("${this.id}/disks/${i}", "${ + disk.devPath || '' + }", "${disk.size || ''}"){ + } + `, + ) + .join('\n'); + return ` + Deployment_Node("${this.id}/disks", "Disks", "", "", "hdd_o"){ + ${str} + } + `; + } + return ''; + } + + private renderFirewall() { + if (!this.showDetails) { + return ''; + } + if (this.server.firewall) { + const rules = { + inbound: [], + outbound: [], + }; + const addRules = ( + portsRule: ServerFirewallPortsRule, + rules: any[], + ) => { + for (const rule of Object.values(portsRule)) { + let portsStr; + let hostsStr; + let descStr = ''; + if (Number.isFinite(rule)) { + portsStr = String(rule); + hostsStr = '*'; + } else { + const detailedRule = rule as ServerFirewallRule; + descStr = detailedRule.description || ''; + const ports = []; + if (detailedRule.ports) { + for (const portVal of Object.values( + detailedRule.ports, + )) { + if (typeof portVal === 'object') { + ports.push(`${portVal.from}-${portVal.to}`); + } else { + ports.push(portVal); + } + } + } else { + ports.push('*'); + } + portsStr = ports.join(', '); + if (detailedRule.hosts) { + hostsStr = detailedRule.hosts.join(', '); + } else { + hostsStr = '*'; + } + } + rules.push({ + hosts: hostsStr, + ports: portsStr, + description: descStr, + }); + } + }; + if (this.server.firewall.inboundPorts) { + addRules(this.server.firewall.inboundPorts, rules.inbound); + } + if (this.server.firewall.outboundPorts) { + addRules(this.server.firewall.outboundPorts, rules.outbound); + } + if (rules.inbound.length || rules.outbound.length) { + return ` + Deployment_Node("${ + this.id + }/firewall", "Firewall", "", "", "lock"){ + AddProperty("Host(s)", "Port(s)") + ${rules.inbound + .map( + (rule: any) => + `AddProperty("${rule.hosts}", "${rule.ports}")`, + ) + .join('\n')} + Deployment_Node("${this.id}/firewall/inbound", "Inbound"){ + } + AddProperty("Host(s)", "Port(s)") + ${rules.outbound + .map( + (rule: any) => + `AddProperty("${rule.hosts}", "${rule.ports}")`, + ) + .join('\n')} + Deployment_Node("${this.id}/firewall/outbound", "Outbound"){ + } + }`; + } + } + return ''; + } + + private renderProps() { + if (!this.showDetails) { + return ''; + } + return ` + AddProperty("Type", "${this.server.type || '-'}") + AddProperty("OS Type", "${this.server.osType || '-'}") + AddProperty("OS Name", "${this.server.osName || '-'}") + AddProperty("Is pool", "${!!this.server.pool}") + ${ + !!this.server.pool + ? ` + AddProperty('Minimal scale', '${this.server.pool?.minScale}') + AddProperty('Maximal scale', '${this.server.pool?.maxScale || '∞'}')` + : '' + } + AddProperty("RAM", "${this.server.hardware?.memory || '-'}") + `; + } + + protected get header(): string { + return ` + ${this.renderProps()} + Deployment_Node("${this.id}", "${ + this.showDetails ? this.server.title : '***' + }", "Server", "${ + this.server.description || '' + }", $sprite=server, $tags="server${ + !this.showDetails ? ',hidden' : '' + }"){ + ${this.renderCpus()} + ${this.renderDisks()} + ${this.renderFirewall()} + `; + } + + protected get footer(): string { + return '}'; + } +} diff --git a/src/exporters/split/splitUml.ts b/src/exporters/split/splitUml.ts new file mode 100644 index 0000000..8568e57 --- /dev/null +++ b/src/exporters/split/splitUml.ts @@ -0,0 +1,534 @@ +import path from 'path'; +import fs from 'fs-extra'; +import _ from 'lodash'; +import { PlantUmlObject } from './plantUmlObject'; +import { ProjectContainer } from '../../yaan/types'; +import { createGraph, GraphRelation, RelationType } from '../graph/graph'; +import { PlantUmlServer } from './plantUmlServer'; +import { PlantUmlPresentation } from './plantUmlPresentation'; +import { PlantUmlKubernetesCluster } from './plantUmlKubernetesCluster'; +import { PlantUmlDeployment } from './plantUmlDeployment'; +import { PlantUmlDeploymentGroup } from './plantUmlDeploymentGroup'; +import { PlantUmlComponent } from './plantUmlComponent'; +import { PlantUmlComponentPort } from './plantUmlComponentPort'; +import { PlantUmlRelation, RelationDirection } from './plantUmlRelation'; +import { PlantUmlComponentGroup } from './plantUmlComponentGroup'; +import { PlantUmlOrganization } from './plantUmlOrganization'; +import { GroupVisibility, PlantUmlGroup } from './plantUmlGroup'; +import { LayoutDirection, PlantUmlLayout } from './plantUmlLayout'; + +interface Group { + group: PlantUmlObject; + servers: PlantUmlObject; + clusters: PlantUmlObject; + deployments: PlantUmlObject; +} + +interface SplitPlantUml { + title: string; + context: PlantUmlPresentation; + componentGroups: { + data: PlantUmlObject; + componentsUml: PlantUmlObject[]; // for components level if needed + }[]; +} + +export class SplitDocs { + public readonly doc: SplitPlantUml; + constructor( + private readonly project: ProjectContainer, + private readonly presentation: string, + ) { + const graph = createGraph(project); + const pres = graph.get('presentations').get(presentation); + if (!pres.value) { + throw new Error(`Presentation not found: ${presentation}`); + } + + const contextUml = new PlantUmlPresentation( + pres.key, + pres.value.presentation, + ); + const c1Leaves: PlantUmlObject[] = []; + const layouts: PlantUmlLayout[] = []; + const plantGroups: Record = {}; + const ensureGroup = ( + ownership: Set, + parent?: PlantUmlObject, + ): Group => { + const groupId = Array.from(ownership).join(',\n') || 'commongroup'; + if (plantGroups[groupId]) { + return plantGroups[groupId]; + } else { + const plantGroup = new PlantUmlGroup(groupId + 'group'); + const infra = new PlantUmlGroup( + groupId + 'infra', + GroupVisibility.HiddenIfEmpty, + 'Infrastructure', + ['infra'], + ); + const servers = new PlantUmlGroup( + groupId + 'servers', + GroupVisibility.HiddenIfEmpty, + 'Servers', + ); + const clusters = new PlantUmlGroup( + groupId + 'clusters', + GroupVisibility.HiddenIfEmpty, + 'Virtualization', + ); + const deployments = new PlantUmlGroup( + groupId + 'deployments', + GroupVisibility.HiddenIfEmpty, + 'Software', + ['software'], + ); + infra.children.push(clusters, servers); + clusters.parents = [...infra.parents, infra.id]; + servers.parents = [...infra.parents, infra.id]; + plantGroup.children.push(deployments, infra); + deployments.parents = [...plantGroup.parents, plantGroup.id]; + infra.parents = [...plantGroup.parents, plantGroup.id]; + + layouts.push( + new PlantUmlLayout( + deployments.id, + infra.id, + LayoutDirection.Down, + ), + ); + layouts.push( + new PlantUmlLayout( + clusters.id, + servers.id, + LayoutDirection.Down, + ), + ); + if (parent) { + parent.children.push(plantGroup); + plantGroup.parents = [...parent.parents, parent.id]; + } else { + contextUml.children.push(plantGroup); + plantGroup.parents = [...contextUml.parents, contextUml.id]; + } + plantGroups[groupId] = { + group: plantGroup, + servers, + clusters, + deployments, + }; + return plantGroups[groupId]; + } + }; + pres.get('organizations').each((org) => { + const plantOrg = new PlantUmlOrganization( + org.key, + org.get('organization').value, + ); + contextUml.children.push(plantOrg); + plantOrg.parents = [...contextUml.parents, contextUml.id]; + ensureGroup(new Set([org.key]), plantOrg); + }); + pres.get('deployments').each((deployment) => { + const plantDep = new PlantUmlDeployment( + deployment.key, + deployment.get('deployment').value, + deployment.get('showDetails').value, + ); + const ownershipGroup = ensureGroup(deployment.value.ownership); + ownershipGroup.deployments.children.push(plantDep); + plantDep.parents = [ + ...ownershipGroup.deployments.parents, + ownershipGroup.deployments.id, + ]; + deployment.get('groups').each((group) => { + const plantGroup = new PlantUmlDeploymentGroup( + group.key, + group.get('deploymentGroup').value, + deployment.get('showDetails').value, + ); + plantDep.children.push(plantGroup); + plantGroup.parents = [...plantDep.parents, plantDep.id]; + const componentGroups: Record = + {}; + + group.get('components').each((component) => { + if (!component.value.show) { + return; + } + const plantComponent = new PlantUmlComponent( + component.key, + component.get('component').value, + ); + if (component.value.componentGroupName) { + const groupName = component.value.componentGroupName; + if (!componentGroups[groupName]) { + const g = group + .get('componentGroups') + .get(groupName); + const plantComponentGroup = + new PlantUmlComponentGroup( + g.key, + g.value.group, + ); + componentGroups[groupName] = plantComponentGroup; + plantGroup.children.push(plantComponentGroup); + plantComponentGroup.parents = [ + ...plantGroup.parents, + plantGroup.id, + ]; + c1Leaves.push(plantComponentGroup); + plantComponentGroup.setC1Leaf(true); + } + componentGroups[groupName].children.push( + plantComponent, + ); + plantComponent.parents = [ + ...componentGroups[groupName].parents, + componentGroups[groupName].id, + ]; + } else { + plantGroup.children.push(plantComponent); + plantComponent.parents = [ + ...plantGroup.parents, + plantGroup.id, + ]; + } + component.get('ports').each((port, portName) => { + const plantPort = new PlantUmlComponentPort( + port.key, + port.value, + portName, + ); + plantComponent.children.push(plantPort); + plantPort.parents = [ + ...plantComponent.parents, + plantComponent.id, + ]; + }); + }); + }); + }); + pres.get('servers').each((server) => { + const plantSrv = new PlantUmlServer( + server.key, + server.get('server').value, + server.get('showHardwareDetails').value, + ); + ensureGroup(server.value.ownership).servers.children.push(plantSrv); + }); + pres.get('kubernetesClusters').each((cluster) => { + const plantCluster = new PlantUmlKubernetesCluster( + cluster.key, + cluster.get('kubernetesCluster').value, + cluster.get('showDetails').value, + ); + ensureGroup(cluster.value.ownership).clusters.children.push( + plantCluster, + ); + }); + + const fullDoc = _.cloneDeep(contextUml); + const compGroupsUml: { + data: PlantUmlObject; + componentsUml: PlantUmlObject[]; + }[] = []; + for (const leaf of c1Leaves) { + const pres = _.cloneDeep(contextUml as PlantUmlObject); + let root = pres; + for (let i = 1; i < leaf.parents.length; i++) { + const object = this._findById(contextUml, leaf.parents[i]); + if (object) { + root.children.length = 0; + root.children.push(_.cloneDeep(object)); + root = root.children[0]; + } + } + root.children.length = 0; + root.children.push(_.cloneDeep(leaf)); + compGroupsUml.push({ data: pres, componentsUml: [] }); + leaf.children.length = 0; + } + + for (const layout of layouts) { + if ( + this._findById(contextUml, layout.fromKey) && + this._findById(contextUml, layout.toKey) + ) { + contextUml.children.push(layout); + } + for (const group of compGroupsUml) { + if ( + this._findById(group.data, layout.fromKey) && + this._findById(group.data, layout.toKey) + ) { + contextUml.children.push(layout); + } + } + } + + pres.get('relations').each((relation) => { + const relationVal = relation.value; + if (relationVal.type === RelationType.OwnedBy) { + return; + } + let direction: RelationDirection = RelationDirection.None; + switch (relationVal.type) { + case RelationType.DeployedOn: + case RelationType.ClusteredOn: + direction = RelationDirection.Down; + break; + case RelationType.UsesExternal: + direction = RelationDirection.Right; + break; + } + this._addRelToObject( + fullDoc, + contextUml, + relationVal, + direction, + true, + ); + for (const doc of compGroupsUml) { + // doc.data: presentation - puml group - deployment - deployment group + this._addRelToObject( + fullDoc, + doc.data.children[0].children[0].children[0].children[0], + relationVal, + direction, + false, + ); + } + }); + + this.doc = { + title: presentation, + context: contextUml, + componentGroups: compGroupsUml, + }; + } + + private _findById( + object: PlantUmlObject, + id: string, + ): PlantUmlObject | null { + for (const child of object.children) { + if (child.id === id) { + return child; + } + const subChild = this._findById(child, id); + if (subChild) { + return subChild; + } + } + return null; + } + + private _findParentInObject( + object: PlantUmlObject, + parents: string[], + ): string | null { + for (let i = parents.length - 1; i >= 0; i--) { + if (this._findById(object, parents[i])) { + return parents[i]; + } + } + return null; + } + + private _hasDuplicateRelation( + object: PlantUmlObject, + from: string, + to: string, + relType: string, + relDir: string, + descr?: string, + ) { + for (const rel of object.children) { + if (rel instanceof PlantUmlRelation) { + const { fromKey, toKey, type, direction, title } = + rel.getParams(); + if ( + from === fromKey && + to === toKey && + relType === type && + relDir === direction && + descr === title + ) { + return true; + } + } + } + return false; + } + + private _addRelToObject( + fullDoc: PlantUmlObject, + currentDoc: PlantUmlObject, + relationVal: GraphRelation, + direction: RelationDirection, + searchExternal: boolean, + ): void { + let src: string | null = relationVal.src.key; + let dest: string | null = relationVal.dest.key; + if (!this._findById(currentDoc, relationVal.src.key)) { + if (searchExternal) { + const object = this._findById(fullDoc, relationVal.src.key); + if (object && object.parents.length > 0) { + src = this._findParentInObject(currentDoc, object.parents); + } + } else { + src = null; + } + } + if (!this._findById(currentDoc, relationVal.dest.key)) { + if (searchExternal) { + const object = this._findById(fullDoc, relationVal.dest.key); + if (object && object.parents.length > 0) { + dest = this._findParentInObject(currentDoc, object.parents); + } + } else { + dest = null; + } + } + + if (src && dest && src !== dest) { + if ( + !this._hasDuplicateRelation( + currentDoc, + src, + dest, + relationVal.type, + direction, + relationVal.props?.description, + ) + ) { + currentDoc.children.push( + new PlantUmlRelation( + src, + dest, + relationVal.type, + direction, + relationVal.props?.description, + ), + ); + } + } + } + + private _getGroupInfo(groupObject: PlantUmlObject): { + id: string; + title?: string; + description?: string; + } { + // presentation - puml group - deployment - deploymentGroup + const pumlGroupTitle = (groupObject.children[0] as PlantUmlGroup).title; + const depTitle = ( + groupObject.children[0].children[0] as PlantUmlDeployment + ).deployment.title; + const depGroup = groupObject.children[0].children[0] + .children[0] as PlantUmlDeploymentGroup; + return { + id: depGroup.children[0].id, + title: `${pumlGroupTitle}-${depTitle}-${ + depGroup.deploymentGroup.title + }-${(depGroup.children[0] as PlantUmlComponentGroup).title}`, + description: `${ + (depGroup.children[0] as PlantUmlComponentGroup).title + }: ${( + depGroup.children[0] as PlantUmlComponentGroup + ).group.components.join(', ')}`, + }; + } + + public async print(resultPath: string): Promise { + const resPath = path.join(resultPath, this.doc.title); + await fs.emptyDir(resPath); + await fs.outputFile( + path.join(resPath, 'context.md'), + this.doc.context.children[0] instanceof PlantUmlPresentation + ? this.doc.context.children[0].presentation.title + : this.doc.title, + ); + await fs.outputFile( + path.join(resPath, 'context.puml'), + `${this.header}${this.doc.context.print()}${this.footer}`, + ); + for (const container of this.doc.componentGroups) { + const { id, title, description } = this._getGroupInfo( + container.data, + ); + const compGroupPath = path.join(resPath, title || id); + await fs.outputFile( + path.join(compGroupPath, 'container.md'), + description || title, + ); + await fs.outputFile( + path.join(compGroupPath, 'container.puml'), + `${this.header}${container.data.print()}${this.footer}`, + ); + for (const component of container.componentsUml) { + const { id, title, description } = + this._getGroupInfo(component); + const compPath = path.join(compGroupPath, title || id); + await fs.outputFile( + path.join(compPath, 'component.md'), + description || title, + ); + await fs.outputFile( + path.join(compPath, 'component.puml'), + `${this.header}${component.print()}${this.footer}`, + ); + } + } + } + + protected get header(): string { + return ` + @startuml + !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4.puml + !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Deployment.puml + !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + !define FONTAWESOME https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/font-awesome-5 + !define FONTAWESOME1 https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/font-awesome + !define DEVICONS https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons2 + !include FONTAWESOME/server.puml + !include FONTAWESOME/envelope.puml + !include FONTAWESOME1/microchip.puml + !include FONTAWESOME1/hdd_o.puml + !include FONTAWESOME1/lock.puml + !include DEVICONS/kubernetes.puml + + AddElementTag("hiddenGroup", $bgColor = "transparent", $borderColor="transparent") + AddElementTag("visibleGroup", $bgColor = "transparent", $fontColor="#3B3A2F") + AddElementTag("organization", $bgColor = "#FAF8C9", $shadowing="true", $shape=RoundedBoxShape()) + AddElementTag("software", $bgColor = "#BAB995") + AddElementTag("infra", $bgColor = "#BAB995") + AddElementTag("fallback", $bgColor="#c0c0c0") + AddElementTag("c1Leaf", $bgColor = "9ACD32", $fontColor="#3B3A2F") + + AddRelTag("fallback", $textColor="#c0c0c0", $lineColor="#438DD5") + AddRelTag("uses-external", $textColor="#ff0000", $lineColor="#ff0000", $lineStyle=BoldLine()) + AddRelTag("uses-internal", $textColor="#3B3A2F", $lineColor="#3B3A2F", $lineStyle=DashedLine()) + AddRelTag("deployed-on", $textColor="#3B3A2F", $lineColor="#3B3A2F", $lineStyle=DashedLine()) + AddRelTag("clustered-on", $textColor="#3B3A2F", $lineColor="#3B3A2F", $lineStyle=DashedLine()) + + AddElementTag("deploymentGroup", $bgColor="#BAB995") + AddElementTag("deployment", $shadowing = true, $bgColor = "transparent") + AddElementTag("server", $shadowing = true, $bgColor="#E0DFB4") + AddElementTag("kubernetesCluster", $shadowing = true) + AddElementTag("hidden", $shadowing = false, $shape = EightSidedShape(), $bgColor="#444444") + + + WithoutPropertyHeader() + + `; + } + + protected get footer(): string { + return ` + SHOW_LEGEND() + @enduml + `; + } +} diff --git a/src/yaan/graph.ts b/src/yaan/graph.ts index 3cd391b..e48e9ee 100644 --- a/src/yaan/graph.ts +++ b/src/yaan/graph.ts @@ -151,6 +151,10 @@ export class GraphNode { return val as T; } + getPath(): string[] { + return this.path; + } + defined(): boolean { return !!objectPath.get(this.baseObject, this.path); }