@@ -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)
|
||||
|
||||
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 516 B |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 177 KiB |
@@ -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/*`.
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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')],
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 650 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 672 B |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 650 B |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 650 B |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 650 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 672 B |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 41 KiB |
@@ -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 |
|
||||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 158 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 170 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
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.
|
||||
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 149 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 555 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 656 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 632 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 200 KiB |
@@ -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}…`)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 650 B |