diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a24f443..44d853f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,11 +5,15 @@ on: branches: [main] paths: - 'docs/**' + - 'tools/publisher/**' + - 'README*.md' - 'mkdocs.yml' - '.github/workflows/docs.yml' pull_request: paths: - 'docs/**' + - 'tools/publisher/**' + - 'README*.md' - 'mkdocs.yml' - '.github/workflows/docs.yml' workflow_dispatch: @@ -32,6 +36,15 @@ jobs: with: fetch-depth: 0 + - name: Set up Node for publisher tooling + uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Check shared publisher attribution + run: | + npm ci --prefix tools/publisher --ignore-scripts + npm run check --prefix tools/publisher + - name: Set up Python uses: actions/setup-python@v5 with: diff --git a/.github/workflows/publisher.yml b/.github/workflows/publisher.yml new file mode 100644 index 0000000..b9fafe1 --- /dev/null +++ b/.github/workflows/publisher.yml @@ -0,0 +1,17 @@ +name: Publisher attribution +on: + pull_request: + push: + branches: [main] +permissions: + contents: read +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: npm ci --prefix tools/publisher --ignore-scripts + - run: npm run check --prefix tools/publisher diff --git a/README.ko.md b/README.ko.md index 5c22d5a..f83b6ae 100644 --- a/README.ko.md +++ b/README.ko.md @@ -4,7 +4,11 @@ devslab-kit - DevsLab 오픈소스

-**[DevsLab](https://devslab.kr/) 오픈소스** · [OSS 브랜드 가이드](https://devslab.kr/brand/open-source/) · 레지스트리 O10 + +Open source by [데브스랩(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) diff --git a/README.md b/README.md index 97f68f7..d0d9994 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,11 @@ devslab-kit - Open source by DevsLab

-**Open source by [DevsLab](https://devslab.kr/)** · [OSS brand guide](https://devslab.kr/brand/open-source/) · Registry O10 + +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) diff --git a/docs/overrides/main.html b/docs/overrides/main.html index f945336..656a26d 100644 --- a/docs/overrides/main.html +++ b/docs/overrides/main.html @@ -8,3 +8,8 @@ {% endblock %} + +{% block footer %} +
{% include "publisher.html" %}
+ {{ super() }} +{% endblock %} diff --git a/docs/overrides/publisher.html b/docs/overrides/publisher.html new file mode 100644 index 0000000..bb988c1 --- /dev/null +++ b/docs/overrides/publisher.html @@ -0,0 +1,5 @@ + +

Open source by 데브스랩(DevsLab) +

+ + diff --git a/scripts/check-brand-assets.mjs b/scripts/check-brand-assets.mjs index b13bfea..fae794e 100644 --- a/scripts/check-brand-assets.mjs +++ b/scripts/check-brand-assets.mjs @@ -32,8 +32,8 @@ 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/) 오픈소스'], + ['README.md', 'Open source by [데브스랩(DevsLab)](https://devslab.kr/)'], + ['README.ko.md', 'Open source by [데브스랩(DevsLab)](https://devslab.kr/)'], ['docs/index.md', 'Open source by DevsLab'], ['docs/index.ko.md', 'DevsLab 오픈소스'], ]) { diff --git a/tools/publisher/.gitignore b/tools/publisher/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/tools/publisher/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/tools/publisher/README.md b/tools/publisher/README.md new file mode 100644 index 0000000..6eadfe6 --- /dev/null +++ b/tools/publisher/README.md @@ -0,0 +1,9 @@ +# Publisher attribution + +Documentation-only tooling; this dependency is not shipped with the library. + +`npm ci --prefix tools/publisher` installs the pinned shared publisher helper. +`npm run sync --prefix tools/publisher` regenerates the marked README and public HTML attribution from `@devslab/site-kit/devslab`. +`npm run check --prefix tools/publisher` detects drift without writing files. + +Edit the product URL or repository in `config.json`; update the shared preset in site-kit for company-wide identity changes. Do not hand-edit generated publisher blocks. diff --git a/tools/publisher/config.json b/tools/publisher/config.json new file mode 100644 index 0000000..a1b1199 --- /dev/null +++ b/tools/publisher/config.json @@ -0,0 +1,7 @@ +{ + "name": "devslab-kit", + "kind": "mkdocs", + "url": "https://devslab-kit.devslab.kr/", + "repository": "https://github.com/devslab-kr/devslab-kit", + "pages": [] +} diff --git a/tools/publisher/package-lock.json b/tools/publisher/package-lock.json new file mode 100644 index 0000000..83941e8 --- /dev/null +++ b/tools/publisher/package-lock.json @@ -0,0 +1,118 @@ +{ + "name": "devslab-kit-publisher-docs", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "devslab-kit-publisher-docs", + "devDependencies": { + "@devslab/site-kit": "0.8.0" + } + }, + "node_modules/@devslab/dds-css": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@devslab/dds-css/-/dds-css-0.8.0.tgz", + "integrity": "sha512-PxibTv92uMzU3rKtOesFUOSR5x3RWp3JHoO9LBidlqwFOjg37+Wd/Y7prLA8mw7USJ1FnF/R5Q4xHDJHk3iCAQ==", + "dev": true, + "license": "SEE LICENSE IN LICENSE" + }, + "node_modules/@devslab/dds-icons": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@devslab/dds-icons/-/dds-icons-0.8.0.tgz", + "integrity": "sha512-kspidNuH1A1E9euuVYlsw5D+vLsPZf01+lGShyB186ru7UEsLkz3epdNH9ry+OPx4lloGYzigyyqq1jwBK8Ofg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE" + }, + "node_modules/@devslab/dds-solid": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@devslab/dds-solid/-/dds-solid-0.8.0.tgz", + "integrity": "sha512-Q05I3QeiaIJga1ixgw+usw3U9Oc4V8tps8mv/Jylu1B0uip4IiAILIM2sePaeJe/ciGfRTxJRsAtprqfyk9I/w==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@devslab/dds-css": "0.8.0", + "@devslab/dds-icons": "0.8.0", + "@devslab/dds-tokens": "0.8.0" + }, + "peerDependencies": { + "solid-js": "1.9.15" + } + }, + "node_modules/@devslab/dds-tokens": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@devslab/dds-tokens/-/dds-tokens-0.8.0.tgz", + "integrity": "sha512-lI/K50/jbKFWjlnTIBce23xry7JbJ1Io461VwUjpLvTAxTrZ5vee9ybosSMSTIs/BDWQidt6V80ghZxsGjX5yg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE" + }, + "node_modules/@devslab/site-kit": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@devslab/site-kit/-/site-kit-0.8.0.tgz", + "integrity": "sha512-m2J6/GH7zid3db+BhoN/2GGUg4tlOsDamXBLFjJg+r8JqDgUv5QyocM5tPgqhW6G/jwKIxS0cZo8RcJJAvU7gQ==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@devslab/dds-solid": "0.8.0" + }, + "peerDependencies": { + "@tanstack/solid-router": "1.170.30", + "@tanstack/solid-start": "1.168.47", + "solid-js": "1.9.15" + }, + "peerDependenciesMeta": { + "@tanstack/solid-router": { + "optional": true + }, + "@tanstack/solid-start": { + "optional": true + } + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/seroval": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.6.tgz", + "integrity": "sha512-rVQVWjjSvlINzaQPZH5JFqsqEsIWdTxY3iJZCnTL/5gQbXIRooVZKI60tVCkOVfzcRPejboxO2t0P89dg5mQaA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/seroval-plugins": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.6.tgz", + "integrity": "sha512-HXuLAX2pu/UByPpaeo/TaMfvMIi+1QqIoPJYCcAtU8QkVNwgR6MPlGuCQTErV1JwraaMbYaWVIBX7mppzGLATQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "seroval": "^1.0" + } + }, + "node_modules/solid-js": { + "version": "1.9.15", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.15.tgz", + "integrity": "sha512-EeiY2xfpZJqPLjXspVEKjAII4yv8NyG//NxZ3IpOFHdUNnnTyL0uJOeS9LWGvA7cFCz5y94cjFwYlmw5Luncsg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.1.0", + "seroval": "~1.5.4", + "seroval-plugins": "~1.5.4" + } + } + } +} diff --git a/tools/publisher/package.json b/tools/publisher/package.json new file mode 100644 index 0000000..78561d6 --- /dev/null +++ b/tools/publisher/package.json @@ -0,0 +1,12 @@ +{ + "name": "devslab-kit-publisher-docs", + "private": true, + "type": "module", + "scripts": { + "sync": "node sync.mjs", + "check": "node sync.mjs --check" + }, + "devDependencies": { + "@devslab/site-kit": "0.8.0" + } +} diff --git a/tools/publisher/sync.mjs b/tools/publisher/sync.mjs new file mode 100644 index 0000000..d7dd219 --- /dev/null +++ b/tools/publisher/sync.mjs @@ -0,0 +1,50 @@ +import { readFile, writeFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; +import { buildPublisher, renderPublisherHtml, serializeJsonLd } from "@devslab/site-kit"; +import { DEVSLAB_PUBLISHER } from "@devslab/site-kit/devslab"; + +const root = new URL("../../", import.meta.url); +const config = JSON.parse(await readFile(new URL("./config.json", import.meta.url), "utf8")); +const check = process.argv.includes("--check"); +const { link, reference } = buildPublisher(DEVSLAB_PUBLISHER); +const start = ""; +const end = ""; +const markdown = `${start}\n${config.attributionLabel ?? "Open source by"} [${link.label}](${link.href}).\n${end}`; +const source = { + "@context": "https://schema.org", + "@type": "SoftwareSourceCode", + "@id": `${config.repository}#software`, + name: config.name, + url: config.url, + codeRepository: config.repository, + publisher: reference, +}; +const html = `${start}\n

Open source by ${renderPublisherHtml(DEVSLAB_PUBLISHER)}

\n\n${end}`; + +async function update(relative, transform) { + const url = new URL(relative, root); + const current = (await readFile(url, "utf8").catch((error) => { + if (error.code === "ENOENT") return ""; + throw error; + })).replaceAll("\r\n", "\n"); + const next = transform(current); + if (current === next) return; + if (check) throw new Error(`Publisher output is stale: ${fileURLToPath(url)}; run npm run sync --prefix tools/publisher`); + await writeFile(url, next); +} +function replaceBlock(text, value) { + const from = text.indexOf(start); + const to = text.indexOf(end); + if (from < 0 || to < from) throw new Error("Missing publisher markers"); + return text.slice(0, from) + value + text.slice(to + end.length); +} +for (const path of config.readmes ?? ["README.md", "README.ko.md"]) { + await update(path, (text) => replaceBlock(text, markdown)); +} +if (config.kind === "mkdocs") { + await update("docs/overrides/publisher.html", () => html + "\n"); +} +for (const path of config.pages) { + await update(path, (text) => replaceBlock(text, html)); +} +console.log(check ? "Publisher output is current." : "Publisher output synchronized.");