From e4c2c72dceab5adb848ee981a5f2a1b9e91163f1 Mon Sep 17 00:00:00 2001 From: Sergei Volchkov Date: Sun, 26 Oct 2025 01:47:44 +0300 Subject: [PATCH 1/2] fix: escape URLs in PlantUML files to prevent rendering artifacts Added URL escaping for C4-PlantUML syntax to fix rendering issue where `://` was interpreted as italic formatting, causing `//` artifacts in generated SVG files. --- resources/architecture/C4L2.puml | 8 ++++---- resources/architecture/Demo Generated.svg | 2 +- resources/architecture/Demo Tests.svg | 2 +- resources/architecture/generated.puml | 8 ++++---- test/architecture.test.ts | 6 +++++- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/resources/architecture/C4L2.puml b/resources/architecture/C4L2.puml index 65a6ad6..1de2a08 100644 --- a/resources/architecture/C4L2.puml +++ b/resources/architecture/C4L2.puml @@ -32,11 +32,11 @@ Boundary(our_system, "Our system"){ Rel(front, bff, "") Rel(bff, camunda, "") Rel(camunda, stock_acl, "") -Rel(stock_acl, stock, "", "https://gateway.int.com:443/stock/v1") +Rel(stock_acl, stock, "", "https:~/~/gateway.int.com:443/stock/v1") Rel(bff, goods_acl, "") -Rel(goods_acl, goods_repository, "", "https://gateway.int.com:443/goods/v1") -Rel(goods_acl, goods2_repository, "", "https://gateway.int.com:443/goods2/v1") +Rel(goods_acl, goods_repository, "", "https:~/~/gateway.int.com:443/goods/v1") +Rel(goods_acl, goods2_repository, "", "https:~/~/gateway.int.com:443/goods2/v1") Rel(bff, task_repository, "") Rel(task_repository, task_repository_db, "") @@ -51,7 +51,7 @@ Rel(camunda, invoice_repository, "") Rel(camunda, invoice_acl, "") Rel(invoice_source, invoice_acl, "", "orig-invoice-q8s-v1", $tags="async") Rel(invoice_acl, invoice_dest, "", "result-invoice-q8s-v1", $tags="async") -Rel(invoice_acl, ext_system, "", "https://gateway.int.com:443/ext/v1") +Rel(invoice_acl, ext_system, "", "https:~/~/gateway.int.com:443/ext/v1") 'Rel(invoice_acl, bff, "", "https://gateway.int.com:443/ext/v1") @enduml diff --git a/resources/architecture/Demo Generated.svg b/resources/architecture/Demo Generated.svg index 068a2b1..80a28ec 100644 --- a/resources/architecture/Demo Generated.svg +++ b/resources/architecture/Demo Generated.svg @@ -1 +1 @@ -Our systembffcamundagoods aclinvoice aclinvoice repositoryDBstock acltask repositoryDBgoods_repository  goods2_repository  ext_system  invoice_input  invoice_output  stock  ...........[https:gateway.int.com:443/goods/v1]</size>//.[https:gateway.int.com:443/goods2/v1]</size>//.[https:gateway.int.com:443/ext/v1]</size>//.[orig-invoice-q8s-v1].[result-invoice-q8s-v1].[https:gateway.int.com:443/stock/v1]</size>//Legend personsystemcontainerexternal personexternal systemexternal container \ No newline at end of file +Our systembffcamundagoods aclinvoice aclinvoice repositoryDBstock acltask repositoryDBgoods_repository  goods2_repository  ext_system  invoice_input  invoice_output  stock  ...........[https://gateway.int.com:443/goods/v1].[https://gateway.int.com:443/goods2/v1].[https://gateway.int.com:443/ext/v1].[orig-invoice-q8s-v1].[result-invoice-q8s-v1].[https://gateway.int.com:443/stock/v1]Legend personsystemcontainerexternal personexternal systemexternal container \ No newline at end of file diff --git a/resources/architecture/Demo Tests.svg b/resources/architecture/Demo Tests.svg index 0e40aca..6960f93 100644 --- a/resources/architecture/Demo Tests.svg +++ b/resources/architecture/Demo Tests.svg @@ -1 +1 @@ -Our systemBFFGoods ACLCamundaTask RepositoryInvoice RepositoryInvoice ACLStock ACLDBDBStockExt SystemInvoice SourceInvoice ConsumerGoods RepositoryGoods2 RepositoryFront....[https:gateway.int.com:443/stock/v1]</size>//..[https:gateway.int.com:443/goods/v1]</size>//.[https:gateway.int.com:443/goods2/v1]</size>//........[orig-invoice-q8s-v1].[result-invoice-q8s-v1].[https:gateway.int.com:443/ext/v1]</size>//Legend personsystemcontainerexternal personexternal systemexternal container \ No newline at end of file +Our systemBFFGoods ACLCamundaTask RepositoryInvoice RepositoryInvoice ACLStock ACLDBDBStockExt SystemInvoice SourceInvoice ConsumerGoods RepositoryGoods2 RepositoryFront....[https://gateway.int.com:443/stock/v1]..[https://gateway.int.com:443/goods/v1].[https://gateway.int.com:443/goods2/v1]........[orig-invoice-q8s-v1].[result-invoice-q8s-v1].[https://gateway.int.com:443/ext/v1]Legend personsystemcontainerexternal personexternal systemexternal container \ No newline at end of file diff --git a/resources/architecture/generated.puml b/resources/architecture/generated.puml index 8f6efb2..ef99a78 100644 --- a/resources/architecture/generated.puml +++ b/resources/architecture/generated.puml @@ -25,15 +25,15 @@ Rel(camunda, task_repository, "") Rel(camunda, stock_acl, "") Rel(camunda, invoice_acl, "") System_Ext(goods_repository, "goods_repository", " ") -Rel(goods_acl, goods_repository, "", "https://gateway.int.com:443/goods/v1") +Rel(goods_acl, goods_repository, "", "https:~//gateway.int.com:443/goods/v1") System_Ext(goods2_repository, "goods2_repository", " ") -Rel(goods_acl, goods2_repository, "", "https://gateway.int.com:443/goods2/v1") +Rel(goods_acl, goods2_repository, "", "https:~//gateway.int.com:443/goods2/v1") System_Ext(ext_system, "ext_system", " ") -Rel(invoice_acl, ext_system, "", "https://gateway.int.com:443/ext/v1") +Rel(invoice_acl, ext_system, "", "https:~//gateway.int.com:443/ext/v1") System_Ext(invoice_input, "invoice_input", " ") Rel(invoice_acl, invoice_input, "", "orig-invoice-q8s-v1", $tags="async") System_Ext(invoice_output, "invoice_output", " ") Rel(invoice_acl, invoice_output, "", "result-invoice-q8s-v1", $tags="async") System_Ext(stock, "stock", " ") -Rel(stock_acl, stock, "", "https://gateway.int.com:443/stock/v1") +Rel(stock_acl, stock, "", "https:~//gateway.int.com:443/stock/v1") @enduml \ No newline at end of file diff --git a/test/architecture.test.ts b/test/architecture.test.ts index 2b8bb41..4fbed4f 100644 --- a/test/architecture.test.ts +++ b/test/architecture.test.ts @@ -154,6 +154,10 @@ describe("Architecture", () => { expect(pass).toBeTruthy(); }); + function escapePlantUmlUrl(url: string): string { + return url.replace(/:\//g, ':~/'); + } + function checkSections( config: DeployConfig, containerFromPuml: Container, @@ -295,7 +299,7 @@ Boundary(project, "Our system"){ data += `System_Ext(${toName}, "${toName}", " ") `; extSystems.push(toName); - transportAttribute = `, "${transport}"`; + transportAttribute = `, "${escapePlantUmlUrl(transport)}"`; } data += `Rel(${fromName}, ${toName}, ""${transportAttribute}`; From c094bd05f5eccfbba9e3c4bacf736fe99e3ada3e Mon Sep 17 00:00:00 2001 From: Sergei Volchkov Date: Mon, 29 Dec 2025 19:48:24 +0300 Subject: [PATCH 2/2] fix: handle escaped URLs in architecture test comparisons --- test/architecture.test.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/architecture.test.ts b/test/architecture.test.ts index 4fbed4f..69aa859 100644 --- a/test/architecture.test.ts +++ b/test/architecture.test.ts @@ -102,7 +102,7 @@ describe("Architecture", () => { if ( items && !items.every((i: string) => - config.sections.some((s) => s.prod_value === i), + config.sections.some((s) => s.prod_value === unescapePlantUmlUrl(i)), ) ) { log = `${log}❌ ${relation.to.name} ${items}`; @@ -142,7 +142,7 @@ describe("Architecture", () => { ?.split(", ") .every( (i: string) => - i.startsWith("https://gateway.int.com:443/") || /-v\d$/.exec(i), + unescapePlantUmlUrl(i).startsWith("https://gateway.int.com:443/") || /-v\d$/.exec(i), ) ) { log = `${log}❌ ${r.to.name}`; @@ -158,6 +158,10 @@ describe("Architecture", () => { return url.replace(/:\//g, ':~/'); } + function unescapePlantUmlUrl(url: string): string { + return url.replace(/:~\/~?\//g, '://'); + } + function checkSections( config: DeployConfig, containerFromPuml: Container, @@ -169,12 +173,13 @@ describe("Architecture", () => { const result = containerFromPuml.relations.some((r) => { let result = false; + const technology = r.technology ? unescapePlantUmlUrl(r.technology) : undefined; if (r.tags?.includes(AsyncTag)) - result = r.technology?.includes(section.prod_value) === true; + result = technology?.includes(section.prod_value) === true; if (!result && (!r.tags || r.tags.includes(RestTag))) result = r.to.name === section.name && - (r.technology?.includes(section.prod_value) || + (technology?.includes(section.prod_value) || r.to.type !== SystemExternalType); return result; }) || @@ -183,7 +188,7 @@ describe("Architecture", () => { (r) => r.to.name === config.name && section.prod_value && - r.technology?.includes(section.prod_value), + (r.technology ? unescapePlantUmlUrl(r.technology) : "").includes(section.prod_value), ), ); if (!result && verbose) @@ -200,13 +205,14 @@ describe("Architecture", () => { ), ), ].every((relation) => { + const technology = relation.technology ? unescapePlantUmlUrl(relation.technology) : undefined; const result = relation.to.name.endsWith("_db") || config.sections.some( (configSection) => configSection.name === relation.to.name || (configSection.prod_value && - relation.technology?.includes(configSection.prod_value)), + technology?.includes(configSection.prod_value)), ); if (!result && verbose)