Branding+CI Updates
Release rolling / release (push) Has been cancelled

This commit is contained in:
Raven Scott
2026-07-11 13:19:50 -04:00
parent 2e8ad21326
commit ce6a38aade
110 changed files with 939 additions and 49 deletions
+20 -12
View File
@@ -84,29 +84,37 @@ jobs:
ca-certificates
- name: Install dependencies
timeout-minutes: 20
timeout-minutes: 15
env:
NODE_OPTIONS: '--dns-result-order=ipv4first'
# Faster/safer git deps (lockfile must use https; also rewrite any ssh URLs)
GIT_TERMINAL_PROMPT: '0'
# Electron postinstall (~100MB+) — fail fast rather than hang forever
ELECTRON_GET_USE_PROXY: '0'
npm_config_fetch_retries: '3'
npm_config_fetch_retry_mintimeout: '10000'
npm_config_fetch_retry_maxtimeout: '60000'
npm_config_fetch_timeout: '120000'
npm_config_maxsockets: '20'
# Never clone git deps over SSH (slow/hangs without keys)
npm_config_fetch_retries: '2'
npm_config_fetch_retry_mintimeout: '5000'
npm_config_fetch_retry_maxtimeout: '30000'
npm_config_fetch_timeout: '90000'
npm_config_maxsockets: '50'
npm_config_progress: 'false'
npm_config_fund: 'false'
npm_config_audit: 'false'
npm_config_loglevel: 'error'
run: |
set -euo pipefail
# CI runners often cannot use [email protected] SSH — force HTTPS for GitHub
# Hard fail if lockfile regressed to git+ssh (clones hang forever on CI)
if grep -E 'git\+ssh://|git@github\.com:' package-lock.json >/dev/null 2>&1; then
echo "ERROR: package-lock.json contains git+ssh deps — use npm registry/overrides"
grep -nE 'git\+ssh://|git@github\.com:' package-lock.json | head -20
exit 1
fi
# If any remaining git+https github deps appear, force HTTPS (not SSH)
git config --global url."https://github.com/".insteadOf "ssh://[email protected]/"
git config --global url."https://github.com/".insteadOf "[email protected]:"
# --prefer-offline without an Actions cache just wastes time checking emptiness
# Electron + bare-runtime multi-arch prebuilds typically take several minutes.
npm ci --no-audit --no-fund --no-progress --loglevel=warn
git config --global url."https://github.com/".insteadOf "git+ssh://[email protected]/"
# Timed install — electron + bare-runtime prebuilds are the bulk of the work
echo "npm ci starting $(date -u +%H:%M:%S)"
time npm ci --no-audit --no-fund --no-progress --loglevel=error
echo "npm ci finished $(date -u +%H:%M:%S)"
# Ad-hoc codesign for darwin .app bundles produced on Linux (fixes Gatekeeper "damaged")
- name: Install rcodesign (macOS ad-hoc signing on Linux)
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

+12 -2
View File
@@ -17,12 +17,22 @@ Canonical list: `scripts/hosts.cjs` → `ALL_64`.
---
## Branding
Source kit: `peardock-branding/` (logos, palette, app icons).
```bash
npm run brand:sync # → build/icon.{png,icns,ico} + ui/brand/*
```
CI / `forge` prePackage / bare-standalone all run this so binary icons and UI match the brand kit.
## Builders
| Artifact | Tooling |
|----------|---------|
| **peardock-server** | `scripts/bare-standalone.cjs``bare-pack` with **bare-node-runtime** `imports` + stubs, then embed **bare-runtime** prebuild (`bare-build` platform hooks) |
| **peardock-client** | `esbuild``electron/app.bundle.cjs`, then `electron-forge package --platform … --arch …` |
| **peardock-server** | `scripts/bare-standalone.cjs``bare-pack` with **bare-node-runtime** `imports` + stubs, then embed **bare-runtime** prebuild (`bare-build` platform hooks) + brand icon |
| **peardock-client** | `brand:sync``esbuild``electron/app.bundle.cjs`, then `electron-forge package --platform … --arch …` + macOS codesign |
Server packing uses `package.json` `imports` so Node builtins resolve to `bare-*` under Bare. Optional natives (`ssh2`, `@grpc/*`) map to `build/stubs/*`.
+42 -2
View File
@@ -207,12 +207,33 @@ async function startHolesailControl() {
}
}
function resolveAppIcon() {
const candidates = [
path.join(__dirname, '..', 'build', 'icon.png'),
path.join(__dirname, '..', 'ui', 'brand', 'mark.png'),
path.join(
__dirname,
'..',
'peardock-branding',
'app-icons',
'ios',
'peardock-512.png'
),
]
for (const p of candidates) {
if (fs.existsSync(p)) return p
}
return null
}
function createWindow() {
const icon = resolveAppIcon()
const win = new BrowserWindow({
width: pkg.pear?.gui?.width || 1280,
height: pkg.pear?.gui?.height || 800,
backgroundColor: pkg.pear?.gui?.backgroundColor || '#0a0c10',
title: appName,
backgroundColor: pkg.pear?.gui?.backgroundColor || '#0F172A',
title: appName === 'peardock' ? 'PearDock' : appName,
...(icon ? { icon } : {}),
webPreferences: {
preload: buildPreloadPath(),
// Full peardock UI imports HyperDHT etc. as ESM node packages — same as pear UI.
@@ -264,6 +285,25 @@ app.whenReady().then(async () => {
// App root: project root in dev; Electron app path (app.asar or app/) when packaged
const appRoot = app.isPackaged ? app.getAppPath() : path.resolve(__dirname, '..')
// Brand: dock / about panel (macOS)
try {
const icon = resolveAppIcon()
if (process.platform === 'darwin' && app.dock && icon) {
const { nativeImage } = require('electron')
app.dock.setIcon(nativeImage.createFromPath(icon))
}
if (process.platform === 'darwin' && app.setAboutPanelParameters) {
app.setAboutPanelParameters({
applicationName: 'PearDock',
applicationVersion: pkg.version,
copyright: 'PearDock — P2P Docker',
...(icon ? { iconPath: icon } : {}),
})
}
} catch (err) {
console.warn('[peardock] brand dock/about skipped:', err.message || err)
}
await startStaticServer(appRoot)
await startHolesailControl()
+18 -1
View File
@@ -12,7 +12,9 @@ const path = require('path')
const fs = require('fs')
const pkg = require('./package.json')
const appName = pkg.productName || pkg.name || 'peardock'
// Stable on-disk names (out/peardock-darwin-arm64/…) for CI; UI title uses productName
const appName = pkg.name || 'peardock'
const displayName = pkg.productName || 'PearDock'
/** Path prefixes (packager paths start with /) to exclude from the app bundle */
const IGNORE_PREFIXES = [
@@ -29,6 +31,8 @@ const IGNORE_PREFIXES = [
'/scripts',
'/bin',
'/workers',
// Source branding kit is large; runtime uses build/ + ui/brand/ after brand:sync
'/peardock-branding',
'/build/stubs',
'/build/shims',
// Packaging / Bare server toolchain (not needed at Electron runtime)
@@ -114,8 +118,15 @@ module.exports = {
name: appName,
executableName: 'peardock-client',
appBundleId: 'com.peardock.app',
appCopyright: `${displayName} — P2P Docker`,
appCategoryType: 'public.app-category.developer-tools',
// Prefer generated pack icons from peardock-branding (npm run brand:sync)
icon: fs.existsSync(path.join(__dirname, 'build', 'icon.png'))
? path.join(__dirname, 'build', 'icon')
: fs.existsSync(
path.join(__dirname, 'peardock-branding', 'app-icons', 'ios', 'peardock-1024.png')
)
? path.join(__dirname, 'peardock-branding', 'app-icons', 'ios', 'peardock-1024')
: undefined,
// CJS require() works inside asar (Electron patches it). Unpack natives only.
// asar:false made "Finalizing package" copy the entire tree as loose files → CI stalls.
@@ -161,6 +172,12 @@ module.exports = {
hooks: {
prePackage: async () => {
// Brand icons/assets then GUI bundle (order matters for packager icon path)
require('child_process').execFileSync(
process.execPath,
[path.join(__dirname, 'scripts', 'sync-branding.cjs')],
{ stdio: 'inherit', cwd: __dirname }
)
require('child_process').execFileSync(
process.execPath,
[path.join(__dirname, 'scripts', 'build-client-bundle.cjs')],
+12 -3
View File
@@ -4,7 +4,13 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>peardock</title>
<title>PearDock</title>
<meta name="description" content="PearDock — P2P Docker management over HyperDHT">
<meta name="theme-color" content="#0F172A">
<link rel="icon" href="ui/brand/favicon/favicon.ico" sizes="any">
<link rel="icon" type="image/png" sizes="32x32" href="ui/brand/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="ui/brand/favicon/favicon-16x16.png">
<link rel="apple-touch-icon" href="ui/brand/favicon/apple-touch-icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
@@ -13,6 +19,7 @@
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/xterm.css">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
<link rel="stylesheet" href="ui/brand/brand.css">
<link rel="stylesheet" href="ui/styles.css">
<link rel="stylesheet" href="ui/modern.css">
<link rel="stylesheet" href="ui/ops.css">
@@ -30,8 +37,10 @@
<div class="titlebar-left">
<pear-ctrl></pear-ctrl>
<div class="app-brand">
<div class="app-brand-mark">🍐</div>
<div class="app-brand-text">peardock <span>P2P Docker</span></div>
<div class="app-brand-mark" aria-hidden="true">
<img src="ui/brand/mark.png" alt="" width="22" height="22" draggable="false">
</div>
<div class="app-brand-text">PearDock <span>P2P Docker</span></div>
</div>
</div>
<div class="titlebar-right">
+4 -4
View File
@@ -462,9 +462,9 @@
}
},
"node_modules/@electron/node-gyp": {
"version": "10.2.0-electron.1",
"resolved": "git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2",
"integrity": "sha512-lBSgDMQqt7QWMuIjS8zNAq5FI5o5RVBAcJUGWGI6GgoQITJt3msAkUrHp8YHj3RTVE+h70ndqMGqURjp3IfRyQ==",
"version": "10.2.0-electron.2",
"resolved": "https://registry.npmjs.org/@electron/node-gyp/-/node-gyp-10.2.0-electron.2.tgz",
"integrity": "sha512-OhO6fwqpetMO1vWI3+J8mb3a4s4A405tgKoUCJsgd4nyQDdFh0VvZm+gj/Cc70iRLQoIYUfSaAgYSVwmLsQHig==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -644,7 +644,7 @@
"dev": true,
"license": "MIT",
"dependencies": {
"@electron/node-gyp": "git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2",
"@electron/node-gyp": "10.2.0-electron.2",
"@malept/cross-spawn-promise": "^2.0.0",
"chalk": "^4.0.0",
"debug": "^4.1.1",
+9 -5
View File
@@ -5,7 +5,7 @@
"type": "module",
"license": "Apache-2.0",
"main": "index.js",
"productName": "peardock",
"productName": "PearDock",
"imports": {
"assert": {
"bare": "bare-assert",
@@ -490,10 +490,10 @@
},
"pear": {
"pre": "pear-electron/pre",
"name": "peardock",
"name": "PearDock",
"gui": {
"main": "index.html",
"backgroundColor": "#0a0c10",
"backgroundColor": "#0F172A",
"height": 800,
"width": 1280
},
@@ -520,8 +520,9 @@
"soak:24h": "SOAK_DURATION_MS=86400000 node scripts/soak.js",
"release:checksums": "bash scripts/release-checksums.sh",
"sbom:notes": "node -e \"console.log('See docs/SBOM.md')\"",
"brand:sync": "node scripts/sync-branding.cjs",
"build:client-bundle": "node scripts/build-client-bundle.cjs",
"start:client": "npm run build:client-bundle && electron electron/main.cjs",
"start:client": "npm run brand:sync && npm run build:client-bundle && electron electron/main.cjs",
"package:client": "electron-forge package",
"make": "node scripts/make.cjs both",
"make:server": "node scripts/make.cjs server",
@@ -615,5 +616,8 @@
},
"upgrade": "pear://u8z9cp4jp33zf14z5krkt83agc8n566nyxtf5rprkr5q7mq7mkeo",
"upgradeServer": "pear://wx6ki5nwxf6zq8aeqti639fr1isu5ppq1hs51hhpgkfhzp44ncuy",
"upgradeClient": "pear://cw9t45wouuzzu6bai5tookzzowo4iy84czua3n3ek9n4jtmhehiy"
"upgradeClient": "pear://cw9t45wouuzzu6bai5tookzzowo4iy84czua3n3ek9n4jtmhehiy",
"overrides": {
"@electron/node-gyp": "10.2.0-electron.2"
}
}
+118
View File
@@ -0,0 +1,118 @@
# PearDock Branding Package
**Complete professional branding kit for PearDock**
P2P Container Runtime • Powered by Pear
https://git.ssh.surf/snxraven/peardock
---
## 📦 What's Included
### 1. Brand Guidelines (PDF)
- `guidelines/PearDock-Brand-Guidelines.pdf`
Full 7-page professional guidelines covering brand story, logo system, color palette, typography, app icons, do's & don'ts, and usage rules.
### 2. Logos (`logos/`)
| Folder | Contents |
|--------|----------|
| **primary/** | Transparent master + white / light / dark / soft-blue backgrounds + high-res 512 / 1024 / 2048 px |
| **icon-only/** | Tight-cropped icon perfect for app icons and avatars |
| **horizontal/** | Logo + "PearDock" wordmark side-by-side (headers, nav) |
| **wordmark/** | Stacked logo + text (splash screens, about pages) |
| **monochrome/** | Pure black & pure white versions |
| **social/** | OG image, GitHub social preview, Instagram square |
### 3. App Icons (`app-icons/`)
- **iOS**: 16 → 1024 px (regular + rounded)
- **Android**: 48 → 512 px
- **Favicon package**: 16, 32, 48, apple-touch-icon (180), android-chrome-192/512, **favicon.ico**
### 4. Colors (`colors/`)
- Visual palette swatches (`peardock-color-palette.png`)
- `palette.md` with Hex / RGB / usage
---
## 🎨 Official Color Palette
| Name | Hex | Usage |
|-------------------|-----------|--------------------------------|
| Primary Blue | `#2563EB` | Whale, primary brand, CTAs |
| Pear Green | `#84CC16` | Pear body, success, accents |
| Molecule Cyan | `#06B6D4` | Network / docking elements |
| Deep Navy | `#0F172A` | Text, dark backgrounds |
| Stem Brown | `#B45309` | Organic detail only |
| Soft Sky | `#E0F2FE` | Light backgrounds |
---
## 🚀 App integration (peardock repo)
From the peardock project root, sync this kit into packaging + UI paths:
```bash
npm run brand:sync
# → build/icon.png, build/icon.icns, build/icon.ico
# → ui/brand/* (titlebar mark, logos, favicons, brand.css)
```
That runs automatically before Electron package (`forge` prePackage) and rolling release CI.
## 🚀 Quick Start Recommendations
**Website / Docs header**
`logos/horizontal/peardock-horizontal-transparent.png`
**App icon / PWA / mobile**
`app-icons/ios/peardock-1024.png` or the rounded variants
**Favicon**
→ Drop the entire `app-icons/favicon/` folder into your site root
(also synced to `ui/brand/favicon/` for the desktop shell)
**Dark mode**
→ Use `logos/monochrome/peardock-logo-white.png` or the dark-bg versions
**GitHub / Social**
`logos/social/github-social-preview.png` and `og-image-peardock.png`
---
## 📐 Clear Space & Rules
- Always keep ≥ 20% of logo height as clear space on all sides
- Minimum full-logo height: **32 px**
- Never recolor the pear or whale arbitrarily
- Never remove the keyhole or molecule
- Prefer the transparent masters
---
## 📁 File Tree
```
peardock-branding/
├── README.md ← you are here
├── guidelines/
│ └── PearDock-Brand-Guidelines.pdf
├── logos/
│ ├── primary/
│ ├── icon-only/
│ ├── horizontal/
│ ├── wordmark/
│ ├── monochrome/
│ └── social/
├── app-icons/
│ ├── ios/
│ ├── android/
│ └── favicon/
├── colors/
│ ├── peardock-color-palette.png
│ └── palette.md
└── exports/
```
---
**Generated for PearDock • July 2026**
Built with ❤️ for the Pear ecosystem
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

+12
View File
@@ -0,0 +1,12 @@
# PearDock Color Palette
| Name | Hex | RGB | Usage |
|------|-----|-----|-------|
| Primary Blue | `#2563EB` | 37,99,235 | Whale body, primary brand |
| Pear Green | `#84CC16` | 132,204,22 | Pear, accent, success |
| Molecule Cyan | `#06B6D4` | 6,182,212 | Network / docking elements |
| Deep Navy | `#0F172A` | 15,23,42 | Text, dark backgrounds |
| Stem Brown | `#B45309` | 180,83,9 | Organic detail |
| Soft Sky | `#E0F2FE` | 224,242,254 | Light backgrounds |
| White | `#FFFFFF` | 255,255,255 | Clean backgrounds |
| Slate | `#334155` | 51,65,85 | Secondary text |
Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@@ -0,0 +1 @@
Full vector SVG of the complex logo is best created in Figma/Illustrator from the master PNG. This package provides production-ready high-res PNGs.
Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

+19
View File
@@ -144,6 +144,24 @@ async function buildOne(host, outRoot) {
entry = entry.unmount(baseURL)
entry.id = id(entry).toString('hex')
// Brand icons for standalone binary (mac .icns / win .ico / png)
try {
require('child_process').execFileSync(
process.execPath,
[path.join(root, 'scripts', 'sync-branding.cjs')],
{ stdio: 'inherit', cwd: root }
)
} catch (err) {
console.warn('[bare-standalone] brand:sync skipped:', err.message || err)
}
const iconPng = path.join(root, 'build', 'icon.png')
const iconIco = path.join(root, 'build', 'icon.ico')
const iconIcns = path.join(root, 'build', 'icon.icns')
let icon
if (host.startsWith('win32') && fs.existsSync(iconIco)) icon = iconIco
else if (host.startsWith('darwin') && fs.existsSync(iconIcns)) icon = iconIcns
else if (fs.existsSync(iconPng)) icon = iconPng
const platform = platformForHost(host)
const opts = {
name,
@@ -156,6 +174,7 @@ async function buildOne(host, outRoot) {
standalone: true,
package: false,
base: root,
...(icon ? { icon } : {}),
}
console.log(`[bare-standalone] embedding bare-runtime for ${host}`)
+3
View File
@@ -61,6 +61,9 @@ export npm_config_build_from_source="${npm_config_build_from_source:-false}"
# Skip @electron/rebuild during forge package (prebuilds only) — avoids multi-minute "Finalizing"
export PEARDOCK_SKIP_REBUILD="${PEARDOCK_SKIP_REBUILD:-1}"
# Brand kit → build/icon.{png,icns,ico} + ui/brand/* (Electron + bare-build icons)
log "syncing peardock-branding"
node scripts/sync-branding.cjs
# --- server: all hosts (bare cross-compile) ---
log "building server binaries for: $PEARDOCK_SERVER_HOSTS"
+1
View File
@@ -54,6 +54,7 @@ function makeServer(hosts = SERVER_HOSTS) {
function makeClient(hosts = CLIENT_HOSTS) {
console.log(`[make] client hosts: ${hosts.join(', ')}`)
npmRun('brand:sync')
npmRun('build:client-bundle')
for (const host of hosts) {
const script = clientNpmScript(host)
+276
View File
@@ -0,0 +1,276 @@
#!/usr/bin/env node
/**
* Sync peardock-branding/ build/ + ui/brand/ for the app and binary packaging.
*
* Generates:
* build/icon.png Electron / generic (1024)
* build/icon.icns macOS (iconutil)
* build/icon.ico Windows (from branding favicon package)
* build/icon-*.png common sizes
* ui/brand/* runtime UI assets (titlebar, etc.)
* peardock-branding/app-icons/{macos,windows,web}/ filled platform packs
*
* Requires macOS `sips` + `iconutil` for .icns; otherwise skips icns with a warning.
* Safe to run on Linux CI for png/ico/ui copies.
*
* node scripts/sync-branding.cjs
* npm run brand:sync
*/
'use strict'
const fs = require('fs')
const path = require('path')
const { spawnSync, execFileSync } = require('child_process')
const root = path.resolve(__dirname, '..')
const brand = path.join(root, 'peardock-branding')
const buildDir = path.join(root, 'build')
const uiBrand = path.join(root, 'ui', 'brand')
const SRC = {
master1024: path.join(brand, 'app-icons', 'ios', 'peardock-1024.png'),
master512: path.join(brand, 'app-icons', 'ios', 'peardock-512.png'),
tight: path.join(brand, 'logos', 'icon-only', 'peardock-icon-tight-transparent.png'),
horizontalWhite: path.join(brand, 'logos', 'horizontal', 'peardock-horizontal-white.png'),
horizontalTrans: path.join(
brand,
'logos',
'horizontal',
'peardock-horizontal-transparent.png'
),
logoWhite: path.join(brand, 'logos', 'monochrome', 'peardock-logo-white.png'),
logoTrans: path.join(brand, 'logos', 'primary', 'peardock-logo-transparent-512.png'),
faviconDir: path.join(brand, 'app-icons', 'favicon'),
faviconIco: path.join(brand, 'app-icons', 'favicon', 'favicon.ico'),
}
function log(...a) {
console.log('[brand:sync]', ...a)
}
function mustExist(p, label) {
if (!fs.existsSync(p)) throw new Error(`Missing branding asset (${label}): ${p}`)
}
function copyFile(src, dest) {
fs.mkdirSync(path.dirname(dest), { recursive: true })
fs.copyFileSync(src, dest)
}
function which(cmd) {
const r = spawnSync(process.platform === 'win32' ? 'where' : 'which', [cmd], {
encoding: 'utf8',
})
return r.status === 0 ? r.stdout.trim().split(/\r?\n/)[0] : null
}
function sipsResize(src, dest, size) {
fs.mkdirSync(path.dirname(dest), { recursive: true })
execFileSync(
'sips',
['-z', String(size), String(size), src, '--out', dest],
{ stdio: 'pipe' }
)
}
function buildIcns(src1024, outIcns) {
if (!which('sips') || !which('iconutil')) {
log('skip .icns (sips/iconutil not available — run brand:sync on macOS for full set)')
return false
}
const iconset = path.join(buildDir, 'icon.iconset')
fs.rmSync(iconset, { recursive: true, force: true })
fs.mkdirSync(iconset, { recursive: true })
const pairs = [
[16, 'icon_16x16.png'],
[32, '[email protected]'],
[32, 'icon_32x32.png'],
[64, '[email protected]'],
[128, 'icon_128x128.png'],
[256, '[email protected]'],
[256, 'icon_256x256.png'],
[512, '[email protected]'],
[512, 'icon_512x512.png'],
[1024, '[email protected]'],
]
for (const [size, name] of pairs) {
sipsResize(src1024, path.join(iconset, name), size)
}
execFileSync('iconutil', ['-c', 'icns', iconset, '-o', outIcns], { stdio: 'pipe' })
fs.rmSync(iconset, { recursive: true, force: true })
log('wrote', path.relative(root, outIcns))
return true
}
function syncUiBrand() {
fs.mkdirSync(uiBrand, { recursive: true })
copyFile(SRC.tight, path.join(uiBrand, 'mark.png'))
copyFile(SRC.logoWhite, path.join(uiBrand, 'logo-white.png'))
copyFile(SRC.logoTrans, path.join(uiBrand, 'logo-transparent.png'))
copyFile(SRC.horizontalWhite, path.join(uiBrand, 'horizontal-white.png'))
copyFile(SRC.horizontalTrans, path.join(uiBrand, 'horizontal-transparent.png'))
// favicon set for the desktop shell
const favOut = path.join(uiBrand, 'favicon')
fs.mkdirSync(favOut, { recursive: true })
for (const name of fs.readdirSync(SRC.faviconDir)) {
if (name.startsWith('.')) continue
copyFile(path.join(SRC.faviconDir, name), path.join(favOut, name))
}
log('wrote ui/brand/*')
}
function syncBuildIcons() {
fs.mkdirSync(buildDir, { recursive: true })
// Master PNG for electron-forge (path without extension: build/icon)
copyFile(SRC.master1024, path.join(buildDir, 'icon.png'))
if (which('sips')) {
for (const size of [16, 32, 48, 64, 128, 256, 512]) {
sipsResize(SRC.master1024, path.join(buildDir, `icon-${size}.png`), size)
}
} else {
// Linux CI: copy existing ios sizes where possible
const map = {
16: 'peardock-16.png',
32: 'peardock-32.png',
48: 'peardock-48.png',
64: 'peardock-64.png',
128: 'peardock-128.png',
256: 'peardock-256.png',
512: 'peardock-512.png',
}
for (const [size, name] of Object.entries(map)) {
const src = path.join(brand, 'app-icons', 'ios', name)
if (fs.existsSync(src)) copyFile(src, path.join(buildDir, `icon-${size}.png`))
}
}
// Multi-size .ico (PNG-in-ICO, Vista+) for Windows Electron / bare-build
writeMultiSizeIco(
[16, 32, 48, 64, 128, 256].map((s) => path.join(buildDir, `icon-${s}.png`)),
path.join(buildDir, 'icon.ico')
)
buildIcns(SRC.master1024, path.join(buildDir, 'icon.icns'))
log('wrote build/icon.png (+ sizes)')
}
/**
* Build a multi-resolution ICO containing PNG images (supported since Windows Vista).
* @param {string[]} pngPaths existing PNG files (missing sizes skipped)
* @param {string} outIco
*/
function writeMultiSizeIco(pngPaths, outIco) {
const images = []
for (const p of pngPaths) {
if (!fs.existsSync(p)) continue
const data = fs.readFileSync(p)
// read IHDR width/height
if (data.length < 24 || data[0] !== 0x89) continue
const w = data.readUInt32BE(16)
const h = data.readUInt32BE(20)
images.push({ w: w >= 256 ? 0 : w, h: h >= 256 ? 0 : h, data })
}
if (!images.length) {
if (fs.existsSync(SRC.faviconIco)) {
copyFile(SRC.faviconIco, outIco)
log('wrote build/icon.ico (fallback favicon)')
}
return
}
const count = images.length
const headerSize = 6
const dirEntrySize = 16
let offset = headerSize + dirEntrySize * count
const header = Buffer.alloc(headerSize)
header.writeUInt16LE(0, 0) // reserved
header.writeUInt16LE(1, 2) // type icon
header.writeUInt16LE(count, 4)
const dirs = []
const blobs = []
for (const img of images) {
const dir = Buffer.alloc(dirEntrySize)
dir.writeUInt8(img.w > 255 ? 0 : img.w, 0)
dir.writeUInt8(img.h > 255 ? 0 : img.h, 1)
dir.writeUInt8(0, 2) // color palette
dir.writeUInt8(0, 3)
dir.writeUInt16LE(1, 4) // planes
dir.writeUInt16LE(32, 6) // bit count
dir.writeUInt32LE(img.data.length, 8)
dir.writeUInt32LE(offset, 12)
offset += img.data.length
dirs.push(dir)
blobs.push(img.data)
}
fs.writeFileSync(outIco, Buffer.concat([header, ...dirs, ...blobs]))
log('wrote build/icon.ico (', count, 'sizes )')
}
function fillPlatformFolders() {
// macos
const macDir = path.join(brand, 'app-icons', 'macos')
fs.mkdirSync(macDir, { recursive: true })
if (fs.existsSync(path.join(buildDir, 'icon.icns'))) {
copyFile(path.join(buildDir, 'icon.icns'), path.join(macDir, 'peardock.icns'))
}
copyFile(SRC.master1024, path.join(macDir, 'peardock-1024.png'))
if (fs.existsSync(path.join(buildDir, 'icon-512.png'))) {
copyFile(path.join(buildDir, 'icon-512.png'), path.join(macDir, 'peardock-512.png'))
}
// windows
const winDir = path.join(brand, 'app-icons', 'windows')
fs.mkdirSync(winDir, { recursive: true })
if (fs.existsSync(path.join(buildDir, 'icon.ico'))) {
copyFile(path.join(buildDir, 'icon.ico'), path.join(winDir, 'peardock.ico'))
}
copyFile(SRC.master1024, path.join(winDir, 'peardock-1024.png'))
// web
const webDir = path.join(brand, 'app-icons', 'web')
fs.mkdirSync(webDir, { recursive: true })
for (const name of fs.readdirSync(SRC.faviconDir)) {
if (name.startsWith('.')) continue
copyFile(path.join(SRC.faviconDir, name), path.join(webDir, name))
}
log('filled peardock-branding/app-icons/{macos,windows,web}')
}
function writeBrandCss() {
const css = `/**
* PearDock brand tokens generated by scripts/sync-branding.cjs
* Source: peardock-branding/colors/palette.md
*/
:root {
--pd-blue: #2563eb;
--pd-green: #84cc16;
--pd-cyan: #06b6d4;
--pd-navy: #0f172a;
--pd-stem: #b45309;
--pd-sky: #e0f2fe;
--pd-white: #ffffff;
--pd-slate: #334155;
/* App surface aliases (dark desktop shell) */
--pd-bg: #0a0c10;
--pd-bg-elevated: #0f172a;
}
`
fs.writeFileSync(path.join(uiBrand, 'brand.css'), css)
log('wrote ui/brand/brand.css')
}
function main() {
if (!fs.existsSync(brand)) {
throw new Error('peardock-branding/ not found — add the branding kit to the repo root')
}
mustExist(SRC.master1024, 'ios 1024 icon')
mustExist(SRC.tight, 'icon-only mark')
mustExist(SRC.faviconDir, 'favicon package')
syncBuildIcons()
syncUiBrand()
fillPlatformFolders()
writeBrandCss()
log('done')
}
main()
+18
View File
@@ -0,0 +1,18 @@
/**
* PearDock brand tokens generated by scripts/sync-branding.cjs
* Source: peardock-branding/colors/palette.md
*/
:root {
--pd-blue: #2563eb;
--pd-green: #84cc16;
--pd-cyan: #06b6d4;
--pd-navy: #0f172a;
--pd-stem: #b45309;
--pd-sky: #e0f2fe;
--pd-white: #ffffff;
--pd-slate: #334155;
/* App surface aliases (dark desktop shell) */
--pd-bg: #0a0c10;
--pd-bg-elevated: #0f172a;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Some files were not shown because too many files have changed in this diff Show More