Skip to content

Commit 3e3e0ed

Browse files
committed
init
0 parents  commit 3e3e0ed

File tree

11 files changed

+1718
-0
lines changed

11 files changed

+1718
-0
lines changed

.envrc.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Copy this file to .envrc and update with your values
2+
# If using direnv, run: direnv allow
3+
4+
# Apple Developer credentials
5+
export DEVELOPER_ID="YOUR_DEVELOPER_ID"
6+
export BUNDLE_ID="com.yourcompany.yourapp"
7+
export KEYCHAIN_PROFILE="YOUR_KEYCHAIN_PROFILE"

.github/workflows/release.yml

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
jobs:
9+
build-and-release:
10+
runs-on: macos-latest
11+
12+
permissions:
13+
contents: write
14+
15+
steps:
16+
- name: Checkout code
17+
uses: actions/checkout@v4
18+
19+
- name: Setup pnpm
20+
uses: pnpm/action-setup@v4
21+
with:
22+
version: 10.12.1
23+
24+
- name: Setup Node.js
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: '20'
28+
cache: 'pnpm'
29+
30+
- name: Install dependencies
31+
run: pnpm install --frozen-lockfile
32+
33+
- name: Build binaries
34+
run: pnpm build:binary
35+
36+
- name: Import signing certificate
37+
env:
38+
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
39+
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
40+
run: |
41+
# Create a temporary keychain
42+
KEYCHAIN_NAME="build.keychain"
43+
KEYCHAIN_PASSWORD="$(openssl rand -base64 32)"
44+
45+
# Create the keychain
46+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
47+
48+
# Set the keychain as default
49+
security default-keychain -s "$KEYCHAIN_NAME"
50+
51+
# Unlock the keychain
52+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
53+
54+
# Import certificate
55+
echo "$MACOS_CERTIFICATE" | base64 --decode > certificate.p12
56+
security import certificate.p12 -k "$KEYCHAIN_NAME" -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
57+
58+
# Allow codesign to access the certificate without prompting
59+
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
60+
61+
# Clean up
62+
rm certificate.p12
63+
64+
- name: Sign binaries
65+
env:
66+
DEVELOPER_ID: ${{ secrets.DEVELOPER_ID }}
67+
BUNDLE_ID: ${{ secrets.BUNDLE_ID }}
68+
run: |
69+
# Make binaries executable
70+
chmod +x bin/hello-lightdash-x64
71+
chmod +x bin/hello-lightdash-arm64
72+
73+
# Sign both binaries
74+
codesign -s "$DEVELOPER_ID" -f --timestamp -o runtime \
75+
-i "$BUNDLE_ID" --entitlements entitlements.plist \
76+
bin/hello-lightdash-x64
77+
78+
codesign -s "$DEVELOPER_ID" -f --timestamp -o runtime \
79+
-i "$BUNDLE_ID" --entitlements entitlements.plist \
80+
bin/hello-lightdash-arm64
81+
82+
# Verify signatures
83+
codesign --verify --verbose bin/hello-lightdash-x64
84+
codesign --verify --verbose bin/hello-lightdash-arm64
85+
86+
- name: Notarize binaries
87+
env:
88+
APPLE_ID: ${{ secrets.APPLE_ID }}
89+
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
90+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
91+
run: |
92+
# Store notarization credentials
93+
xcrun notarytool store-credentials "CI_NOTARIZE" \
94+
--apple-id "$APPLE_ID" \
95+
--team-id "$APPLE_TEAM_ID" \
96+
--password "$APPLE_PASSWORD"
97+
98+
# Create temporary directory for zips
99+
mkdir -p notarize-temp
100+
101+
# Function to notarize a binary
102+
notarize_binary() {
103+
local BINARY_NAME=$1
104+
local ZIP_PATH="notarize-temp/${BINARY_NAME}.zip"
105+
106+
echo "Notarizing ${BINARY_NAME}..."
107+
108+
# Create zip for notarization
109+
ditto -c -k --keepParent "bin/${BINARY_NAME}" "$ZIP_PATH"
110+
111+
# Submit for notarization and wait
112+
xcrun notarytool submit "$ZIP_PATH" \
113+
--keychain-profile "CI_NOTARIZE" \
114+
--wait
115+
116+
# Check status
117+
if [ $? -eq 0 ]; then
118+
echo "✓ Notarization successful for ${BINARY_NAME}"
119+
else
120+
echo "✗ Notarization failed for ${BINARY_NAME}"
121+
exit 1
122+
fi
123+
}
124+
125+
# Notarize both binaries
126+
notarize_binary "hello-lightdash-x64"
127+
notarize_binary "hello-lightdash-arm64"
128+
129+
# Clean up
130+
rm -rf notarize-temp
131+
132+
- name: Create release archives
133+
run: |
134+
# Get version from tag
135+
VERSION=${GITHUB_REF#refs/tags/}
136+
137+
# Create archives for each architecture
138+
tar -czf "hello-lightdash-${VERSION}-macos-x64.tar.gz" -C bin hello-lightdash-x64
139+
tar -czf "hello-lightdash-${VERSION}-macos-arm64.tar.gz" -C bin hello-lightdash-arm64
140+
141+
# Create checksums
142+
shasum -a 256 hello-lightdash-*.tar.gz > checksums.txt
143+
144+
- name: Create GitHub Release
145+
uses: softprops/action-gh-release@v2
146+
with:
147+
draft: false
148+
prerelease: false
149+
generate_release_notes: true
150+
files: |
151+
hello-lightdash-*.tar.gz
152+
checksums.txt
153+
body: |
154+
## Downloads
155+
156+
### macOS
157+
- **Apple Silicon (M1/M2/M3):** `hello-lightdash-${{ github.ref_name }}-macos-arm64.tar.gz`
158+
- **Intel:** `hello-lightdash-${{ github.ref_name }}-macos-x64.tar.gz`
159+
160+
### Installation
161+
162+
```bash
163+
# Download and extract (replace with your architecture)
164+
tar -xzf hello-lightdash-${{ github.ref_name }}-macos-arm64.tar.gz
165+
166+
# Make executable (if needed)
167+
chmod +x hello-lightdash-arm64
168+
169+
# Run
170+
./hello-lightdash-arm64
171+
```
172+
173+
### Verification
174+
175+
The binaries are signed and notarized by Apple. To verify:
176+
```bash
177+
codesign --verify --verbose hello-lightdash-arm64
178+
```
179+
180+
### Checksums
181+
182+
Verify download integrity:
183+
```bash
184+
shasum -a 256 -c checksums.txt
185+
```

.gitignore

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Dependencies
2+
node_modules/
3+
4+
# Build outputs
5+
dist/
6+
build/
7+
bin/
8+
9+
# IDE
10+
.vscode/
11+
.idea/
12+
13+
# OS
14+
.DS_Store
15+
16+
# Logs
17+
*.log
18+
npm-debug.log*
19+
yarn-debug.log*
20+
yarn-error.log*
21+
pnpm-debug.log*
22+
23+
# Environment
24+
.env
25+
.env.local
26+
.env.*.local
27+
.envrc

README.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# hello-lightdash-binary-example
2+
3+
TypeScript app that compiles to standalone macOS binaries.
4+
5+
## Structure
6+
7+
- `src/` - TypeScript source
8+
- `dist/` - TypeScript compilation output
9+
- `build/` - Bundled single-file JavaScript
10+
- `bin/` - Final binary executables
11+
12+
## Commands
13+
14+
```bash
15+
pnpm dev # Run TypeScript directly
16+
pnpm build # Compile TS → JS
17+
pnpm build:binary # Full build → binary
18+
```
19+
20+
## Binary Compilation Process
21+
22+
1. **TypeScript → JavaScript** (`tsc`)
23+
Compiles to CommonJS in `dist/`
24+
25+
2. **Bundle dependencies** (`@vercel/ncc`)
26+
Creates single-file bundle in `build/` with all node_modules included
27+
28+
3. **JavaScript → Binary** (`@yao-pkg/pkg`)
29+
Packages Node.js runtime + bundled code into standalone executables
30+
31+
## Output
32+
33+
Creates two binaries in `bin/`:
34+
- `hello-lightdash-arm64` - Apple Silicon (~46MB)
35+
- `hello-lightdash-x64` - Intel Macs (~51MB)
36+
37+
These run without Node.js installed. Each binary contains:
38+
- Node.js runtime (v20)
39+
- Your bundled application code
40+
- All dependencies
41+
42+
## Code Signing & Notarization
43+
44+
For macOS distribution without Gatekeeper warnings:
45+
46+
### 1. Code Sign
47+
```bash
48+
pnpm codesign
49+
```
50+
Signs both binaries with Developer ID and Bundle ID configured in `scripts/codesign.sh`.
51+
52+
### 2. Notarize
53+
```bash
54+
pnpm notarize
55+
```
56+
Submits binaries to Apple for notarization and staples the ticket for offline verification.
57+
58+
### Setup (one-time)
59+
60+
1. Copy `.envrc.example` to `.envrc` and update with your credentials
61+
2. If using direnv: `direnv allow`
62+
3. Store Apple credentials in keychain:
63+
```bash
64+
xcrun notarytool store-credentials "AppPasswordCodesignNotarize" \
65+
--apple-id "[email protected]" \
66+
--team-id "AF5SF5H727" \
67+
--password "app-specific-password"
68+
```
69+
70+
Generate app-specific password at [appleid.apple.com](https://appleid.apple.com).
71+
72+
### Verify
73+
```bash
74+
spctl -a -v bin/hello-lightdash-x64
75+
spctl -a -v bin/hello-lightdash-arm64
76+
```
77+
78+
## Distribution
79+
80+
Ship the appropriate binary for the target architecture. No installation required - users just run the executable.
81+
82+
## Testing
83+
84+
To check gatekeeper locally, force the quarantine attribute:
85+
86+
```
87+
xattr -w com.apple.quarantine "0083;$(date +%s);Safari;F643CD5F-6071-46AB-83AB-390BA944DEC5" /path/to/your/binary
88+
```
89+
90+
## CI/CD - GitHub Actions
91+
92+
The release workflow automatically builds, signs, notarizes, and publishes binaries when you push a version tag:
93+
94+
```bash
95+
git tag v1.0.0
96+
git push origin v1.0.0
97+
```
98+
99+
### Required GitHub Secrets
100+
101+
Configure these in your repository settings:
102+
103+
1. **MACOS_CERTIFICATE** - Base64 encoded p12 certificate
104+
```bash
105+
base64 -i DeveloperIDApplication.p12 | pbcopy
106+
```
107+
108+
2. **MACOS_CERTIFICATE_PASSWORD** - Password for the p12 certificate
109+
110+
3. **DEVELOPER_ID** - Your Developer ID (e.g., "Developer ID Application: Name (TEAMID)")
111+
112+
4. **BUNDLE_ID** - Your bundle identifier (e.g., "com.company.app")
113+
114+
5. **APPLE_ID** - Your Apple ID email
115+
116+
6. **APPLE_PASSWORD** - App-specific password for notarization
117+
118+
7. **APPLE_TEAM_ID** - Your Apple Team ID (e.g., "AF5SF5H727")
119+
120+
### Export Certificate for CI
121+
122+
To export your Developer ID certificate:
123+
```bash
124+
# Find your certificate
125+
security find-identity -v -p codesigning
126+
127+
# Export to p12 (replace with your identity)
128+
security export -k ~/Library/Keychains/login.keychain-db \
129+
-t identities -f pkcs12 -o DeveloperIDApplication.p12 \
130+
-P "your-password-here" \
131+
-T /usr/bin/codesign
132+
```

entitlements.plist

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<!-- Allow JIT compilation for V8 JavaScript engine -->
6+
<key>com.apple.security.cs.allow-jit</key>
7+
<true/>
8+
9+
<!-- Allow unsigned executable memory for V8 -->
10+
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
11+
<true/>
12+
13+
<!-- Allow DYLD environment variables -->
14+
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
15+
<true/>
16+
17+
<!-- Disable library validation to allow loading of unsigned libraries -->
18+
<key>com.apple.security.cs.disable-library-validation</key>
19+
<true/>
20+
21+
<!-- Disable executable memory protection -->
22+
<key>com.apple.security.cs.disable-executable-page-protection</key>
23+
<true/>
24+
</dict>
25+
</plist>

0 commit comments

Comments
 (0)