Skip to content

Commit 7321c61

Browse files
committed
feat(penpal): add packaging script and dev build branding
- Add scripts/package.sh to zip .app bundles for distribution using ditto (preserves code signatures and resource forks). Accepts an optional arch argument for CI cross-compilation. - Patch `just install` to set CFBundleName/CFBundleDisplayName to "Penpal Dev" so developers can distinguish local builds from Homebrew installs at a glance. - Add `just package` recipe that builds and packages in one step. - Add dist/ to .gitignore and clean recipe.
1 parent 87fb886 commit 7321c61

4 files changed

Lines changed: 71 additions & 10 deletions

File tree

apps/penpal/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
# Test artifacts
1919
test-results/
2020

21+
# Package output
22+
dist/
23+
2124
# Tauri build artifacts
2225
frontend/src-tauri/target/
2326
frontend/src-tauri/binaries/

apps/penpal/frontend/src/components/Layout.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,20 @@ export default function Layout() {
226226
}, []);
227227

228228
function handleInstallModalClose(installed: boolean) {
229-
if (installed || !toolsInstalled) {
230-
// Persist dismiss: tools are up to date, or user opted out with nothing installed
231-
localStorage.setItem(`penpal-install-dismissed-${__BUILD_ID__}`, '1');
232-
}
233229
setShowInstallModal(false);
230+
// Re-check actual install status so dismiss decision uses current state,
231+
// not the stale initial `toolsInstalled` value. This prevents partial
232+
// installs (e.g. CLI succeeded but plugin failed) from being dismissed.
233+
api.checkInstallStatus()
234+
.then((status) => {
235+
const allInstalled = status.cli.installed && status.plugin.installed;
236+
const noneInstalled = !status.cli.installed && !status.plugin.installed;
237+
if (allInstalled || noneInstalled) {
238+
localStorage.setItem(`penpal-install-dismissed-${__BUILD_ID__}`, '1');
239+
}
240+
setToolsInstalled(status.cli.installed || status.plugin.installed);
241+
})
242+
.catch(() => {});
234243
}
235244

236245

apps/penpal/justfile

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,34 +39,43 @@ dev: ensure-deps build-go
3939
build-go: ensure-deps
4040
./scripts/build-go.sh
4141

42-
# Install Penpal: build desktop app and copy to /Applications
43-
install: build
42+
# Install Penpal: build dev-branded desktop app and copy to /Applications
43+
install: ensure-deps build-go
4444
#!/usr/bin/env bash
4545
set -euo pipefail
4646

47+
# Build with "Penpal Dev" product name so devs can distinguish from Homebrew installs
48+
cd frontend && pnpm install && VITE_BASE=/ VITE_API_URL=http://localhost:8080 pnpm run build \
49+
&& npx tauri build --config '{"productName":"Penpal Dev"}'
50+
51+
cd ..
52+
4753
# Quit running Penpal if present
4854
if pgrep -x Penpal >/dev/null 2>&1; then
4955
echo "Quitting Penpal..."
5056
osascript -e 'quit app "Penpal"' 2>/dev/null || true
5157
sleep 1
52-
# Force kill if it didn't quit gracefully
5358
pkill -x Penpal 2>/dev/null || true
5459
fi
5560

56-
# Copy .app to /Applications
57-
APP_SRC="frontend/src-tauri/target/release/bundle/macos/Penpal.app"
61+
# The dev build bundles as "Penpal Dev.app"
62+
APP_SRC="frontend/src-tauri/target/release/bundle/macos/Penpal Dev.app"
5863
if [ -d "$APP_SRC" ]; then
5964
echo "Installing Penpal.app to /Applications..."
6065
rm -rf /Applications/Penpal.app
6166
cp -R "$APP_SRC" /Applications/Penpal.app
62-
echo "Penpal.app installed."
67+
echo "Penpal.app installed (dev build)."
6368
else
6469
echo "Warning: $APP_SRC not found, skipping app install."
6570
fi
6671

6772
# Launch the app — CLI + plugin are installed via in-app menu
6873
open /Applications/Penpal.app
6974

75+
# Package .app into a distributable zip
76+
package: build
77+
./scripts/package.sh
78+
7079
# Uninstall Penpal
7180
uninstall:
7281
#!/usr/bin/env bash
@@ -125,6 +134,7 @@ test-e2e: ensure-deps
125134
# Clean build artifacts
126135
clean:
127136
rm -f penpal
137+
rm -rf dist
128138
rm -rf frontend/dist
129139
rm -rf frontend/src-tauri/target
130140
rm -rf frontend/src-tauri/binaries

apps/penpal/scripts/package.sh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5+
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
6+
DIST_DIR="$ROOT_DIR/dist"
7+
8+
# Read version from Cargo.toml
9+
VERSION=$(grep '^version' "$ROOT_DIR/frontend/src-tauri/Cargo.toml" | head -1 | sed 's/version = "//;s/"//')
10+
11+
# Architecture: use argument if provided, otherwise detect host
12+
ARCH="${1:-$(uname -m)}"
13+
case "$ARCH" in
14+
aarch64|arm64) ARCH="arm64" ;;
15+
x86_64|amd64) ARCH="x86_64" ;;
16+
*) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;;
17+
esac
18+
19+
APP_SRC="$ROOT_DIR/frontend/src-tauri/target/release/bundle/macos/Penpal.app"
20+
if [ ! -d "$APP_SRC" ]; then
21+
echo "Error: $APP_SRC not found. Run 'just build' first." >&2
22+
exit 1
23+
fi
24+
25+
# Verify the binary architecture matches the requested arch
26+
ACTUAL_ARCH=$(lipo -archs "$APP_SRC/Contents/MacOS/Penpal")
27+
if [[ "$ACTUAL_ARCH" != *"$ARCH"* ]]; then
28+
echo "Error: Binary architecture mismatch. Requested '$ARCH' but binary contains '$ACTUAL_ARCH'." >&2
29+
exit 1
30+
fi
31+
32+
mkdir -p "$DIST_DIR"
33+
OUTPUT="$DIST_DIR/Penpal-${VERSION}-${ARCH}.zip"
34+
35+
echo "Packaging Penpal.app → $OUTPUT"
36+
# ditto preserves resource forks and code signatures; --keepParent puts Penpal.app at the zip root
37+
ditto -c -k --sequesterRsrc --keepParent "$APP_SRC" "$OUTPUT"
38+
39+
echo "Done: $OUTPUT ($(du -h "$OUTPUT" | cut -f1))"

0 commit comments

Comments
 (0)