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 0000000..da3fbbb Binary files /dev/null and b/.github/assets/readme-header.png differ diff --git a/.github/assets/social-preview.png b/.github/assets/social-preview.png new file mode 100644 index 0000000..c09747a Binary files /dev/null and b/.github/assets/social-preview.png differ 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/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/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/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..3662678 --- /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.indexOf('\u001b') >= 0)); + + String bundled = new String(Logo.class.getResourceAsStream("/branding/logo.txt").readAllBytes(), StandardCharsets.UTF_8); + assertEquals(String.join("\n", lines) + "\n", bundled); + } +}