From 452d219ac5c4d35b7f5e75d8fd420714e77180f6 Mon Sep 17 00:00:00 2001 From: Ali Hesari Date: Wed, 19 Aug 2026 22:14:36 +0200 Subject: [PATCH 1/2] feat(docs): apply the brand at build time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The docs site now builds per brand. Content under content/** keeps naming the product the normal way; a remark plugin rewrites prose, service hostnames, and MDX component props as the pages compile, and the frontmatter/meta schemas do the same for page titles and sidebar labels, which never reach a remark plugin. Names inside code fences are left alone: most are SDK symbols naming a real published package, and rewriting them would hand the reader code that does not run. Hostnames in code are rewritten, so a copied curl still works. With nothing configured the build is unchanged — verified byte-for-byte against the OwlStack output. --- README.md | 29 +++++++++ app/[[...slug]]/page.tsx | 3 +- app/layout.tsx | 8 ++- lib/brand.ts | 25 ++++++++ lib/layout.shared.tsx | 12 ++-- lib/remark-brand.mjs | 100 ++++++++++++++++++++++++++++++ next.config.mjs | 9 ++- public/brand/fopost/favicon.ico | Bin 0 -> 1357 bytes public/brand/fopost/logo.png | Bin 0 -> 3709 bytes public/brand/owlstack/favicon.ico | Bin 0 -> 4286 bytes public/brand/owlstack/logo.png | Bin 0 -> 25450 bytes source.config.ts | 17 ++++- 12 files changed, 189 insertions(+), 14 deletions(-) create mode 100644 lib/brand.ts create mode 100644 lib/remark-brand.mjs create mode 100644 public/brand/fopost/favicon.ico create mode 100644 public/brand/fopost/logo.png create mode 100644 public/brand/owlstack/favicon.ico create mode 100644 public/brand/owlstack/logo.png diff --git a/README.md b/README.md index 30af046..0ec8d62 100644 --- a/README.md +++ b/README.md @@ -80,3 +80,32 @@ i18n/ # Translation files (fa, zh, ja) ## License This documentation is part of the [OwlStack](https://github.com/owlstacks) project. + +## Branding + +The docs site is built once per brand. Prose under `content/**` keeps naming +the product "OwlStack"; `lib/remark-brand.mjs` applies the deployment's brand +during the build, and `lib/brand.ts` covers everything written in TypeScript. + +| Variable | Effect | +|---|---| +| `NEXT_PUBLIC_BRAND_NAME` | Product name in prose, page titles, and the sidebar | +| `NEXT_PUBLIC_BRAND_SLUG` | Lowercase identifier | +| `NEXT_PUBLIC_BRAND_DOMAIN` | Bare apex domain | +| `NEXT_PUBLIC_BRAND_LOGO` | Nav mark, e.g. `/brand/fopost/logo.png` | +| `NEXT_PUBLIC_BRAND_FAVICON` | Browser tab icon | +| `NEXT_PUBLIC_WEBSITE_URL` / `_APP_URL` / `_API_URL` / `_DOCS_URL` | Service hosts, rewritten in prose and in code samples | + +Unset means OwlStack: the build is byte-for-byte what it was before. + +Two things are deliberately **not** rewritten: + +- **Names inside code fences.** Most are SDK symbols (`OwlStack::Client`, + `from owlstack import OwlStack`) that name a real published package, so + rewriting them would hand the reader code that does not run. Hostnames in + code *are* rewritten, so a copied `curl` still hits the right API. +- **URL slugs** such as `/introduction/what-is-owlstack`. They are routes with + existing inbound links, not branding. + +Add a brand by dropping `logo.png` and `favicon.ico` into +`public/brand//` and pointing the variables at them. diff --git a/app/[[...slug]]/page.tsx b/app/[[...slug]]/page.tsx index fdbd9fb..9ac31ec 100644 --- a/app/[[...slug]]/page.tsx +++ b/app/[[...slug]]/page.tsx @@ -8,6 +8,7 @@ import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; import { createRelativeLink } from 'fumadocs-ui/mdx'; import { getMDXComponents } from '@/mdx-components'; +import { BRAND } from '@/lib/brand'; export default async function Page(props: PageProps<'/[[...slug]]'>) { const params = await props.params; @@ -50,7 +51,7 @@ export async function generateMetadata( description: page.data.description, // page.url is the public path under /docs; root ('/') must not end with a slash alternates: { - canonical: `https://owlstack.app/docs${page.url === '/' ? '' : page.url}`, + canonical: `${BRAND.docsUrl}${page.url === '/' ? '' : page.url}`, }, }; } diff --git a/app/layout.tsx b/app/layout.tsx index 37388c7..1eed5f5 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -6,14 +6,16 @@ import { source } from '@/lib/source'; import { GeistSans } from 'geist/font/sans'; import { GeistMono } from 'geist/font/mono'; import type { Metadata } from 'next'; +import { BRAND } from '@/lib/brand'; export const metadata: Metadata = { - metadataBase: new URL('https://owlstack.app/docs'), + metadataBase: new URL(BRAND.docsUrl), title: { - template: '%s - OwlStack Docs', - default: 'OwlStack Docs', + template: `%s - ${BRAND.name} Docs`, + default: `${BRAND.name} Docs`, }, description: 'Unified Social Media Publishing SDK - Documentation', + icons: { icon: BRAND.favicon }, }; export default function Layout({ children }: LayoutProps<'/'>) { diff --git a/lib/brand.ts b/lib/brand.ts new file mode 100644 index 0000000..25f98b0 --- /dev/null +++ b/lib/brand.ts @@ -0,0 +1,25 @@ +/** + * Brand identity for this docs deployment. + * + * The docs site is built once per brand, so the name, domain, and logo are + * build-time configuration. Prose in `content/**` keeps writing "OwlStack"; + * the remark plugin in `lib/remark-brand.mjs` rewrites it during the build. + * Anything in TypeScript reads from here instead. + * + * Each field is listed explicitly because Next.js inlines + * `process.env.NEXT_PUBLIC_*` by literal text substitution — a computed + * lookup would be undefined in the browser bundle. + */ +const pick = (value: string | undefined, fallback: string) => value?.trim() || fallback; + +export const BRAND = { + name: pick(process.env.NEXT_PUBLIC_BRAND_NAME, 'OwlStack'), + slug: pick(process.env.NEXT_PUBLIC_BRAND_SLUG, 'owlstack'), + domain: pick(process.env.NEXT_PUBLIC_BRAND_DOMAIN, 'owlstack.app'), + logo: pick(process.env.NEXT_PUBLIC_BRAND_LOGO, '/brand/owlstack/logo.png'), + favicon: pick(process.env.NEXT_PUBLIC_BRAND_FAVICON, '/brand/owlstack/favicon.ico'), + websiteUrl: pick(process.env.NEXT_PUBLIC_WEBSITE_URL, 'https://owlstack.app'), + appUrl: pick(process.env.NEXT_PUBLIC_APP_URL, 'https://app.owlstack.app'), + apiUrl: pick(process.env.NEXT_PUBLIC_API_URL, 'https://api.owlstack.app'), + docsUrl: pick(process.env.NEXT_PUBLIC_DOCS_URL, 'https://owlstack.app/docs'), +} as const; diff --git a/lib/layout.shared.tsx b/lib/layout.shared.tsx index d248163..4c4cb48 100644 --- a/lib/layout.shared.tsx +++ b/lib/layout.shared.tsx @@ -1,7 +1,7 @@ import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; import { Globe, LayoutDashboard } from 'lucide-react'; import Image from 'next/image'; -import logo from '@/public/logo.png'; +import { BRAND } from '@/lib/brand'; /** * Shared layout configurations @@ -15,13 +15,13 @@ export function baseOptions(): BaseLayoutProps { title: ( <> OwlStack Logo - OwlStack Docs + {BRAND.name} Docs ), }, @@ -31,7 +31,7 @@ export function baseOptions(): BaseLayoutProps { label: 'Website', text: 'Website', icon: , - url: 'https://owlstack.app', + url: BRAND.websiteUrl, external: true, }, { @@ -39,7 +39,7 @@ export function baseOptions(): BaseLayoutProps { label: 'Dashboard', text: 'Dashboard', icon: , - url: 'https://app.owlstack.app', + url: BRAND.appUrl, external: true, }, ], diff --git a/lib/remark-brand.mjs b/lib/remark-brand.mjs new file mode 100644 index 0000000..4f82c30 --- /dev/null +++ b/lib/remark-brand.mjs @@ -0,0 +1,100 @@ +/** + * Apply the deployment's brand to the docs at build time. + * + * The 119 files under content/ are prose. Threading a JSX expression through + * every sentence that names the product would make them unreadable and would + * break the moment someone writes a new page the normal way — so the content + * keeps saying "OwlStack" and this module applies the brand as the docs are + * compiled. + * + * Two rules, deliberately different: + * + * Prose — the product name is rewritten, and so are service hostnames. + * Longer identifiers are left alone (`@owlstackapp/sdk`, + * `owlstack-core`): the published packages carry that name + * whatever the site is called. + * Code — only the hostnames, so a copied curl command hits the API the + * reader actually has an account on. The name is left alone inside + * a fence on purpose: most occurrences there are SDK symbols + * (`OwlStack::Client`, `use OwlStack\\OwlStack`, + * `from owlstack import OwlStack`) that name a real published + * package. Rewriting them would hand the reader code that does not + * run. The cost is a handful of ASCII diagrams that keep the old + * name; the alternative is broken samples. + * + * A no-op when nothing is configured: the defaults are the OwlStack values. + * + * Frontmatter and meta.json titles never reach a remark plugin — fumadocs + * parses them separately — so source.config.ts runs `brandText` over them + * through the schemas. That is where the sidebar and page titles come from. + */ + +const DEFAULTS = { + name: 'OwlStack', + websiteHost: 'owlstack.app', + appHost: 'app.owlstack.app', + apiHost: 'api.owlstack.app', + docsHost: 'owlstack.app/docs', +}; + +const pick = (value, fallback) => (value && value.trim()) || fallback; +const hostOf = (url, fallback) => + pick(url, '').replace(/^https?:\/\//, '').replace(/\/$/, '') || fallback; + +/** Standalone brand words only — never part of a longer identifier. */ +const NAME_RE = /(? from !== to); + return { name, hosts, changed: name !== DEFAULTS.name || hosts.length > 0 }; +} + +/** Service hostnames only. Safe inside a code sample. */ +export function brandCode(value) { + const { hosts } = config(); + return hosts.reduce((acc, [from, to]) => acc.split(from).join(to), value); +} + +/** Hostnames and the product name. For prose, titles, and descriptions. */ +export function brandText(value) { + const { name } = config(); + return brandCode(value).replace(NAME_RE, name); +} + +export default function remarkBrand() { + const { changed } = config(); + return (tree) => { + if (!changed) return; + visit(tree, (node) => { + if (node.type === 'text' || node.type === 'html') { + node.value = brandText(node.value); + } else if (node.type === 'code' || node.type === 'inlineCode') { + node.value = brandCode(node.value); + } else if (node.type === 'link' || node.type === 'definition') { + node.url = brandCode(node.url); + } + // MDX component props — — are + // attributes, not children, so the walk above never sees them. + for (const attr of node.attributes ?? []) { + if (attr.type === 'mdxJsxAttribute' && typeof attr.value === 'string') { + attr.value = brandText(attr.value); + } + } + }); + }; +} + +/** Minimal depth-first walk — avoids a unist-util-visit dependency. */ +function visit(node, fn) { + fn(node); + for (const child of node.children ?? []) visit(child, fn); +} diff --git a/next.config.mjs b/next.config.mjs index affc090..8649331 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -2,6 +2,11 @@ import { createMDX } from 'fumadocs-mdx/next'; const withMDX = createMDX(); +// The marketing site these redirects point at is per-brand. The /what-is-* +// and /why-* rewrites above are legacy content slugs, not branding — they +// name a page that exists, so they stay as written. +const WEBSITE_URL = process.env.NEXT_PUBLIC_WEBSITE_URL?.trim() || 'https://owlstack.app'; + /** @type {import('next').NextConfig} */ const config = { reactStrictMode: true, @@ -29,8 +34,8 @@ const config = { { source: '/guide/plans', destination: '/guide/plans/overview', permanent: true }, { source: '/guide/ai', destination: '/guide', permanent: true }, { source: '/guide/pro', destination: '/guide', permanent: true }, - { source: '/support', destination: 'https://owlstack.app/contact', permanent: true }, - { source: '/changelog', destination: 'https://owlstack.app/roadmap', permanent: true }, + { source: '/support', destination: `${WEBSITE_URL}/contact`, permanent: true }, + { source: '/changelog', destination: `${WEBSITE_URL}/roadmap`, permanent: true }, ]; }, }; diff --git a/public/brand/fopost/favicon.ico b/public/brand/fopost/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3d0237491501f62904fdc9c9281037b53bdc3269 GIT binary patch literal 1357 zcmV-T1+w}80096301yxW0000W00sg8044wc03aX$0000W03iba04xFk0EtjeM-2)Z z3IG5A4M|8uQUCw|5C8xG5C{eU001BJ|6u?C0nJH7K~#90eN(+|6G0IEW@c?)Vmks8 zM3ah#K;j9~r;q#uR5U0^L?DqDprVW-rJzWkf`Xc#2M9lk0D%w$ z1~xFnr;OkuBeunk=@_HC&vo4nJLi8 z2<B#w8MkgPCW6}w18)yJX!G6?UcdZ{eqYdEB6NFHC#MSMhE#jL zbN4EaM-kJh;pvkRCKH3mteF=G)P+XBB94AU6M03y2UVzYQ;* z|3S9EX+0nNl$)|LAfK7rF!S?QrSP}?B`2TXbA?fLPT~s5hW{8M9j?QD-WND0Y~qk? z@ZSFQ9cVFR&fWpsNSSM!C9D=h=l%m2w#%fn*OO}i0000!o$`w_l`fOI7gLwsUkyzDu2R96bqzQdB26h3K&zaLyA?)zH=eAWd?aiiU8%Tn zB`~9pt*gV@rX?Z;(4dhY5R07FS*_I&!l*i1UL9YiQt{(BLA@kp3f8t95$SZnzu2%^ zXB4~W8^fJdj&aMF{AgsGhYlABLs-HTwo-&?Ya6V5+W(D&)n;PaFk`|Yq``|liHIDB z5VlE@b}38smpm~8&cz*%v`GSwBA{M(K39D3dFP&1grVX1*)(zC%_ig2=>Rjcc^{>~ zSR(DPY#_ZZCO zDE2Do+kiwjF&W_8`5Z>_3Rcn&sT0QQ^_G7g_fKN_H83?Z;*Va{Y7W=tHxWhbGnCpY z_?;RJMyts_(r&~%Uo4W!^gI8H3*S**-oufC@+(6mMp~`-(f4gYRIf9tRp%?eYe1A_ z2@YgHqBN#`^cGy)$on$DydSOq`1Bn8$|QkHwA~bEI$yv+W#Ynv0NCELc)nWm_bPA0 zR7_B*_=%JLjz!rlrDDJ&oq|H4y(5v&Z7Ng1%(4yFNsBNI30o22gQ8%-T277@re{0G ziq<)D?DseSM8aw_u_D{@+flSvH9D&#C5H_chOmSwY*k)8vB9v{wHhL=Gn(0T_B@Ax zW+z>dv}PnsVJm46rAKSFcIF9sD-#s7Qkm471Q}Dt7KX5d`Lj9wpY8b%fPj(zZcU}O P00000NkvXXu0mjftbt;H literal 0 HcmV?d00001 diff --git a/public/brand/fopost/logo.png b/public/brand/fopost/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..24d5a7d0360628b0b29bb9599653fbb298835138 GIT binary patch literal 3709 zcmaJ^X*?8c*B7!h5h`n82BQcWZDvB4v1Q-MR<`Wfx0?}?WiYmsHM?Q#S(*%~Gvw+V!&$F${{I9+eNB#2f~Oc57&x@G;Ks*q>EDNq z<@gq8exl02zzNretD1)7(eu}R^2V|V!>@NkKQ7ugoD~I>6Nzk9VP;vf5e>$C0z41i zrHV_NtM7}{OTVfzr?o_U3aqiMalqhc0K4xZSbiDnTtj0?prxBcDQ{nuwFRJ$M@!yK zEB+#ECuI5cE_O&kAtB>d?-zk#oBh2=yJQNb`QU{=GCy3nJW#dGP*mZT76jVY5$QAE za5f_A;p({9(2TvlOMy#DSnPDr`;i#XdEf)P!ks7~v3D1mvdIzPa1HSn!-xacvzL+l zk`$!)Reh_E(bM48K0|gc#lGL8Mx8NAgnI9%nh=<1Xxn2yPyOOshx!J>NWhtdgP+b z1gbf{sQjKtJw3JFZM2)8@J4G$rYv3U-JqmoT7C0HaN~@rwrs)9LZq^??>N8$HUa@^ zYMoC09jl?Kd0@Z37w`pcTN1a}84#)Zh8!Dz1vKxX88E$q>P37GNXzbnDO>UH)x8fk zK%r3lf(Pu$qf6{`9Q$j}_P4BY74|4k%I=S#1zEKZr;`LG)&g#1wsjY#=$-y;=u_wH z`C1CNDpG&cpmLhO>4!>jC1qX*Vj|>NZ~J4N@dgKIAs~cT^B_-ri-1=-n%NMu&$X+` zLWg5u zuwdxZINhqw`3@z?U_=Xr1Jnm=U3tz?3~FQHUdPf+Z|WrEB56O~!)D!0`*LfKl(vaJ zi_%W#3WIBz1-b%9?v$leO4Crd5>CwB0BJS%qocJqb5*9!-;}h-n?rK<+u6Jmhc1)O z2Wre1Jtav5y6dT#k7s2VAp+h&uy1MHAs!bU=wVEjnhs_riwI;hz zpI2_F)KFKd^&Geax@6#ehzePZ3GErwd`h7++C%)4XMS(5X2wCdawsrmzazhGkGaY1 zQFWy{Egf3sjd7$+ULU^aJ0Jd&RMJ3?koERO%p)zLuM!KuYK=W5EZkOBgq8n+L};E_^Oz&yTiLzM`rON zJIgkQvDf#e+B4;Ym{(Ep^A{BDp%yJ=`A<1#RI>F$g)s<1-fGzGlzpGzNcM`OI{*js zagt)7!9`QP!5w;`MYvBnrTWw6vWOS6gYzhpbJ)OA=z*%OuB42C$_^F(Y>&x>#=E5t@x7Y_}Z_?C%~GW%Gs~5+;Yj+vTVuX5vOsyr9|V?O^-7;-Nj2)DXA+n2#TW z_h{b)Aj1`=x=GIlIMHPp%zXc`uNd1YHuh*nwdj{oXwgfe;(v$`D=BBO0>N}t-pRo{bj~m8&6Vu< zn_v0dnw|7}Y&7Y{g;ckIQzDp_Pq1R~iQo4jCTd{@+l$Cqjome(nkHPR2wWcw58KGv zjWj9dJ6gIO80CxEJkceJ?9aq%!CmR%-1)Z+7e<{DE;yg!;4Vm+C` zjb1oYd`(2{8qwkY?N!oF8fE$R0gc|>!Uk+k%$HQDGQ8k(h!uhN2%+s5qY{Sle{47L z4Q2S#@4ZZUP+nU*C?WxLikT^2L$;b|VZ4I9gG8NTAeAWkIf%5w1S|8&KYjQI@o~+2 zv+}}b_f_KlVBtg)4_9NC_{0l=xl#PLT3L0YOr^^}a`M-HGqJ;E7+waiL4|eNmC6n6 z4QR6ieeNg;dQZ8m_#AblTK5t}%vQEt@17&%s<8SgazW~C;24RM9@NY$~)k*nc-Yti{@!S5MWyCl4)n zaZu`MY$f#*6e3y5*!Aw@MehGQWB>6#CIZ0435v7II6(I@P9~#q58K7p?`4c1%%E(| z&csF^fBCAUI7@v60`-9I&EIn+9opkJi6%1IFw-^9$c;qbzi~F(r1Hc=)^u=HRT8wn zV6!9hYK1u?(T}7QXlQ$F)OS9D@=RHwyd)@$Im#SlH)Fy&hEPyGY*qwE4(D*@(_hZ9 z$RP4fc1fy%^7L}6mFxlYVQFyAv4~ZczfmQrSol15 zx98iiMbh;^{eQ>MajnIvm`Po7zf$bzvHWrnMGEJTaEU!9B|g4cK6HI})R_9Xq4#3b z7*yxV*o=V7<#RnKz1f>$#-YbB%#7QJ2MajfX63aQ|Gzy>wVjl`!aI?SiWgA%YyIxh z<~O8f3n7uMlvxjvZ`?|f57ax(5iQle0jYG|OxloeDB^w(RT;~a6F>B_I$ZubK^E4U zj&^r15zE zW09^%@D#ihefn)*dWeM7*g79pBR9TVD2rSi)BKt9t#Ornr?D_cYID_x{ySr9 z$u&M?B!{!ar#)^b3fun>ih`E@z0p7GK%{QkxMFuV_IL88&ri(8z_lyezNfUB3*MBalmq6@C~Z&uoC)qU!{|(Gu_O$`?1Qk1>I~Bkp@f_~i5q1f z235)+pKXhUh(!GXA{w5Nk!O>herbIg1crXao#pp{U3aieZ&>4+iH#c?SrMWN(S%?s zyW$y&LIiyC6Myn{G&Eg9BT)UKk%hTf$>{ek*@*Wv!KDa7I@w_Ekv26%cCEF!MjLD9 zasdMsrN!ck{)SGvB@n4<{Q}DymzJ2`f0EN^+1d|-`SSfxml^#G3DpDeO?cyrpNe~L zR5+exQXx&<%zcafP?g$I9zIKjF0rK^1ID)!fbb_P`L}4Ps9v||I$>A1N(Yr&X`*oD z$mVF57KMAMUnxZEad$TLzKqv38A|GQ{d8vLzmYFBi}8t7nWo^>@*Q_go7fuq)iip! zj^D;BzTK^jgg16=lL^g_`u9@CSk4*m1+5q0yzYAM`;!m0J}$Fv`(_Vhpbg+j_f&^C zq=Bo{JCL;Gu+eb5v|fh1oc*Q3(501;E`KSvBExH~ppwy7%+pJYPngsx`(D{odZ1xN zZQ&tAMCic-#a6A^>{TWQ+nQ{5F2op$i%aGl5Z9Uq_FkRf{SP-tmiTXOfa>s;aqM0l zA)_80JR_cWeCk?|ci0%b9m}TFr8_6SAm`=>9?RQKNAH#`-#m%s0Xl9v-OA4`GH4_8;k9ZG(f3p8uvPxK2u$fwd z8^P6a-PZNVvj0*7e#IZh9niF8jiXiNQC36?bsLhG@M*#ca>Y(v!Yask1 z8>Ie{pVxq&u?}=C{WnHI`&J+fkW@)_uvyHw0MqXMSOW>Z3K@Y~qi`NPgDc%~9y@^UNj$|M*GNm1m)^ zy%Iew`v?{2C3H5Fpp@3RaCfkPIVp(r2tsNAuZCK^mh{T za4!Exep-7G|7vWq4Z;q=uf)%}xf6Kr`ZZ)G=%M#?LUC3MW~cgb^V%Zbesda~HQ8vg z7zGYm3^=%#P?`W!qMxEaa}zkH6(4C0SOX;%Z1`2**%sy(HaKAU^eK^cAAfWQrj#fY zXG9_^J_rq!dFXB`Ls3d7artW~Fa{ulv=F%`{Z_f{_!S$zk$>rRg`bUh>*5m1^OJG< zU9 z?F(<0P1YDt{kN{SEx($V(tlxtmH+d5cd>tO0WL0{7QXYJw=chezLs*Fo;r+$iEi-P zYoImAPY#DS)lV6RGR`;hTklnEm|NvPJAV?t`^`tNm{JfMvID*C4Pqa>^TDrhpe!HR zi9r}XT!UImDy{j8s4h;zD{cFP7P*EHjbEH8WZhA9korr0Zl#UN{_BVwV+7)Mdcc$% zgUQiBEH0eFP;Up!sc}e&3WP!LfuvoYs3=H8Q)LdS_v}VmtRD*L3=$LMrksh$7dw8r z&)CGDEjQbs>_4;zxHzJ_xeoX4{uS@vdPZ=e0K1r7}n+#^V`Ikw*PI&f3|^ZH^t7( z)T_90^)lkqOfdIeKyrB}a=VwH4-H0UTqulscZ7umAp6h~jFxufb}b<$DFc1Yr4$os z8~E*cXie1p*S=ra%lkjoEw1^~V?#)-?1RBn39qf+L5QCR4pi*Luit-<{A@wCQI8-$ zcX-mXxZIbJRMCTaOA0#b3Y8jYtG~QfVuEVF$U$v?)YgLdId|qx9LJSQ=aHCGij=Z8 zgn2q+^hg)g-v_iGe2K-GL7X_&jD4nf+Vh@*v8)qu>1K2_7NNc@UHC8Ol&#!<=$_A> ztbrCEe69FTeRRIJn3D0y?>|PSAriY^jKJkL&f}k7e~mwW@&|DJIY;$pw67M^RBzg9 z&4>!~Mq-2)jvx66k|VwC^p`vv`T1V*iZP^(QiB~4wEYQ@do>m_etYLbtgo-(r%PvX z>*fvogZy8f8$y3;Ioj*YVlN!-szQz-Sma~?8T(D*VDvY*qlGD8i?9Q>%TiP0GBQ-Vom+_xN_+tA_Ja9T&O#8lOm9r7>0!K z9f%Lzju(7>MElKF96negY?vKuM{7+UqWDb0xg^(>TN{3|$8P;at)}te^xf0n(MVb^ z;NrP?oSGORUQgi)@KGRqMp@SL= zZsOZ(m2*IV^MPuDFZEj?@3~c;D`V)M#xuxv5l8ghr1tUq%6Qv%RISyTL!53~R^|Tz I{$K6C0CUBm1poj5 literal 0 HcmV?d00001 diff --git a/public/brand/owlstack/logo.png b/public/brand/owlstack/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2c720d24cc0b398b4e9850be518f61624411ec45 GIT binary patch literal 25450 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%0AY*|2C_p0-egIM_F( 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 M07*qoM6N<$f>HRmW&i*H literal 0 HcmV?d00001 diff --git a/source.config.ts b/source.config.ts index af55e96..3cb7ab3 100644 --- a/source.config.ts +++ b/source.config.ts @@ -5,20 +5,33 @@ import { metaSchema, } from 'fumadocs-mdx/config'; import { z } from 'zod'; +import remarkBrand, { brandText } from './lib/remark-brand.mjs'; + +/* Frontmatter and meta.json never reach a remark plugin — fumadocs parses + them before MDX compiles — so the brand is applied here instead. These are + the page titles, the descriptions in , and the sidebar labels. */ +const branded = z.string().transform(brandText); export const docs = defineDocs({ docs: { schema: frontmatterSchema.extend({ order: z.number().optional(), + title: branded, + description: branded.optional(), }), }, meta: { - schema: metaSchema, + schema: metaSchema.extend({ + title: branded.optional(), + pages: z.array(z.string()).optional(), + }), }, }); export default defineConfig({ mdxOptions: { - // MDX options + // Applies the deployment's brand to prose and service URLs as the docs + // compile, so content/** can keep naming the product the normal way. + remarkPlugins: [remarkBrand], }, }); From 5e0863a3e2f36f1c456429a14981dd37d757f312 Mon Sep 17 00:00:00 2001 From: Ali Hesari Date: Wed, 19 Aug 2026 22:15:06 +0200 Subject: [PATCH 2/2] build(docs): accept brand and URL build args MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The remark plugin bakes branding into the compiled pages, so these have to reach the builder stage — passing them at runtime would do nothing. --- Dockerfile | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Dockerfile b/Dockerfile index b6b80a8..288340f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,29 @@ WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NEXT_TELEMETRY_DISABLED=1 + +# Branding and service URLs are resolved at build time — the remark plugin +# bakes them into the compiled pages, so these must arrive as build args, not +# runtime env. Unset means OwlStack. Changing one needs a rebuild. +ARG NEXT_PUBLIC_BRAND_NAME +ARG NEXT_PUBLIC_BRAND_SLUG +ARG NEXT_PUBLIC_BRAND_DOMAIN +ARG NEXT_PUBLIC_BRAND_LOGO +ARG NEXT_PUBLIC_BRAND_FAVICON +ARG NEXT_PUBLIC_WEBSITE_URL +ARG NEXT_PUBLIC_APP_URL +ARG NEXT_PUBLIC_API_URL +ARG NEXT_PUBLIC_DOCS_URL +ENV NEXT_PUBLIC_BRAND_NAME=$NEXT_PUBLIC_BRAND_NAME \ + NEXT_PUBLIC_BRAND_SLUG=$NEXT_PUBLIC_BRAND_SLUG \ + NEXT_PUBLIC_BRAND_DOMAIN=$NEXT_PUBLIC_BRAND_DOMAIN \ + NEXT_PUBLIC_BRAND_LOGO=$NEXT_PUBLIC_BRAND_LOGO \ + NEXT_PUBLIC_BRAND_FAVICON=$NEXT_PUBLIC_BRAND_FAVICON \ + NEXT_PUBLIC_WEBSITE_URL=$NEXT_PUBLIC_WEBSITE_URL \ + NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL \ + NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL \ + NEXT_PUBLIC_DOCS_URL=$NEXT_PUBLIC_DOCS_URL + RUN npm run build # Stage 3: Production runner