feat: implement @repo/brand package for centralized asset management and automated distribution to consuming apps

This commit is contained in:
Firman Ramdhani
2026-07-29 12:27:27 +07:00
parent 6c67fbc68e
commit fcea8f221b
15 changed files with 162 additions and 10 deletions
+32
View File
@@ -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"
}
+17
View File
@@ -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"
}
}
+6
View File
@@ -0,0 +1,6 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Blok Utama (Brand Color) -->
<rect x="6" y="6" width="16" height="16" rx="4" fill="#652ed9"/>
<!-- Blok Sekunder (Memberikan kesan struktur & kedalaman) -->
<rect x="18" y="18" width="16" height="16" rx="4" fill="#652ed9" fill-opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 359 B

@@ -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);
}
+10
View File
@@ -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"
}
}
+3
View File
@@ -0,0 +1,3 @@
import brandInfo from './data/info.json';
export type BrandInfo = typeof brandInfo;
+5
View File
@@ -0,0 +1,5 @@
{
"extends": "@repo/typescript-config/library.json",
"include": ["."],
"exclude": ["node_modules"]
}