diff --git a/.github/assets/readme-header.png b/.github/assets/readme-header.png new file mode 100644 index 0000000..739ee1c 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..6c88ec3 Binary files /dev/null and b/.github/assets/social-preview.png differ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 233e852..01fc6e4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,6 +21,14 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Set up Node 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Verify OSS brand sources + run: node scripts/check-brand-assets.mjs + - name: Set up Java 21 (GraalVM CE) uses: graalvm/setup-graalvm@v1 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 771fd37..7453772 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,6 +28,14 @@ jobs: - name: Checkout uses: actions/checkout@v6 + - name: Set up Node 22 + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Verify OSS brand sources + run: node scripts/check-brand-assets.mjs + # The build's Java toolchain pins vendor = GraalVM (build.gradle.kts), so the # runner must provide a GraalVM JDK 21 — a temurin JDK makes Gradle fail with # "Cannot find a Java installation … matching {vendor=GraalVM Community}". diff --git a/README.ko.md b/README.ko.md index dd13c8c..5c22d5a 100644 --- a/README.ko.md +++ b/README.ko.md @@ -1,5 +1,11 @@ # devslab-kit +

+ devslab-kit - DevsLab 오픈소스 +

+ +**[DevsLab](https://devslab.kr/) 오픈소스** · [OSS 브랜드 가이드](https://devslab.kr/brand/open-source/) · 레지스트리 O10 + [![Maven Central](https://img.shields.io/maven-central/v/kr.devslab/devslab-kit-spring-boot-starter?logo=apachemaven)](https://central.sonatype.com/artifact/kr.devslab/devslab-kit-spring-boot-starter) [![Build](https://github.com/devslab-kr/devslab-kit/actions/workflows/build.yml/badge.svg)](https://github.com/devslab-kr/devslab-kit/actions/workflows/build.yml) [![codecov](https://codecov.io/gh/devslab-kr/devslab-kit/branch/main/graph/badge.svg)](https://codecov.io/gh/devslab-kr/devslab-kit) diff --git a/README.md b/README.md index cfa59ef..97f68f7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # devslab-kit +

+ devslab-kit - Open source by DevsLab +

+ +**Open source by [DevsLab](https://devslab.kr/)** · [OSS brand guide](https://devslab.kr/brand/open-source/) · Registry O10 + [![Maven Central](https://img.shields.io/maven-central/v/kr.devslab/devslab-kit-spring-boot-starter?logo=apachemaven)](https://central.sonatype.com/artifact/kr.devslab/devslab-kit-spring-boot-starter) [![Build](https://github.com/devslab-kr/devslab-kit/actions/workflows/build.yml/badge.svg)](https://github.com/devslab-kr/devslab-kit/actions/workflows/build.yml) [![codecov](https://codecov.io/gh/devslab-kr/devslab-kit/branch/main/graph/badge.svg)](https://codecov.io/gh/devslab-kr/devslab-kit) diff --git a/docs/assets/favicon.svg b/docs/assets/favicon.svg new file mode 100644 index 0000000..d725919 --- /dev/null +++ b/docs/assets/favicon.svg @@ -0,0 +1 @@ +devslab-kit \ No newline at end of file diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg index 188ac30..d725919 100644 --- a/docs/assets/logo.svg +++ b/docs/assets/logo.svg @@ -1,3 +1 @@ - - - +devslab-kit \ No newline at end of file diff --git a/docs/assets/social-preview.png b/docs/assets/social-preview.png new file mode 100644 index 0000000..6c88ec3 Binary files /dev/null and b/docs/assets/social-preview.png differ diff --git a/docs/index.ko.md b/docs/index.ko.md index d5e5fdb..0533f6d 100644 --- a/docs/index.ko.md +++ b/docs/index.ko.md @@ -1,5 +1,10 @@ # devslab-kit + + + devslab-kitDevsLab 오픈소스 + + 재사용 가능한 **Spring Boot 4 플랫폼 스타터**. 애플리케이션에 끼워 넣으면 인증, 인가, 멀티테넌시, 동적 메뉴, 감사 로깅이 자동 구성으로 제공되고, 관리자 REST API와 바로 쓰는 관리자 콘솔까지 딸려옵니다. 매번 플랫폼 계층을 다시 만드는 대신 각 제품은 자기 도메인에만 diff --git a/docs/index.md b/docs/index.md index 0f79d98..e1e6c61 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,10 @@ # devslab-kit + + + devslab-kitOpen source by DevsLab + + A reusable **Spring Boot 4 platform starter**. Drop it into an application and get authentication, authorization, multi-tenancy, dynamic menus and audit logging from auto-configuration — plus an admin REST API and a ready-made admin console — so each diff --git a/docs/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 0000000..f945336 --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block site_meta %} + {{ super() }} + + + + + +{% endblock %} diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index feb3dee..80b4e98 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -241,3 +241,72 @@ html[lang="ko"] .md-header [aria-label="Select language"]::before, html[lang="ko"] .md-header [aria-label*="언어"]::before { content: "한"; } + +/* === O10 project identity =============================================== */ +.oss-project-intro { + position: relative; + isolation: isolate; + display: flex; + align-items: center; + gap: 0.75rem; + width: fit-content; + max-width: 100%; + margin: 0.25rem 0 1.5rem; + padding: 0.75rem 1rem; + color: inherit; + border: 1px solid color-mix(in srgb, var(--md-accent-fg-color) 32%, transparent); + border-radius: 0.35rem; + text-decoration: none; +} + +.oss-project-intro[data-atmosphere="project"]::before { + position: absolute; + z-index: -1; + inset: 0; + content: ""; + background: linear-gradient(135deg, rgb(14 116 144 / 0.10), transparent 66%); + pointer-events: none; +} + +[data-md-color-scheme="slate"] .oss-project-intro[data-atmosphere="project"]::before { + background: linear-gradient(135deg, rgb(34 211 238 / 0.10), transparent 66%); +} + +.oss-project-intro img { + flex: 0 0 auto; + width: 2rem; + height: 2rem; +} + +.oss-project-intro span { + display: grid; + min-width: 0; +} + +.oss-project-intro strong, +.oss-project-intro small { + overflow-wrap: anywhere; +} + +.oss-project-intro small { + color: var(--md-default-fg-color--light); + font-size: 0.78rem; +} + +.oss-project-intro:hover, +.oss-project-intro:focus-visible { + border-color: var(--md-accent-fg-color); + text-decoration: none; +} + +@media (max-width: 28rem) { + .oss-project-intro { + align-items: flex-start; + } +} + +@media (forced-colors: active), print { + .oss-project-intro[data-atmosphere="project"]::before { + display: none; + } +} diff --git a/mkdocs.yml b/mkdocs.yml index 356bd42..3e91533 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,9 +14,10 @@ docs_dir: docs theme: name: material + custom_dir: docs/overrides language: en logo: assets/logo.svg - favicon: assets/logo.svg + favicon: assets/favicon.svg features: - navigation.tabs - navigation.sections diff --git a/scripts/check-brand-assets.mjs b/scripts/check-brand-assets.mjs new file mode 100644 index 0000000..78811f9 --- /dev/null +++ b/scripts/check-brand-assets.mjs @@ -0,0 +1,62 @@ +import { createHash } from 'node:crypto'; +import { existsSync, readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +const root = resolve(import.meta.dirname, '..'); +const expectedHashes = new Map([ + ['.github/assets/readme-header.png', '181bb155b383789b0e88454a4ff9d16dafd22d5083140fa83255bb4e306785a2'], + ['.github/assets/social-preview.png', '4666515df130b8d85a5e4ac3fa38c5699af938c9a30eeab98a3ace0d5d301e2c'], + ['docs/assets/logo.svg', '927af7009de24040ac94c0586f3d93dac30c28b08c1d29b2a83c1bf74f1c4248'], + ['docs/assets/favicon.svg', '927af7009de24040ac94c0586f3d93dac30c28b08c1d29b2a83c1bf74f1c4248'], + ['docs/assets/social-preview.png', '4666515df130b8d85a5e4ac3fa38c5699af938c9a30eeab98a3ace0d5d301e2c'], +]); + +const read = (file) => readFileSync(resolve(root, file), 'utf8'); +const assert = (condition, message) => { + if (!condition) throw new Error(message); +}; + +for (const [file, expected] of expectedHashes) { + const absolute = resolve(root, file); + assert(existsSync(absolute), `missing required O10 source asset: ${file}`); + const actual = createHash('sha256').update(readFileSync(absolute)).digest('hex'); + assert(actual === expected, `${file} has ${actual}; expected the oss-brand v0.1.1 hash ${expected}`); +} + +const logo = read('docs/assets/logo.svg'); +assert(logo.includes('data-oss-project="O10"'), 'docs logo must identify the O10 project asset'); +assert(logo.includes('M5 5H13V13H5Z') && logo.includes('M13 10H19V22H13Z'), 'docs logo must use the O10 lattice geometry'); +assert(!logo.includes('M 4 4 L 28 4'), 'docs logo must not retain the former shared backend mark'); +assert(!logo.includes('M7 6H21V20H7Z'), 'docs logo must not use the O08 geometry'); +assert(!logo.includes('M5 8H13V14H5Z'), 'docs logo must not use the O09 geometry'); + +for (const [file, endorsement] of [ + ['README.md', 'Open source by [DevsLab](https://devslab.kr/)'], + ['README.ko.md', '[DevsLab](https://devslab.kr/) 오픈소스'], + ['docs/index.md', 'Open source by DevsLab'], + ['docs/index.ko.md', 'DevsLab 오픈소스'], +]) { + const content = read(file); + assert(content.includes('https://devslab.kr/brand/open-source/'), `${file} must link to the canonical OSS brand guide`); + assert(content.includes(endorsement), `${file} must include its localized DevsLab endorsement`); +} + +const mkdocs = read('mkdocs.yml'); +assert(mkdocs.includes('custom_dir: docs/overrides'), 'mkdocs must load the source-only template override'); +assert(mkdocs.includes('favicon: assets/favicon.svg'), 'mkdocs must use the O10 favicon source'); +assert(mkdocs.includes('logo: assets/logo.svg'), 'mkdocs must use the O10 logo source'); + +const metadata = read('docs/overrides/main.html'); +assert(metadata.includes('https://devslab-kit.devslab.kr/assets/social-preview.png'), 'metadata must use the local O10 social preview'); +assert(metadata.includes('og:image:alt') && metadata.includes('twitter:image:alt'), 'metadata must describe the social preview'); + +const styles = read('docs/stylesheets/extra.css'); +assert(styles.includes('.oss-project-intro') && styles.includes('data-atmosphere="project"'), 'docs need the O10 identity panel and scoped atmosphere'); +const slateAtmosphere = `[data-md-color-scheme="slate"] .oss-project-intro[data-atmosphere="project"]::before { + background: linear-gradient(135deg, rgb(34 211 238 / 0.10), transparent 66%); +}`; +assert(styles.includes(slateAtmosphere), 'Material slate must apply the O10 cyan atmosphere directly at the 0.10 opacity cap'); +assert(styles.includes('pointer-events: none'), 'atmosphere decoration must not intercept input'); +assert(styles.includes('@media (forced-colors: active), print'), 'atmosphere must be suppressed for forced colors and print'); + +console.log(`O10 brand source contract passed (${expectedHashes.size} exact assets).`);