From 8ff44dba2e2b1abe1fb0deb29723fc339d55488c Mon Sep 17 00:00:00 2001
From: Sin-Kang
Date: Sun, 6 Sep 2026 18:08:14 +0900
Subject: [PATCH] docs: share bilingual DevsLab publisher attribution
---
.github/workflows/publisher.yml | 17 +++++
profile/README.ko.md | 4 +
profile/README.md | 4 +
tools/publisher/.gitignore | 1 +
tools/publisher/README.md | 9 +++
tools/publisher/config.json | 12 +++
tools/publisher/package-lock.json | 118 ++++++++++++++++++++++++++++++
tools/publisher/package.json | 12 +++
tools/publisher/sync.mjs | 50 +++++++++++++
9 files changed, 227 insertions(+)
create mode 100644 .github/workflows/publisher.yml
create mode 100644 tools/publisher/.gitignore
create mode 100644 tools/publisher/README.md
create mode 100644 tools/publisher/config.json
create mode 100644 tools/publisher/package-lock.json
create mode 100644 tools/publisher/package.json
create mode 100644 tools/publisher/sync.mjs
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/profile/README.ko.md b/profile/README.ko.md
index 314e94b..ee60560 100644
--- a/profile/README.ko.md
+++ b/profile/README.ko.md
@@ -8,6 +8,10 @@
🇺🇸 English
+
+Published by [데브스랩(DevsLab)](https://devslab.kr/).
+
+
---
```bash
diff --git a/profile/README.md b/profile/README.md
index 34dc9b7..7bdffbf 100644
--- a/profile/README.md
+++ b/profile/README.md
@@ -8,6 +8,10 @@
🇰🇷 한국어
+
+Published by [데브스랩(DevsLab)](https://devslab.kr/).
+
+
---
```bash
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..ec8d50e
--- /dev/null
+++ b/tools/publisher/config.json
@@ -0,0 +1,12 @@
+{
+ "name": ".github",
+ "kind": "readme",
+ "url": "https://github.com/devslab-kr/.github",
+ "repository": "https://github.com/devslab-kr/.github",
+ "pages": [],
+ "readmes": [
+ "profile/README.md",
+ "profile/README.ko.md"
+ ],
+ "attributionLabel": "Published by"
+}
diff --git a/tools/publisher/package-lock.json b/tools/publisher/package-lock.json
new file mode 100644
index 0000000..90ce9c7
--- /dev/null
+++ b/tools/publisher/package-lock.json
@@ -0,0 +1,118 @@
+{
+ "name": "org-profile-publisher-docs",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "org-profile-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..c14630a
--- /dev/null
+++ b/tools/publisher/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "org-profile-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}\nOpen 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.");