Skip to content

Commit 2057352

Browse files
cameroncookeclaude
andcommitted
fix(release): Address PR review findings for portable packaging
Align npm tag resolution between dry-run and production publish, enforce\nrelease asset ordering before Homebrew tap updates, and harden portable\npackaging scripts with checksum verification and safer argument parsing.\n\nAlso improve docs command fence parsing, memoize resource-root resolution\nwith test reset hooks, and reduce duplicated AXe helper fixtures in key\npress UI automation tests. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 0695165 commit 2057352

11 files changed

Lines changed: 179 additions & 191 deletions

File tree

.github/workflows/release.yml

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,22 @@ jobs:
7474
# For tag-based releases, package.json was already updated by release script
7575
fi
7676
77+
- name: Resolve npm tag from version
78+
id: resolve_npm_tag
79+
run: |
80+
VERSION="${{ steps.get_version.outputs.VERSION }}"
81+
if [[ "$VERSION" == *"-beta"* ]]; then
82+
NPM_TAG="beta"
83+
elif [[ "$VERSION" == *"-alpha"* ]]; then
84+
NPM_TAG="alpha"
85+
elif [[ "$VERSION" == *"-rc"* ]]; then
86+
NPM_TAG="rc"
87+
else
88+
NPM_TAG="latest"
89+
fi
90+
echo "NPM_TAG=$NPM_TAG" >> "$GITHUB_OUTPUT"
91+
echo "Resolved npm tag: $NPM_TAG"
92+
7793
- name: Generate GitHub release notes (production releases only)
7894
if: github.event_name == 'push'
7995
run: |
@@ -88,13 +104,7 @@ jobs:
88104
if: github.event_name == 'workflow_dispatch'
89105
run: |
90106
echo "🧪 Testing package creation (dry run)"
91-
VERSION="${{ steps.get_version.outputs.VERSION }}"
92-
if [[ "$VERSION" == *"-"* ]]; then
93-
NPM_TAG="next"
94-
else
95-
NPM_TAG="latest"
96-
fi
97-
npm publish --dry-run --access public --tag "$NPM_TAG"
107+
npm publish --dry-run --access public --tag "${{ steps.resolve_npm_tag.outputs.NPM_TAG }}"
98108
99109
- name: Publish to NPM (production releases only)
100110
if: github.event_name == 'push'
@@ -105,17 +115,7 @@ jobs:
105115
echo "✅ xcodebuildmcp@$VERSION already on NPM. Skipping publish."
106116
exit 0
107117
fi
108-
# Determine the appropriate npm tag based on version
109-
if [[ "$VERSION" == *"-beta"* ]]; then
110-
NPM_TAG="beta"
111-
elif [[ "$VERSION" == *"-alpha"* ]]; then
112-
NPM_TAG="alpha"
113-
elif [[ "$VERSION" == *"-rc"* ]]; then
114-
NPM_TAG="rc"
115-
else
116-
# For stable releases, explicitly use latest tag
117-
NPM_TAG="latest"
118-
fi
118+
NPM_TAG="${{ steps.resolve_npm_tag.outputs.NPM_TAG }}"
119119
echo "📦 Publishing to NPM with tag: $NPM_TAG"
120120
npm publish --access public --tag "$NPM_TAG"
121121
@@ -314,7 +314,7 @@ jobs:
314314
315315
publish_portable_assets:
316316
if: github.event_name == 'push'
317-
needs: [release, build_universal_and_verify]
317+
needs: [release, build_and_package_macos, build_universal_and_verify]
318318
runs-on: ubuntu-latest
319319
steps:
320320
- name: Download arm64 artifact
@@ -350,7 +350,7 @@ jobs:
350350
351351
update_homebrew_tap:
352352
if: github.event_name == 'push'
353-
needs: [release, build_and_package_macos]
353+
needs: [release, build_and_package_macos, publish_portable_assets]
354354
runs-on: ubuntu-latest
355355
env:
356356
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
@@ -400,7 +400,7 @@ jobs:
400400
run: |
401401
VERSION="${{ needs.release.outputs.version }}"
402402
DEFAULT_BRANCH="main"
403-
git clone "https://x-access-token:${GH_TOKEN}@github.com/cameroncooke/homebrew-xcodebuildmcp.git" tap-repo
403+
gh repo clone cameroncooke/homebrew-xcodebuildmcp tap-repo
404404
mkdir -p tap-repo/Formula
405405
cp dist/homebrew/Formula/xcodebuildmcp.rb tap-repo/Formula/xcodebuildmcp.rb
406406
cd tap-repo

docs/dev/PROJECT_CONFIG_PLAN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ Responsibilities:
119119

120120
### 7) Runtime overrides
121121
**File:** runtime entrypoints
122-
- Pass overrides into bootstrap/config store, so explicit runtime overrides have highest precedence.
122+
- Pass overrides into bootstrap/config store, so explicit runtime overrides have the highest precedence.
123123

124124
### 8) Documentation updates
125125
- Update `docs/CONFIGURATION.md`, `docs/GETTING_STARTED.md`, `docs/SESSION_DEFAULTS.md`.

scripts/check-docs-cli-commands.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ function extractCommandCandidates(content) {
8585
const lines = content.split(/\r?\n/u);
8686
const candidates = [];
8787
const inlineCodeRegex = /`([^`\n]+)`/g;
88-
const fenceHeaderRegex = /^\s*```([a-z0-9_-]*)\s*$/iu;
88+
const fenceHeaderRegex = /^\s*(?:```|~~~)([a-z0-9_-]*)\s*$/iu;
8989
const codeFenceLanguages = new Set(['', 'bash', 'sh', 'zsh', 'shell', 'console']);
9090

9191
let inFence = false;

scripts/create-homebrew-formula.sh

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,41 @@ Usage:
1515
EOF
1616
}
1717

18+
require_arg_value() {
19+
local flag_name="$1"
20+
local value="${2:-}"
21+
if [[ -z "$value" || "$value" == -* ]]; then
22+
echo "Error: $flag_name requires a value"
23+
usage
24+
exit 1
25+
fi
26+
}
27+
1828
while [[ $# -gt 0 ]]; do
1929
case "$1" in
2030
--version)
21-
VERSION="${2:-}"
31+
require_arg_value "--version" "${2:-}"
32+
VERSION="$2"
2233
shift 2
2334
;;
2435
--arm64-sha)
25-
ARM64_SHA="${2:-}"
36+
require_arg_value "--arm64-sha" "${2:-}"
37+
ARM64_SHA="$2"
2638
shift 2
2739
;;
2840
--x64-sha)
29-
X64_SHA="${2:-}"
41+
require_arg_value "--x64-sha" "${2:-}"
42+
X64_SHA="$2"
3043
shift 2
3144
;;
3245
--base-url)
33-
BASE_URL="${2:-}"
46+
require_arg_value "--base-url" "${2:-}"
47+
BASE_URL="$2"
3448
shift 2
3549
;;
3650
--out)
37-
OUT_PATH="${2:-}"
51+
require_arg_value "--out" "${2:-}"
52+
OUT_PATH="$2"
3853
shift 2
3954
;;
4055
-h|--help)

scripts/package-macos-portable.sh

Lines changed: 70 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -25,30 +25,45 @@ Notes:
2525
EOF
2626
}
2727

28+
require_arg_value() {
29+
local flag_name="$1"
30+
local value="${2:-}"
31+
if [[ -z "$value" || "$value" == -* ]]; then
32+
echo "Missing value for $flag_name"
33+
usage
34+
exit 1
35+
fi
36+
}
37+
2838
while [[ $# -gt 0 ]]; do
2939
case "$1" in
3040
--arch)
31-
ARCH="${2:-}"
41+
require_arg_value "--arch" "${2:-}"
42+
ARCH="$2"
3243
shift 2
3344
;;
3445
--universal)
3546
UNIVERSAL=true
3647
shift
3748
;;
3849
--arm64-root)
39-
ARM64_ROOT="${2:-}"
50+
require_arg_value "--arm64-root" "${2:-}"
51+
ARM64_ROOT="$2"
4052
shift 2
4153
;;
4254
--x64-root)
43-
X64_ROOT="${2:-}"
55+
require_arg_value "--x64-root" "${2:-}"
56+
X64_ROOT="$2"
4457
shift 2
4558
;;
4659
--dist-dir)
47-
DIST_DIR="${2:-}"
60+
require_arg_value "--dist-dir" "${2:-}"
61+
DIST_DIR="$2"
4862
shift 2
4963
;;
5064
--version)
51-
VERSION="${2:-}"
65+
require_arg_value "--version" "${2:-}"
66+
VERSION="$2"
5267
shift 2
5368
;;
5469
-h|--help)
@@ -153,38 +168,55 @@ install_node_runtime_for_arch() {
153168
esac
154169

155170
local archive_name="node-v${node_version}-darwin-${node_arch}.tar.gz"
171+
local checksums_name="SHASUMS256.txt"
156172
local download_url="https://nodejs.org/dist/v${node_version}/${archive_name}"
173+
local checksums_url="https://nodejs.org/dist/v${node_version}/${checksums_name}"
157174
local temp_dir
158175
temp_dir="$(mktemp -d)"
176+
cleanup_temp_dir() {
177+
if [[ -d "$temp_dir" ]]; then
178+
rm -r "$temp_dir"
179+
fi
180+
}
181+
trap cleanup_temp_dir RETURN
159182

160183
curl -fLsS "$download_url" -o "$temp_dir/$archive_name"
184+
curl -fLsS "$checksums_url" -o "$temp_dir/$checksums_name"
185+
186+
local expected_sha
187+
expected_sha="$(awk -v target="$archive_name" '$2 == target {print $1}' "$temp_dir/$checksums_name")"
188+
if [[ -z "$expected_sha" ]]; then
189+
echo "Unable to find checksum for $archive_name in ${checksums_name}"
190+
exit 1
191+
fi
192+
193+
local actual_sha
194+
actual_sha="$(shasum -a 256 "$temp_dir/$archive_name" | awk '{print $1}')"
195+
if [[ "$actual_sha" != "$expected_sha" ]]; then
196+
echo "Node runtime checksum mismatch for $archive_name"
197+
echo "Expected: $expected_sha"
198+
echo "Actual: $actual_sha"
199+
exit 1
200+
fi
201+
161202
tar -xzf "$temp_dir/$archive_name" -C "$temp_dir"
162203

163204
local extracted_node="$temp_dir/node-v${node_version}-darwin-${node_arch}/bin/node"
164205
if [[ ! -x "$extracted_node" ]]; then
165206
echo "Failed to locate extracted Node runtime at $extracted_node"
166-
rm -r "$temp_dir"
167207
exit 1
168208
fi
169209

170210
cp "$extracted_node" "$output_path"
171211
chmod +x "$output_path"
172-
rm -r "$temp_dir"
173212
}
174213

175214
write_wrapper_scripts() {
176215
local root="$1"
177216
local bin_dir="$root/bin"
178217
local libexec_dir="$root/libexec"
179218

180-
cat > "$libexec_dir/xcodebuildmcp" <<'EOF'
181-
#!/usr/bin/env bash
182-
set -euo pipefail
183-
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
184-
exec "$ROOT/node-runtime" "$ROOT/build/cli.js" "$@"
185-
EOF
186-
187-
cat > "$bin_dir/xcodebuildmcp" <<'EOF'
219+
cat > "$libexec_dir/_resolve-resource-root.sh" <<'EOF'
188220
#!/usr/bin/env bash
189221
set -euo pipefail
190222
SOURCE="${BASH_SOURCE[0]}"
@@ -194,29 +226,39 @@ while [[ -L "$SOURCE" ]]; do
194226
[[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
195227
done
196228
SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
197-
RESOURCE_ROOT="$(cd "$SCRIPT_DIR/../libexec" && pwd)"
229+
RESOURCE_ROOT="$(cd "$SCRIPT_DIR" && pwd)"
198230
export XCODEBUILDMCP_RESOURCE_ROOT="$RESOURCE_ROOT"
199231
export DYLD_FRAMEWORK_PATH="$RESOURCE_ROOT/bundled/Frameworks${DYLD_FRAMEWORK_PATH:+:$DYLD_FRAMEWORK_PATH}"
232+
EOF
233+
234+
cat > "$libexec_dir/xcodebuildmcp" <<'EOF'
235+
#!/usr/bin/env bash
236+
set -euo pipefail
237+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
238+
exec "$ROOT/node-runtime" "$ROOT/build/cli.js" "$@"
239+
EOF
240+
241+
cat > "$bin_dir/xcodebuildmcp" <<'EOF'
242+
#!/usr/bin/env bash
243+
set -euo pipefail
244+
SCRIPT_DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
245+
source "$SCRIPT_DIR/../libexec/_resolve-resource-root.sh"
200246
exec "$RESOURCE_ROOT/xcodebuildmcp" "$@"
201247
EOF
202248

203249
cat > "$bin_dir/xcodebuildmcp-doctor" <<'EOF'
204250
#!/usr/bin/env bash
205251
set -euo pipefail
206-
SOURCE="${BASH_SOURCE[0]}"
207-
while [[ -L "$SOURCE" ]]; do
208-
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
209-
SOURCE="$(readlink "$SOURCE")"
210-
[[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
211-
done
212-
SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
213-
RESOURCE_ROOT="$(cd "$SCRIPT_DIR/../libexec" && pwd)"
214-
export XCODEBUILDMCP_RESOURCE_ROOT="$RESOURCE_ROOT"
215-
export DYLD_FRAMEWORK_PATH="$RESOURCE_ROOT/bundled/Frameworks${DYLD_FRAMEWORK_PATH:+:$DYLD_FRAMEWORK_PATH}"
252+
SCRIPT_DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
253+
source "$SCRIPT_DIR/../libexec/_resolve-resource-root.sh"
216254
exec "$RESOURCE_ROOT/xcodebuildmcp" doctor "$@"
217255
EOF
218256

219-
chmod +x "$libexec_dir/xcodebuildmcp" "$bin_dir/xcodebuildmcp" "$bin_dir/xcodebuildmcp-doctor"
257+
chmod +x \
258+
"$libexec_dir/_resolve-resource-root.sh" \
259+
"$libexec_dir/xcodebuildmcp" \
260+
"$bin_dir/xcodebuildmcp" \
261+
"$bin_dir/xcodebuildmcp-doctor"
220262
}
221263

222264
create_tarball_and_checksum() {
@@ -229,7 +271,7 @@ create_tarball_and_checksum() {
229271
cd "$(dirname "$portable_root")"
230272
tar -czf "$tarball_path" "$(basename "$portable_root")"
231273
)
232-
shasum -a 256 "$tarball_path" > "$checksum_path"
274+
shasum -a 256 "$tarball_path" | awk '{print $1}' > "$checksum_path"
233275
echo "Created artifact: $tarball_path"
234276
echo "Created checksum: $checksum_path"
235277
}

scripts/verify-portable-install.sh

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,20 @@ while [[ $# -gt 0 ]]; do
3636
esac
3737
done
3838

39+
cleanup() {
40+
if [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]]; then
41+
rm -r "$TEMP_DIR"
42+
fi
43+
}
44+
3945
if [[ -z "$ARCHIVE_PATH" && -z "$PORTABLE_ROOT" ]]; then
4046
usage
4147
exit 1
4248
fi
4349

4450
if [[ -n "$ARCHIVE_PATH" ]]; then
4551
TEMP_DIR="$(mktemp -d)"
52+
trap cleanup EXIT
4653
tar -xzf "$ARCHIVE_PATH" -C "$TEMP_DIR"
4754
extracted_count="$(find "$TEMP_DIR" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')"
4855
if [[ "$extracted_count" -ne 1 ]]; then
@@ -52,13 +59,6 @@ if [[ -n "$ARCHIVE_PATH" ]]; then
5259
PORTABLE_ROOT="$(find "$TEMP_DIR" -mindepth 1 -maxdepth 1 -type d | head -1)"
5360
fi
5461

55-
cleanup() {
56-
if [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]]; then
57-
rm -r "$TEMP_DIR"
58-
fi
59-
}
60-
trap cleanup EXIT
61-
6262
if [[ ! -d "$PORTABLE_ROOT/bin" || ! -d "$PORTABLE_ROOT/libexec" ]]; then
6363
echo "Portable layout missing bin/ or libexec/: $PORTABLE_ROOT"
6464
exit 1
@@ -105,8 +105,8 @@ if [[ -z "$RUNTIME_ARCHS" ]]; then
105105
fi
106106

107107
NORMALIZED_HOST_ARCH="$HOST_ARCH"
108-
if [[ "$HOST_ARCH" == "x86_64" ]]; then
109-
NORMALIZED_HOST_ARCH="x86_64"
108+
if [[ "$HOST_ARCH" == "aarch64" ]]; then
109+
NORMALIZED_HOST_ARCH="arm64"
110110
fi
111111

112112
CAN_EXECUTE="false"

src/core/__tests__/resource-root.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
getBundledFrameworksDir,
88
getManifestsDir,
99
getResourceRoot,
10+
resetResourceRootCacheForTests,
1011
} from '../resource-root.ts';
1112

1213
describe('resource-root', () => {
@@ -18,6 +19,7 @@ describe('resource-root', () => {
1819
originalExecPath = process.execPath;
1920
originalResourceRoot = process.env.XCODEBUILDMCP_RESOURCE_ROOT;
2021
tempDir = mkdtempSync(join(tmpdir(), 'xbmcp-resource-root-'));
22+
resetResourceRootCacheForTests();
2123
});
2224

2325
afterEach(() => {
@@ -28,6 +30,7 @@ describe('resource-root', () => {
2830
process.env.XCODEBUILDMCP_RESOURCE_ROOT = originalResourceRoot;
2931
}
3032
rmSync(tempDir, { recursive: true, force: true });
33+
resetResourceRootCacheForTests();
3134
});
3235

3336
it('uses XCODEBUILDMCP_RESOURCE_ROOT when set', () => {

0 commit comments

Comments
 (0)