From 26a6955faa0e129e80cd8a72d1df20f58425433b Mon Sep 17 00:00:00 2001 From: Sin-Kang Date: Sun, 30 Aug 2026 21:07:58 +0900 Subject: [PATCH 1/2] feat: apply DevsLab OSS identity to DataLinq --- .github/assets/project-lockup.svg | 1 + .github/assets/project-mark.svg | 1 + .github/assets/readme-header.png | Bin 0 -> 9821 bytes .github/assets/social-preview.png | Bin 0 -> 14404 bytes README.ko.md | 4 +- README.md | 4 +- src/main/resources/branding/logo.txt | 10 ++--- .../java/kr/devslab/datalinq/ui/LogoTest.java | 37 ++++++++++++++++++ 8 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 .github/assets/project-lockup.svg create mode 100644 .github/assets/project-mark.svg create mode 100644 .github/assets/readme-header.png create mode 100644 .github/assets/social-preview.png create mode 100644 src/test/java/kr/devslab/datalinq/ui/LogoTest.java diff --git a/.github/assets/project-lockup.svg b/.github/assets/project-lockup.svg new file mode 100644 index 0000000..0bc54fa --- /dev/null +++ b/.github/assets/project-lockup.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/.github/assets/project-mark.svg b/.github/assets/project-mark.svg new file mode 100644 index 0000000..4920640 --- /dev/null +++ b/.github/assets/project-mark.svg @@ -0,0 +1 @@ +DataLinq \ No newline at end of file diff --git a/.github/assets/readme-header.png b/.github/assets/readme-header.png new file mode 100644 index 0000000000000000000000000000000000000000..da3fbbbff198f7ba13b741c8a6cf5acd1f372dba GIT binary patch literal 9821 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 V(*`ONBsjOP8=4xF{OI`Te*tj@l|ujk literal 0 HcmV?d00001 diff --git a/.github/assets/social-preview.png b/.github/assets/social-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..c09747a84957a08bc1f55e7a35f321327fdd74e9 GIT binary patch literal 14404 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%0ADUbc?aJl`io>-Q$9_4|G z+ygLoFfy)wV?q_5cDLH*+nEvt%3BH#>c@FSpn{(l8LF$(1)TK&28xeHafuEkw4ihUz)oc^;r@zm!t|~*$h*eY>?{G9BWLAdtqK$Nf!VGttA>Zv4T z>;STX@_^ehBC75F6eO8%eSue2jg^P~S=jhJVQbaP_xP!JAe|4iD$s94yZU6Uf6B)P zYk(8vPK|hzGUU(JEwv;R7B9X%pt`VvEGm-eJkrD z%g(NvsVfn}jWF1Wa(eoqeS1U9V!{WI5=CeSLmW@xMAtVZ`2hyqJRu5{9zrJ|{-DP% zcnt*1*9U^)fnY01j&C*9^ZRk2-hF*VrgL(B;Y+h1{>>rXub<%~wajV8NsYTSB@~-8 z`9_(n25_za0|lu8748o03jl_gaG-YI#Ya3xfne}}?N0!0rRL~zC2B9p%1{M#sFlwgOhjo9smV_d44A<0A5O%O%t=B5itBNdjWKQBQWRS62aob}goCg5H6T^1^+?7wh zPNTTz+iTi39M{0e55llWyhMQbEDb1ifhz^%4)9Ezub$29pZYk~f(2NT6ksrD6S>AN z_*m-~I%WF>yazK@`aUr~Ppb>maQu9kTEh2&_*C_T_Cwm^k%tTtXevlYiwK&#r*g(@ zq`)8u$sn*yJL*||0N((G=3I#@(s7DAR)Vxu5uqM2R-f|Ryn7-p&W?;KB5bMY@e4_*5@uTboHKi^mz z#%*E@kEm}4ZUG}US5T7d`Ws2 z#c=0xCs-IK?SXMX+WS*8vP=?$&+HNN$`fO6 zS9>Wax_KcVxMXO)w6(bSyaw0DbnWF<(q-|eUVbg7>-nsoh-B&S>b8d~t&k%C>P>|& z3?(8Cf(6LZpDhUtl-lQqpA-5UuwOor|Ck^6%sb5jFR2ZR`zLJrW2UFE;9r5 z4C{$=Qi!?QL!62W?Mo9>E2NngW5~F0VKB*hu#l30LPQAZQ$uITfWr>B~eBY==wV+F9ctiWeu-Xl=z+ zlas#1Nt}wsvRqcR*dotC-(msi-8WwZ-Ti;^h6_|do?0#1wuMLcs=RRmRkW(qk&H{zx1@2z zz-I7J;MjoBQAM}TZZVv+6%ncO(7x?M^+NSvXCRSw!5i*Paq$PIyF_l50qFc&LJ1$) zgW)|Z5Wi4G%@%s?kq{RyOo|u98&_tDI)5Lp!yJEd&Uk;C#pl-t!D@|)TOlhSNxkBO zzh1n_lRR4P+S+mLL4Fl;Q>%=%AlS)DIPc-lERO_GWq^A()$m=__^*mxvDw93Ry?nG zua~!ej!wOpWIPpZW3;H9L^`TYgj};`k8wnuRvTIchHGi8Lou6WqkZ80*32M*M1`@Nm$8wj1w0{U1t({#q7Y5V;6ozD9AkY* zdVjrwQwEa$#D=u_=DOLO&RDFgedt}Knpl^kDz`enxC#7PJ=mlNF>lI_^n~>cJwquK zlTR%OtU&lN2_d(yo!4*nM{-%UiK91$Ql1>enx)))KCR(5v*O2Ke8!AD-NNspVU8Jo zFmd~7-<=wCxG+m{btTpCRbiZ1cHdY5FPNAK9+r=f`*~?Gfs&Nr*RyA0B0`fAVnopW zI$B6OrsJPnMzwTzEZEzLDNGSPdw+aNlH023S<199K1YEOrJ&rJc1sus=$r1hDpWrz zu48ezuw^2woD&nwkI9<@C`n^uv9aLj*=SnS;%iMOqJwn_kNSwkExRG|z(iafP&7X{ z4Sfz$=~j{k@w^{cj%xT}r;^xnGoWF=Kr&>X^PGvL5~p2Oc=t%C$9nC+gh!Hr;!DdC zPDE2uAHG06iKR{)xh9Yx9Z!sXJs@h6G+HMRDt(G5X;(6i?VGB@hYJhHK|>5TqFo=A zOb7Gi@>*x?!Q>i~7kr;uhBW^w>lUD`?>(QPT!PuKaczjca(x_tRU$L&X91QIXaICe zwf5#))Dsw(0V1CiIK5Zc`c^VsHb&IEgwzY9zPp{_AfVXXK0kfw7eHyHD4oToE{lWm%8v$5@t z8Dg>AV$vxS-ej?SK5fExwOcd4y;bI2)0SnqdWy6PQ6Id>^LBIC*~z;lUhB>qdKBHFJxNT-!2lkD5^1McrIeehmtHP_ptrH=UZ)+k=s|ZR)HUEl;r# zejrWvsNPtDbqX$zE<>Uqt_=zgZQzHbS~pbcd`;HVwKOhv1L-_qjWBu*ncM!BF@Il> z8L6W-Hrr{8RUoHCi<%^{+=hdwo72-Fivs{X{>zAnfcwXn698}Q@GHc!>A6-GuT73o zg5q9GXD4BAXV($c>N|e>4ZLs7OF{&?JlwPm=c*^4r*+K&w?}rtVM^YyHckQvh2+Cm zz%k^wEM?m=={?zzp}Bm!b11+7G%<7bFTSM7!oJ zmNiF4EN=0}iXFj4_j{>ns6oBGVpw*uF*~*7(F>K*lEk?e1G)Fq4#9l4x*xJzf^vM2 z+oeM}zg18iI08f)s$?9u*$}&zja&g>$;OO-w7U=3e0yNz&xoEE{2g2BZ=bdJmmi?` zcb?sNk^J-!MMbQdF%IwhQN1*KjDGqQpc60ng4D+|(lEltSpQdQXd2)4N5|Fy$uJF= z*7(&i{GM_os~j`!g4TTRrYWwCoe9lHY_-kWv}Po@@fo%CzwFh6*UL|bJT=h{X~Zsn z$mw+Blc=wx$g{&rc&PSlVTmk^*%o%&Q*~P&Mp$cx_dQTAbw^2HTR>s_i8 z>-KylcHTck!hC#RAxYk;v`MVS!4|$<+gbUyHf-3%L&H%oLw1ux-!K_9}-EO33m`ey>iEp>< z(f^fi`-|iC)~?X6Y38x-FM+gQ*x&Q)siphajL940D`=b5D?#LK6_{30ny~@QL(!-#rA}kx2_>08O{y$KnT4GbtC)4@&Z=@Uw0&ug>TsJVge_c zIVx6)nx!Gb#Fu*^Pj7kOI3A%|_u(WLF%(Lyn*lZ0YWf#Bu7&oj_tPx*G*9{z-(aZO zUkcUt4ja=KVbsj0YL@0Dqz`{tevYkDY@drJo4BaaVtwI3)1$p%>E~`3Bh1jv!S8Zz?B`&@l&yz%Ox%fhtHVA#f47;+|Q34<4E8oi}bFYEa$<({u3 zM^>PUL~r+8g|ri2*cblk>f?v9G9WivhaInGpMuy{%SA|J=+`(8U}n@>$m;ZSVgnVP zz(r@RLA_aH*cx?-HMZOdq=iX_`T}f-(nHp>myAIn>YtVCulrinkZhxqsrL5gDab=N z7?sQc#PzXr%WJ;dG8}m}mUTe6bD^tGwd%B(e?ys&ZC4*<`F2FOaF$ediStr+F5($k zDV8#Lvb}M083-RdN-wa-E66|Qe*H*1m~pYK$-QdxV_|sr{az~C0Jia1Nas1q(JJ~o zQkyE$UoyGy;Uxmr{Bn}?@mK9*GQZAgRm`|Y3fz&t?i*y96>=uaWVL8!N=O|Jp_^PT z3-PyXoAMwmc6EPL$;t-qWTR6$2gADq-@ct8$B4Rn7b~h@<1YoBHtx+LrKb3OqPJ(= zZr3!psRs?h#QfYf495d&nN*OmZqtXc))UN?6bzg5I;oCG>+XYvzBlxJ(7ndIF>3GJ za&xn&3|)djoE@PIH>19k>OOsbdGQ+UJxNZv7oB2r^e1^_MbRv3ohOIJ@ud)Vur*akJrx2qPd9Qym>Oys zQxeIxg_}0dWy@4TS2cjZ?4h z*MS-xhcE)>G*l?5(MfV#x=+~oG*l*c`TWY%Do_cOPZ+4~tqr+VSbWkU;hxRoq7X{i z*}+2TDIyK1zcE_*XC(I?;j?FK_oX%pRnpb}nvk*rQ zdBHbPhqq+gBVk&hKTaQqWR;p0XD>xPQPvz{Z0QX{BRboPo+C>jJvO%f)pxhiZ<5ta z>qfX0DYFV(j)P%aFIQ}s1lz^W4_dC;@?ap}fkD3Q$&V|B)^TByJJ0B@xE48v%EPK? zt@fm3ZntNU3zDY`p-vMBQ@=G#h?PYvGS}5pt>)CIu^FYFz=^&uNb9?!4>4|vsle7g zducNF(Xy(nNWp0W-vm)-EJ-?g>m9JN+WS&>rdtpfU02j;aTL~}D3L~ckTlv^ZPJ85 zgjJ_M5Ee_Os(*+!`Di@0Js)DRN=BiCCJ`1yrJ|Fw%R+DczMSzK-MZ>NX~{$Kog-KT zXZBa#eF}n0Qvy3*mKlL2j(jZi9za6mp?dTi1CFr1k;IupV72DylRV7kIklM6sFPOh z1T;{UAs**0wljV!w6_J=5jI#5WZlT!e&eX=<94-ua}KK&Vr7hbtQvhM;cfW-Dj@EMm4DBWhig5T;q@Q1u?clz+@zYKYWVYdu9z zjkY;05Zj{exn%OG%Ysf=bd-k@-SYaim}jb6e#R5janjLE(60hTz+q1hn0@4EtX3Fl z>Rw+IVq+iDf!pLa#YaNkgU^#grfrzwOUqHRcS;HZ(!+q7ssGk04`yX6BxenLN~Coi z;P|F}ggnN-zujOu;r?TyRn#}bZp-Q~6-$b;d1uxLq4dD7#%@Z5KV*d?X|Z;)!`Om?uW?$}^;1x}U}(yv9U zO-}ENX40a6OHoWNeX5__XQOSw`nJ=5VB z<1Dm;Xu%sZdtH5{XAkDsEbqPP0FA5?VX8!{hzmt!8>l|nVRCk9JKQX-nEK-n!~#D6 zd{#7``kbU5KR{RuVfOWrUq|wT%uubGH&nx*m$8B7Bh_)uns@jy8~c1ps8$1RXf4Wy zPhrQ&1Tzyi`%JEzOxSz zIm(LPU=UxTy>-papYXjpeNPN4(I_vN+p1eHLGRTP zEjr8Z*z(47L`kL9<IOU&!wvbY>K@Z!@K-DTLm zS-nu^D)9y$e5iNAo7q<8jKJt5M{lBM);BhecUc@>CQoTxH+<9YUTLz9_;tZegk{@Z z6f_KurPK_<3TJ&rx8lVIPrmUx`lYiZ?Oq7`Mu)5Ktq1o} zfMgP-F`A?6xYs=kH|MjK(uW9~lzyD&z|25734AUIO9b^ZaZyd}nMe}E( z=HK`Y6p;DfYSQ})l*!u9^U{|IZlUP9-4 zw(f^lw#(I(cn%%gb8siXpWe}Yw{6(T1-mT=_f8hr75Y2TZ$f`3`WNGI8w0tCgX3Qw b@C{G(3Ato`qL&f->(>lTFBe~QdiZ|;1zVhu literal 0 HcmV?d00001 diff --git a/README.ko.md b/README.ko.md index 1a51299..8c93f5f 100644 --- a/README.ko.md +++ b/README.ko.md @@ -1,3 +1,5 @@ +[![DataLinq](.github/assets/readme-header.png)](https://devslab.kr/brand/open-source/) + # DataLinq [English](README.md) | **한국어** @@ -11,7 +13,7 @@ MariaDB/MySQL, PostgreSQL 드라이버는 **번들**되어 있고, 그 외(Oracl 명령 한 줄로 내려받습니다. 연결은 **DB 종류 + host/port/database**(또는 raw URL)로 설정하며, `application.yml` 에 적거나 DB 연결 화면에서 바로 편집·저장할 수 있습니다. -> **DevsLab Co., Ltd.** (주식회사 데브스랩) 제작 · https://devslab.kr · Apache-2.0 +> [DevsLab 오픈소스](https://devslab.kr/brand/open-source/) · **DevsLab Co., Ltd.** (주식회사 데브스랩) 제작 · https://devslab.kr · Apache-2.0 ## 빠른 시작 diff --git a/README.md b/README.md index 5bc63ed..03f4f26 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![DataLinq](.github/assets/readme-header.png)](https://devslab.kr/brand/open-source/) + # DataLinq **English** | [한국어](README.ko.md) @@ -11,7 +13,7 @@ MariaDB/MySQL and PostgreSQL drivers are **bundled**; others (Oracle, H2, SQLite one-line `driver` download away. Connections are configured by **DB type + host/port/database** (or a raw URL), in `application.yml` or live from the DB Connection screen. -> By **DevsLab Co., Ltd.** (주식회사 데브스랩) · https://devslab.kr · Apache-2.0 +> [Open source by DevsLab](https://devslab.kr/brand/open-source/) · By **DevsLab Co., Ltd.** (주식회사 데브스랩) · https://devslab.kr · Apache-2.0 ## Quick start diff --git a/src/main/resources/branding/logo.txt b/src/main/resources/branding/logo.txt index 9bab72a..fe97f59 100644 --- a/src/main/resources/branding/logo.txt +++ b/src/main/resources/branding/logo.txt @@ -1,6 +1,4 @@ - ____ __ __ _ - / __ \____ _/ /_____ _/ / (_)___ ____ _ - / / / / __ `/ __/ __ `/ / / / __ \/ __ `/ - / /_/ / /_/ / /_/ /_/ / /___/ / / / / /_/ / -/_____/\__,_/\__/\__,_/_____/_/_/ /_/\__, / - /_/ +DataLinq +======== +[ source ] ===> [ target ] +Open source by DevsLab diff --git a/src/test/java/kr/devslab/datalinq/ui/LogoTest.java b/src/test/java/kr/devslab/datalinq/ui/LogoTest.java new file mode 100644 index 0000000..d8886e8 --- /dev/null +++ b/src/test/java/kr/devslab/datalinq/ui/LogoTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2026 DevsLab Co., Ltd. + * SPDX-License-Identifier: Apache-2.0 + */ +package kr.devslab.datalinq.ui; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class LogoTest { + + @Test + void bundledNoColorLogoUsesStableAsciiUtf8Output(@TempDir Path tempDir) throws Exception { + List lines = Logo.load(tempDir.resolve("missing-logo.txt")); + + assertEquals(List.of( + "DataLinq", + "========", + "[ source ] ===> [ target ]", + "Open source by DevsLab"), lines); + assertEquals(List.of(8, 8, 26, 22), lines.stream().map(String::length).toList()); + assertEquals(26, lines.stream().mapToInt(String::length).max().orElseThrow()); + assertTrue(lines.stream().flatMapToInt(String::chars).allMatch(codePoint -> codePoint <= 0x7f)); + assertFalse(lines.stream().anyMatch(line -> line.contains("\u001b["))); + + String bundled = new String(Logo.class.getResourceAsStream("/branding/logo.txt").readAllBytes(), StandardCharsets.UTF_8); + assertEquals(String.join("\n", lines) + "\n", bundled); + } +} From 7e1bd82a8e2c430d14932fdf718ca57e5571c776 Mon Sep 17 00:00:00 2001 From: Sin-Kang Date: Sun, 30 Aug 2026 21:41:58 +0900 Subject: [PATCH 2/2] fix: gate O11 brand assets in CI and release --- .github/workflows/ci.yml | 8 +++ .github/workflows/release.yml | 8 +++ scripts/check-brand-assets.mjs | 55 +++++++++++++++++++ .../java/kr/devslab/datalinq/ui/LogoTest.java | 2 +- 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 scripts/check-brand-assets.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46c41e0..64346c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,14 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Set up Node 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Verify O11 brand sources + run: node scripts/check-brand-assets.mjs + - name: Set up JDK 21 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5bb766e..a5add66 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,6 +13,14 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Set up Node 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Verify O11 brand sources + run: node scripts/check-brand-assets.mjs + - name: Set up JDK 21 uses: actions/setup-java@v4 with: diff --git a/scripts/check-brand-assets.mjs b/scripts/check-brand-assets.mjs new file mode 100644 index 0000000..e55cc5d --- /dev/null +++ b/scripts/check-brand-assets.mjs @@ -0,0 +1,55 @@ +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { readFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; + +const root = resolve(import.meta.dirname, '..'); +const guide = 'https://devslab.kr/brand/open-source/'; +const terminalLogo = 'DataLinq\n========\n[ source ] ===> [ target ]\nOpen source by DevsLab\n'; +const assets = new Map([ + ['.github/assets/project-mark.svg', '6a5ddb30c2f0c6251d60a96f78b88f026e946aa7c5cda31284b29194f8482309'], + ['.github/assets/project-lockup.svg', '123d4746b874a955b6f4c4a9f1373a2b312b01016028703ff3257fcc0e0e2c6d'], + ['.github/assets/readme-header.png', 'cc5b10846a31c4c458ccd784fe158c83616368df0ffb645015561b059343267d'], + ['.github/assets/social-preview.png', '9b83784be89547d512a174853e7e8bbd56ea43975b076530ce930839b7067c3a'], + ['src/main/resources/branding/logo.txt', 'aa4aa8c9bcc2b07257f96854a481496b8229d28bc27057eedbadbce4fa1cc6d7'], +]); + +const file = (relative) => resolve(root, relative); +const text = (relative) => readFile(file(relative), 'utf8'); +const hash = async (relative) => createHash('sha256').update(await readFile(file(relative))).digest('hex'); + +for (const [relative, expected] of assets) { + assert.equal(await hash(relative), expected, `${relative} must match the oss-brand v0.1.1 O11 asset`); +} + +const mark = await text('.github/assets/project-mark.svg'); +assert.match(mark, /data-oss-project="O11"/, 'project mark must identify O11'); +assert.match(mark, /M5 6H11V12H5Z/, 'project mark must retain O11 source column geometry'); +assert.match(mark, /M17 12L21 16L17 20/, 'project mark must retain O11 transfer path geometry'); + +assert.equal(await text('src/main/resources/branding/logo.txt'), terminalLogo, 'bundled terminal logo must be the stable UTF-8 no-color O11 asset'); +assert.ok(!terminalLogo.includes('\u001b'), 'bundled terminal logo must contain no ANSI escape character'); +assert.deepEqual(terminalLogo.trimEnd().split('\n').map((line) => line.length), [8, 8, 26, 22], 'terminal logo line widths must remain stable'); + +for (const [relative, endorsement] of [ + ['README.md', 'Open source by DevsLab'], + ['README.ko.md', 'DevsLab 오픈소스'], +]) { + const readme = await text(relative); + assert.match(readme, /\.github\/assets\/readme-header\.png/, `${relative} must use the vendored O11 README header`); + assert.ok(readme.includes(guide), `${relative} must link to the canonical OSS brand guide`); + assert.ok(readme.includes(endorsement), `${relative} must include its localized DevsLab endorsement`); +} + +const brandCommand = 'run: node scripts/check-brand-assets.mjs'; +for (const [relative, gradleCommand] of [ + ['.github/workflows/ci.yml', 'run: ./gradlew build'], + ['.github/workflows/release.yml', 'run: ./gradlew shadowJar'], +]) { + const workflow = await text(relative); + const brandIndex = workflow.indexOf(brandCommand); + assert.ok(workflow.includes('actions/setup-node@v4') && workflow.includes("node-version: '22'"), `${relative} must provide Node 22 for the brand checker`); + assert.ok(brandIndex >= 0 && brandIndex < workflow.indexOf(gradleCommand), `${relative} must verify O11 brand sources before Gradle`); +} + +console.log(`O11 brand contract passed (${assets.size} exact v0.1.1 assets, terminal, README, CI, and release).`); diff --git a/src/test/java/kr/devslab/datalinq/ui/LogoTest.java b/src/test/java/kr/devslab/datalinq/ui/LogoTest.java index d8886e8..3662678 100644 --- a/src/test/java/kr/devslab/datalinq/ui/LogoTest.java +++ b/src/test/java/kr/devslab/datalinq/ui/LogoTest.java @@ -29,7 +29,7 @@ void bundledNoColorLogoUsesStableAsciiUtf8Output(@TempDir Path tempDir) throws E assertEquals(List.of(8, 8, 26, 22), lines.stream().map(String::length).toList()); assertEquals(26, lines.stream().mapToInt(String::length).max().orElseThrow()); assertTrue(lines.stream().flatMapToInt(String::chars).allMatch(codePoint -> codePoint <= 0x7f)); - assertFalse(lines.stream().anyMatch(line -> line.contains("\u001b["))); + assertFalse(lines.stream().anyMatch(line -> line.indexOf('\u001b') >= 0)); String bundled = new String(Logo.class.getResourceAsStream("/branding/logo.txt").readAllBytes(), StandardCharsets.UTF_8); assertEquals(String.join("\n", lines) + "\n", bundled);