Skip to content

Commit 9763ffa

Browse files
committed
Improve cache-ability.
1 parent cb66f0d commit 9763ffa

2 files changed

Lines changed: 120 additions & 52 deletions

File tree

docs/specs/deploy.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,21 @@ This runs in CI because VSCode Marketplace publishing uses PAT tokens (no hardwa
120120

121121
`scripts/sign-and-deploy.sh` — modeled on the Type The Rhythm script.
122122

123+
### Local directory layout
124+
125+
The script uses a three-directory layout under `release-signed/`:
126+
127+
| Directory | Purpose | Mutated? |
128+
|-----------|---------|----------|
129+
| `downloads/` | Raw CI artifacts, cached per-artifact | **Never** — read-only after download |
130+
| `work/` | Fresh copy of downloads for each signing run | Yes — codesign, jsign, and NSIS path patching all modify files here |
131+
| `release-assets/` | Final signed artifacts for GitHub Release upload | Yes — built from signed work copies |
132+
133+
**Key invariant:** Downloaded artifacts in `downloads/` are never modified. All signing and patching operates on copies in `work/`. This means:
134+
- Re-running a signing step after a failure doesn't require re-downloading
135+
- Modifying the signing scripts (e.g. `patch-nsis-paths.pl`) doesn't require re-downloading
136+
- Per-artifact caching: each artifact has its own download marker, so a partial download failure only retries the failed artifacts
137+
123138
### Prerequisites
124139

125140
```bash

scripts/sign-and-deploy.sh

Lines changed: 105 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,25 @@ set -euo pipefail
99
#
1010
# Usage: ./scripts/sign-and-deploy.sh all <version>
1111
# Example: ./scripts/sign-and-deploy.sh all 0.1.0
12+
#
13+
# INVARIANT: Downloaded artifacts in $DOWNLOAD_DIR are NEVER modified.
14+
# All signing/patching operates on copies in $SIGN_DIR.
15+
# This allows re-running any signing step without re-downloading.
1216
# =============================================================================
1317

1418
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1519
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
1620
WORK_DIR="$REPO_ROOT/release-signed"
21+
DOWNLOAD_DIR="$WORK_DIR/downloads"
22+
SIGN_DIR="$WORK_DIR/work"
23+
24+
# Known artifact names (must match release.yml matrix artifact-name values)
25+
ARTIFACT_NAMES=(
26+
standalone-mac-aarch64
27+
standalone-win-x64
28+
standalone-linux-x64
29+
vscode-extension
30+
)
1731

1832
# =============================================================================
1933
# Configuration
@@ -91,16 +105,23 @@ check_command() {
91105
command -v "$1" &>/dev/null || error "Required command not found: $1. Install with: $2"
92106
}
93107

94-
artifacts_cached() {
95-
local version="$1"
96-
[[ -f "$WORK_DIR/.version" ]] && [[ "$(cat "$WORK_DIR/.version")" == "$version" ]]
108+
# Returns 0 if a specific artifact has already been downloaded
109+
artifact_downloaded() {
110+
local name="$1"
111+
[[ -f "$DOWNLOAD_DIR/.downloaded-$name" ]]
112+
}
113+
114+
# Returns 0 if ALL known artifacts have been downloaded
115+
all_artifacts_downloaded() {
116+
for name in "${ARTIFACT_NAMES[@]}"; do
117+
artifact_downloaded "$name" || return 1
118+
done
119+
return 0
97120
}
98121

99122
check_git_clean() {
100123
log "Checking git status..."
101124

102-
rm -rf "$WORK_DIR"
103-
104125
if ! git -C "$REPO_ROOT" diff --quiet || ! git -C "$REPO_ROOT" diff --cached --quiet; then
105126
error "Local changes detected. Commit or stash changes before deploying."
106127
fi
@@ -125,8 +146,22 @@ check_git_clean() {
125146
log "Git status clean."
126147
}
127148

149+
# Copies downloaded artifacts to $SIGN_DIR for mutation.
150+
# Call this before any signing step to get a fresh working copy.
151+
prepare_sign_dir() {
152+
log "Preparing working copies from downloaded artifacts..."
153+
rm -rf "$SIGN_DIR"
154+
mkdir -p "$SIGN_DIR"
155+
# Copy only the artifact directories (not marker files)
156+
for name in "${ARTIFACT_NAMES[@]}"; do
157+
if [[ -d "$DOWNLOAD_DIR/$name" ]]; then
158+
cp -R "$DOWNLOAD_DIR/$name" "$SIGN_DIR/$name"
159+
fi
160+
done
161+
}
162+
128163
find_nsis_script() {
129-
find "$WORK_DIR/standalone-win-x64" \
164+
find "$SIGN_DIR/standalone-win-x64" \
130165
-name "installer.nsi" \
131166
-print \
132167
| head -1
@@ -148,12 +183,12 @@ rebuild_windows_installer() {
148183
# The .nsi contains ~60 absolute Windows paths from the CI runner.
149184
# Replace them all with local artifact paths using the helper script.
150185
local artifact_dir
151-
artifact_dir="$(cd "$WORK_DIR/standalone-win-x64" && pwd)"
186+
artifact_dir="$(cd "$SIGN_DIR/standalone-win-x64" && pwd)"
152187
perl "$SCRIPT_DIR/patch-nsis-paths.pl" "$script_path" "$artifact_dir"
153188

154189
# Patch ADDITIONALPLUGINSPATH separately — it is outside the checkout tree.
155190
local plugin_dir
156-
plugin_dir=$(find "$WORK_DIR/standalone-win-x64" -name "nsis_tauri_utils.dll" -exec dirname {} \; | head -1)
191+
plugin_dir=$(find "$SIGN_DIR/standalone-win-x64" -name "nsis_tauri_utils.dll" -exec dirname {} \; | head -1)
157192
if [[ -n "$plugin_dir" ]]; then
158193
local abs_plugin_dir
159194
abs_plugin_dir="$(cd "$plugin_dir" && pwd)"
@@ -204,15 +239,47 @@ find_release_run_id() {
204239
}
205240

206241
# =============================================================================
207-
# Download CI Artifacts
242+
# Download CI Artifacts (per-artifact caching)
208243
# =============================================================================
209244

245+
# Downloads artifacts individually, skipping any already cached.
246+
# Artifacts are stored in $DOWNLOAD_DIR and NEVER modified after download.
247+
download_artifacts_from_run() {
248+
local run_id="$1"
249+
250+
mkdir -p "$DOWNLOAD_DIR"
251+
252+
for name in "${ARTIFACT_NAMES[@]}"; do
253+
if artifact_downloaded "$name"; then
254+
log " $name: already downloaded, skipping"
255+
continue
256+
fi
257+
258+
log " $name: downloading..."
259+
if gh run download "$run_id" \
260+
--repo "$GITHUB_REPO" \
261+
--name "$name" \
262+
--dir "$DOWNLOAD_DIR"; then
263+
touch "$DOWNLOAD_DIR/.downloaded-$name"
264+
log " $name: done"
265+
else
266+
warn " $name: download failed (will retry on next run)"
267+
fi
268+
done
269+
270+
if all_artifacts_downloaded; then
271+
log "All artifacts downloaded to $DOWNLOAD_DIR"
272+
else
273+
error "Some artifacts failed to download. Re-run to retry."
274+
fi
275+
}
276+
210277
download_artifacts() {
211278
local version="$1"
212279
local tag="v$version"
213280

214-
if artifacts_cached "$version"; then
215-
log "Artifacts already downloaded for $version, skipping download"
281+
if all_artifacts_downloaded; then
282+
log "All artifacts already downloaded, skipping"
216283
return
217284
fi
218285

@@ -246,26 +313,16 @@ download_artifacts() {
246313
|| error "Workflow failed. Check: https://github.com/$GITHUB_REPO/actions/runs/$run_id"
247314

248315
log "Workflow completed successfully!"
249-
250-
rm -rf "$WORK_DIR"
251-
mkdir -p "$WORK_DIR"
252-
253316
log "Downloading artifacts..."
254-
gh run download "$run_id" \
255-
--repo "$GITHUB_REPO" \
256-
--dir "$WORK_DIR"
257-
258-
echo "$version" > "$WORK_DIR/.version"
259-
log "Artifacts downloaded to $WORK_DIR"
260-
ls -la "$WORK_DIR"
317+
download_artifacts_from_run "$run_id"
261318
}
262319

263320
resume_download() {
264321
local version="$1"
265322
local tag="v$version"
266323

267-
if artifacts_cached "$version"; then
268-
log "Artifacts already downloaded for $version, skipping download"
324+
if all_artifacts_downloaded; then
325+
log "All artifacts already downloaded, skipping"
269326
return
270327
fi
271328

@@ -288,18 +345,8 @@ resume_download() {
288345
fi
289346

290347
log "Found completed workflow run: $run_id"
291-
292-
rm -rf "$WORK_DIR"
293-
mkdir -p "$WORK_DIR"
294-
295348
log "Downloading artifacts..."
296-
gh run download "$run_id" \
297-
--repo "$GITHUB_REPO" \
298-
--dir "$WORK_DIR"
299-
300-
echo "$version" > "$WORK_DIR/.version"
301-
log "Artifacts downloaded to $WORK_DIR"
302-
ls -la "$WORK_DIR"
349+
download_artifacts_from_run "$run_id"
303350
}
304351

305352
# =============================================================================
@@ -346,7 +393,7 @@ sign_macos() {
346393
log "Starting macOS code signing..."
347394

348395
local app
349-
app=$(find "$WORK_DIR/standalone-mac-aarch64" -name "*.app" -type d | head -1)
396+
app=$(find "$SIGN_DIR/standalone-mac-aarch64" -name "*.app" -type d | head -1)
350397

351398
[[ -n "$app" ]] && sign_macos_app "$app" "aarch64"
352399

@@ -363,7 +410,7 @@ notarize_macos_app() {
363410

364411
log "Notarizing macOS app ($arch_label)..."
365412

366-
local zip_path="$WORK_DIR/notarize-${arch_label}.zip"
413+
local zip_path="$SIGN_DIR/notarize-${arch_label}.zip"
367414

368415
ditto -c -k --keepParent "$app_path" "$zip_path"
369416

@@ -390,7 +437,7 @@ notarize_macos() {
390437
prompt_secret APPLE_SIGN_PASS "Enter Apple ID password (or app-specific password)"
391438

392439
local app
393-
app=$(find "$WORK_DIR/standalone-mac-aarch64" -name "*.app" -type d | head -1)
440+
app=$(find "$SIGN_DIR/standalone-mac-aarch64" -name "*.app" -type d | head -1)
394441

395442
[[ -n "$app" ]] && notarize_macos_app "$app" "aarch64"
396443

@@ -401,10 +448,10 @@ notarize_macos() {
401448

402449
log "Creating $FNAME_MAC_DMG..."
403450
hdiutil create -volname "MouseTerm" -srcfolder "$app" \
404-
-ov -format UDZO "$WORK_DIR/$FNAME_MAC_DMG"
451+
-ov -format UDZO "$SIGN_DIR/$FNAME_MAC_DMG"
405452

406453
log "Creating $FNAME_MAC_UPDATE..."
407-
tar -czf "$WORK_DIR/$FNAME_MAC_UPDATE" -C "$(dirname "$app")" "$app_name"
454+
tar -czf "$SIGN_DIR/$FNAME_MAC_UPDATE" -C "$(dirname "$app")" "$app_name"
408455
fi
409456

410457
log "All macOS notarization and packaging complete"
@@ -422,7 +469,7 @@ sign_windows() {
422469

423470
# Find the inner exe
424471
local exe_path
425-
exe_path=$(find "$WORK_DIR/standalone-win-x64" \( -name "MouseTerm.exe" -o -name "mouseterm.exe" \) -not -name "*setup*" -not -name "*install*" | head -1)
472+
exe_path=$(find "$SIGN_DIR/standalone-win-x64" \( -name "MouseTerm.exe" -o -name "mouseterm.exe" \) -not -name "*setup*" -not -name "*install*" | head -1)
426473
[[ -n "$exe_path" ]] || error "Windows executable not found"
427474

428475
log "Signing inner executable: $exe_path"
@@ -436,7 +483,7 @@ sign_windows() {
436483

437484
# Find the NSIS installer
438485
local installer_path
439-
installer_path=$(find "$WORK_DIR/standalone-win-x64" -name "*setup*.exe" -o -name "*install*.exe" | head -1)
486+
installer_path=$(find "$SIGN_DIR/standalone-win-x64" -name "*setup*.exe" -o -name "*install*.exe" | head -1)
440487

441488
if [[ -n "$installer_path" ]]; then
442489
rebuild_windows_installer "$exe_path" "$installer_path"
@@ -450,7 +497,7 @@ sign_windows() {
450497
"$installer_path"
451498

452499
# Copy with stable filename
453-
cp "$installer_path" "$WORK_DIR/$FNAME_WIN_EXE"
500+
cp "$installer_path" "$SIGN_DIR/$FNAME_WIN_EXE"
454501
fi
455502

456503
log "Windows signing complete"
@@ -472,18 +519,18 @@ sign_updates() {
472519

473520
# Collect and rename update bundles with stable filenames
474521
# macOS .tar.gz (already created by notarize step)
475-
[[ -f "$WORK_DIR/$FNAME_MAC_UPDATE" ]] && cp "$WORK_DIR/$FNAME_MAC_UPDATE" "$release_dir/"
476-
[[ -f "$WORK_DIR/$FNAME_MAC_DMG" ]] && cp "$WORK_DIR/$FNAME_MAC_DMG" "$release_dir/"
522+
[[ -f "$SIGN_DIR/$FNAME_MAC_UPDATE" ]] && cp "$SIGN_DIR/$FNAME_MAC_UPDATE" "$release_dir/"
523+
[[ -f "$SIGN_DIR/$FNAME_MAC_DMG" ]] && cp "$SIGN_DIR/$FNAME_MAC_DMG" "$release_dir/"
477524

478525
# Windows NSIS zip — rebuild with signed exe so Tauri auto-update gets the signed binary
479526
local win_nsis
480-
win_nsis=$(find "$WORK_DIR/standalone-win-x64" -name "*.nsis.zip" | head -1)
527+
win_nsis=$(find "$SIGN_DIR/standalone-win-x64" -name "*.nsis.zip" | head -1)
481528
if [[ -n "$win_nsis" ]]; then
482529
local signed_exe
483-
signed_exe=$(find "$WORK_DIR/standalone-win-x64" -name "MouseTerm.exe" -not -name "*setup*" -not -name "*install*" | head -1)
530+
signed_exe=$(find "$SIGN_DIR/standalone-win-x64" -name "MouseTerm.exe" -not -name "*setup*" -not -name "*install*" | head -1)
484531
if [[ -n "$signed_exe" ]]; then
485532
log "Rebuilding NSIS zip with signed executable..."
486-
local nsis_tmp="$WORK_DIR/nsis-repack"
533+
local nsis_tmp="$SIGN_DIR/nsis-repack"
487534
mkdir -p "$nsis_tmp"
488535
unzip -o "$win_nsis" -d "$nsis_tmp"
489536
# Replace the unsigned exe inside the extracted zip with the signed one
@@ -504,19 +551,19 @@ sign_updates() {
504551
fi
505552

506553
# Windows installer
507-
[[ -f "$WORK_DIR/$FNAME_WIN_EXE" ]] && cp "$WORK_DIR/$FNAME_WIN_EXE" "$release_dir/"
554+
[[ -f "$SIGN_DIR/$FNAME_WIN_EXE" ]] && cp "$SIGN_DIR/$FNAME_WIN_EXE" "$release_dir/"
508555

509556
# Linux AppImage
510557
local linux_appimage
511-
linux_appimage=$(find "$WORK_DIR/standalone-linux-x64" -name "*.AppImage" -not -name "*.tar.gz" | head -1)
558+
linux_appimage=$(find "$SIGN_DIR/standalone-linux-x64" -name "*.AppImage" -not -name "*.tar.gz" | head -1)
512559
[[ -n "$linux_appimage" ]] && cp "$linux_appimage" "$release_dir/$FNAME_LINUX_APPIMAGE"
513560

514561
local linux_update
515-
linux_update=$(find "$WORK_DIR/standalone-linux-x64" -name "*.AppImage.tar.gz" | head -1)
562+
linux_update=$(find "$SIGN_DIR/standalone-linux-x64" -name "*.AppImage.tar.gz" | head -1)
516563
[[ -n "$linux_update" ]] && cp "$linux_update" "$release_dir/$FNAME_LINUX_UPDATE"
517564

518565
local linux_deb
519-
linux_deb=$(find "$WORK_DIR/standalone-linux-x64" -name "*.deb" | head -1)
566+
linux_deb=$(find "$SIGN_DIR/standalone-linux-x64" -name "*.deb" | head -1)
520567
[[ -n "$linux_deb" ]] && cp "$linux_deb" "$release_dir/$FNAME_LINUX_DEB"
521568

522569
# Generate .sig files for update bundles using Tauri CLI
@@ -678,6 +725,7 @@ main() {
678725

679726
check_git_clean
680727
download_artifacts "$version"
728+
prepare_sign_dir
681729
sign_macos
682730
notarize_macos
683731
sign_windows
@@ -689,24 +737,29 @@ main() {
689737
[[ -z "$version" ]] && error "Usage: $(basename "$0") resume <version>"
690738

691739
resume_download "$version"
740+
prepare_sign_dir
692741
sign_macos
693742
notarize_macos
694743
sign_windows
695744
sign_updates "$version"
696745
create_release "$version"
697746
;;
698747
sign-mac)
748+
prepare_sign_dir
699749
sign_macos
700750
;;
701751
notarize)
752+
prepare_sign_dir
702753
notarize_macos
703754
;;
704755
sign-win)
756+
prepare_sign_dir
705757
sign_windows
706758
;;
707759
sign-updates)
708760
local version="${2:-}"
709761
[[ -z "$version" ]] && error "Usage: $(basename "$0") sign-updates <version>"
762+
prepare_sign_dir
710763
sign_updates "$version"
711764
;;
712765
release)

0 commit comments

Comments
 (0)