From fcea8f221b454819096299e9019aab1c37741734 Mon Sep 17 00:00:00 2001
From: Firman Ramdhani <33869609+firmanramdhani@users.noreply.github.com>
Date: Wed, 29 Jul 2026 12:27:27 +0700
Subject: [PATCH] feat: implement @repo/brand package for centralized asset
management and automated distribution to consuming apps
---
.gitignore | 3 ++
apps/landing/index.html | 7 +++-
apps/landing/package.json | 6 ++-
apps/web/index.html | 2 +-
apps/web/package.json | 6 ++-
apps/web/public/typescript.svg | 1 -
apps/web/public/vite.svg | 1 -
packages/brand/README.md | 32 ++++++++++++++++
packages/brand/package.json | 17 +++++++++
packages/brand/public/favicon.svg | 6 +++
packages/brand/scripts/copy-brand-assets.js | 31 +++++++++++++++
packages/brand/src/data/info.json | 10 +++++
packages/brand/src/index.ts | 3 ++
packages/brand/tsconfig.json | 5 +++
pnpm-lock.yaml | 42 ++++++++++++++++++++-
15 files changed, 162 insertions(+), 10 deletions(-)
delete mode 100644 apps/web/public/typescript.svg
delete mode 100644 apps/web/public/vite.svg
create mode 100644 packages/brand/README.md
create mode 100644 packages/brand/package.json
create mode 100644 packages/brand/public/favicon.svg
create mode 100644 packages/brand/scripts/copy-brand-assets.js
create mode 100644 packages/brand/src/data/info.json
create mode 100644 packages/brand/src/index.ts
create mode 100644 packages/brand/tsconfig.json
diff --git a/.gitignore b/.gitignore
index e4ba428..5e0724f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,9 @@ dist-ssr
server/dist
public/dist
+# Auto-generated brand assets (copied from @repo/brand)
+**/public/brand/
+
# Storybook Build Outputs
*storybook.log
storybook-static
diff --git a/apps/landing/index.html b/apps/landing/index.html
index ceabddf..96cdf05 100644
--- a/apps/landing/index.html
+++ b/apps/landing/index.html
@@ -2,10 +2,13 @@
-
+
Eigen — Company Profile
-
+
diff --git a/apps/landing/package.json b/apps/landing/package.json
index 2ca4eef..af45cde 100644
--- a/apps/landing/package.json
+++ b/apps/landing/package.json
@@ -4,13 +4,15 @@
"version": "0.0.0",
"type": "module",
"scripts": {
- "dev": "vite --clearScreen false",
- "build": "tsc && vite build",
+ "copy:brand": "node ../../packages/brand/scripts/copy-brand-assets.js",
+ "dev": "pnpm run copy:brand && vite --clearScreen false",
+ "build": "pnpm run copy:brand && tsc && vite build",
"preview": "vite preview",
"lint": "eslint \"src/**/*.ts\"",
"typecheck": "tsc --noEmit"
},
"dependencies": {
+ "@repo/brand": "workspace:*",
"@repo/core-api": "workspace:*",
"@repo/core-i18n": "workspace:*",
"@repo/core-storage": "workspace:*",
diff --git a/apps/web/index.html b/apps/web/index.html
index eed3f7d..b209b4f 100644
--- a/apps/web/index.html
+++ b/apps/web/index.html
@@ -2,7 +2,7 @@
-
+
Applications
diff --git a/apps/web/package.json b/apps/web/package.json
index fc8add5..ef5ab0c 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -4,8 +4,9 @@
"version": "0.0.0",
"type": "module",
"scripts": {
- "dev": "vite --clearScreen false",
- "build": "tsc && vite build",
+ "copy:brand": "node ../../packages/brand/scripts/copy-brand-assets.js",
+ "dev": "pnpm run copy:brand && vite --clearScreen false",
+ "build": "pnpm run copy:brand && tsc && vite build",
"preview": "vite preview",
"lint": "eslint \"src/**/*.ts\"",
"test": "vitest run",
@@ -14,6 +15,7 @@
},
"dependencies": {
"@hookform/resolvers": "^5.0.1",
+ "@repo/brand": "workspace:*",
"@repo/core-api": "workspace:*",
"@repo/core-events": "workspace:*",
"@repo/core-i18n": "workspace:*",
diff --git a/apps/web/public/typescript.svg b/apps/web/public/typescript.svg
deleted file mode 100644
index d91c910..0000000
--- a/apps/web/public/typescript.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/apps/web/public/vite.svg b/apps/web/public/vite.svg
deleted file mode 100644
index e7b8dfb..0000000
--- a/apps/web/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/brand/README.md b/packages/brand/README.md
new file mode 100644
index 0000000..b4345c6
--- /dev/null
+++ b/packages/brand/README.md
@@ -0,0 +1,32 @@
+# @repo/brand
+
+This package serves as the single source of truth for our brand identity, containing static visual assets (logos, favicons) and brand information.
+
+## 📁 Public Assets
+
+Place your raw, static brand assets inside the `public/` directory:
+
+- **`logo.png`** — Primary brand logo
+- **`favicon.svg`** — Browser favicon
+
+### How Assets are Distributed
+
+Due to monorepo constraints, frameworks like Vite or Next.js do not natively serve static assets from external packages. To solve this, these files are automatically copied into the consuming apps' `public/brand/` directories via the `scripts/copy-brand-assets.js` script.
+
+This makes them directly available in the browser at:
+- `/brand/logo.png`
+- `/brand/favicon.svg`
+
+---
+
+## 🚀 Usage in Consuming Apps
+
+### 1. Automate the Copy Process
+To ensure the consuming app always has the latest brand assets, add the copy script to the app's `package.json` (e.g., in `apps/web/package.json`):
+
+```json
+"scripts": {
+ "copy:brand": "node ../../packages/brand/scripts/copy-brand-assets.js",
+ "dev": "pnpm run copy:brand && vite --clearScreen false",
+ "build": "pnpm run copy:brand && tsc && vite build"
+}
\ No newline at end of file
diff --git a/packages/brand/package.json b/packages/brand/package.json
new file mode 100644
index 0000000..d17cbe7
--- /dev/null
+++ b/packages/brand/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "@repo/brand",
+ "version": "0.0.0",
+ "private": true,
+ "exports": {
+ ".": "./src/index.ts",
+ "./public/*": "./public/*"
+ },
+ "license": "MIT",
+ "scripts": {
+ "typecheck": "tsc --noEmit"
+ },
+ "devDependencies": {
+ "@repo/typescript-config": "workspace:*",
+ "typescript": "5.5.4"
+ }
+}
diff --git a/packages/brand/public/favicon.svg b/packages/brand/public/favicon.svg
new file mode 100644
index 0000000..7abbacf
--- /dev/null
+++ b/packages/brand/public/favicon.svg
@@ -0,0 +1,6 @@
+
diff --git a/packages/brand/scripts/copy-brand-assets.js b/packages/brand/scripts/copy-brand-assets.js
new file mode 100644
index 0000000..6fcdedf
--- /dev/null
+++ b/packages/brand/scripts/copy-brand-assets.js
@@ -0,0 +1,31 @@
+const fs = require('node:fs');
+const path = require('node:path');
+
+// Resolve the source directory (packages/brand/public)
+const brandPublicDir = path.resolve(__dirname, '../public');
+
+// Resolve the destination directory (public/brand folder in the calling app)
+// process.cwd() points to the app folder (e.g., apps/web) when the script is executed
+const destDir = path.resolve(process.cwd(), 'public', 'brand');
+
+// Ensure the destination folder exists
+if (!fs.existsSync(destDir)) {
+ fs.mkdirSync(destDir, { recursive: true });
+}
+
+// Copy files using fs.cpSync (Available in Node.js 16.7+)
+try {
+ fs.cpSync(brandPublicDir, destDir, {
+ recursive: true,
+ force: true, // Overwrite files if they already exist
+ filter: (src) => {
+ const filename = path.basename(src);
+ // Exclude unwanted files
+ return !['.gitkeep', 'README.md'].includes(filename);
+ },
+ });
+ console.log(`✓ Brand assets successfully copied to ${destDir}`);
+} catch (error) {
+ console.error(`❌ Failed to copy brand assets:`, error.message);
+ process.exit(1);
+}
diff --git a/packages/brand/src/data/info.json b/packages/brand/src/data/info.json
new file mode 100644
index 0000000..b72a7be
--- /dev/null
+++ b/packages/brand/src/data/info.json
@@ -0,0 +1,10 @@
+{
+ "name": "My Brand",
+ "description": "A short description of your brand or product.",
+ "tagline": "Building the future, one pixel at a time.",
+ "socialLinks": {
+ "twitter": "https://twitter.com/mybrand",
+ "github": "https://github.com/mybrand",
+ "website": "https://mybrand.com"
+ }
+}
diff --git a/packages/brand/src/index.ts b/packages/brand/src/index.ts
new file mode 100644
index 0000000..fb17fae
--- /dev/null
+++ b/packages/brand/src/index.ts
@@ -0,0 +1,3 @@
+import brandInfo from './data/info.json';
+
+export type BrandInfo = typeof brandInfo;
diff --git a/packages/brand/tsconfig.json b/packages/brand/tsconfig.json
new file mode 100644
index 0000000..f1277d8
--- /dev/null
+++ b/packages/brand/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "extends": "@repo/typescript-config/library.json",
+ "include": ["."],
+ "exclude": ["node_modules"]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a833d8e..68c45b1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -88,6 +88,9 @@ importers:
apps/landing:
dependencies:
+ '@repo/brand':
+ specifier: workspace:*
+ version: link:../../packages/brand
'@repo/core-api':
specifier: workspace:*
version: link:../../packages/core-api
@@ -243,6 +246,9 @@ importers:
'@hookform/resolvers':
specifier: ^5.0.1
version: 5.4.0(react-hook-form@7.79.0)
+ '@repo/brand':
+ specifier: workspace:*
+ version: link:../../packages/brand
'@repo/core-api':
specifier: workspace:*
version: link:../../packages/core-api
@@ -329,6 +335,15 @@ importers:
specifier: ^4.0.17
version: 4.0.17(jsdom@26.1.0)
+ packages/brand:
+ devDependencies:
+ '@repo/typescript-config':
+ specifier: workspace:*
+ version: link:../configs/typescript
+ typescript:
+ specifier: 5.5.4
+ version: 5.5.4
+
packages/configs/eslint:
dependencies:
'@typescript-eslint/eslint-plugin':
@@ -7444,6 +7459,18 @@ packages:
dependencies:
picomatch: 4.0.3
+ /fdir@6.5.0(picomatch@4.0.5):
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+ dependencies:
+ picomatch: 4.0.5
+ dev: false
+
/fetch-cookie@2.2.0:
resolution: {integrity: sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==}
dependencies:
@@ -9989,6 +10016,11 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
+ /picomatch@4.0.5:
+ resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
+ engines: {node: '>=12'}
+ dev: false
+
/plist@3.1.0:
resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
engines: {node: '>=10.4.0'}
@@ -11252,7 +11284,7 @@ packages:
is-plain-obj: 4.1.0
semver: 7.7.1
sort-object-keys: 2.0.1
- tinyglobby: 0.2.15
+ tinyglobby: 0.2.17
dev: false
/source-map-js@1.2.1:
@@ -11627,6 +11659,14 @@ packages:
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
+ /tinyglobby@0.2.17:
+ resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
+ engines: {node: '>=12.0.0'}
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.5)
+ picomatch: 4.0.5
+ dev: false
+
/tinyrainbow@3.0.3:
resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==}
engines: {node: '>=14.0.0'}