395 lines
12 KiB
Bash
Executable File
395 lines
12 KiB
Bash
Executable File
#!/bin/sh
|
|
set -eu
|
|
|
|
# post-goreleaser.sh — Post-build packaging for npm and Homebrew.
|
|
#
|
|
# Run after `goreleaser release` or `goreleaser release --snapshot` to stage
|
|
# the npm package and render the Homebrew formula from goreleaser's dist/ output.
|
|
|
|
ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)"
|
|
DIST_DIR="${DWS_PACKAGE_DIST_DIR:-$ROOT/dist}"
|
|
PACKAGE_VERSION="${DWS_PACKAGE_VERSION:-}"
|
|
RELEASE_BASE_URL="${DWS_RELEASE_BASE_URL:-}"
|
|
APPLE_CERTIFICATE_P12="${DWS_APPLE_CERTIFICATE_P12:-}"
|
|
APPLE_CERTIFICATE_PASSWORD_FILE="${DWS_APPLE_CERTIFICATE_PASSWORD_FILE:-}"
|
|
REQUIRE_DEVELOPER_ID_SIGNING="${DWS_REQUIRE_DEVELOPER_ID_SIGNING:-false}"
|
|
|
|
export LANG=C
|
|
export LC_ALL=C
|
|
export LC_CTYPE=C
|
|
|
|
say() {
|
|
printf '%s\n' "$*"
|
|
}
|
|
|
|
err() {
|
|
printf 'error: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
sha256_file() {
|
|
target="$1"
|
|
if command -v sha256sum >/dev/null 2>&1; then
|
|
sha256sum "$target" | awk '{print $1}'
|
|
return
|
|
fi
|
|
shasum -a 256 "$target" | awk '{print $1}'
|
|
}
|
|
|
|
detect_os() {
|
|
os="$(uname -s)"
|
|
case "$os" in
|
|
Linux*) printf 'linux\n' ;;
|
|
Darwin*) printf 'darwin\n' ;;
|
|
MINGW*|MSYS*|CYGWIN*) printf 'windows\n' ;;
|
|
*) err "unsupported OS: $os" ;;
|
|
esac
|
|
}
|
|
|
|
detect_arch() {
|
|
arch="$(uname -m)"
|
|
case "$arch" in
|
|
x86_64|amd64) printf 'amd64\n' ;;
|
|
arm64|aarch64) printf 'arm64\n' ;;
|
|
*) err "unsupported architecture: $arch" ;;
|
|
esac
|
|
}
|
|
|
|
resolve_version() {
|
|
# Priority 1: Use DWS_PACKAGE_VERSION environment variable (set by CI)
|
|
if [ -n "$PACKAGE_VERSION" ]; then
|
|
# Strip leading 'v' if present for semver compatibility
|
|
printf '%s\n' "$PACKAGE_VERSION" | sed 's/^v//'
|
|
return
|
|
fi
|
|
|
|
# Priority 2: Get version from git tag (for local snapshot builds with tag)
|
|
if git describe --tags --exact-match HEAD >/dev/null 2>&1; then
|
|
git describe --tags --exact-match HEAD | sed 's/^v//'
|
|
return
|
|
fi
|
|
|
|
# Priority 3: Read from version.go (for local development without tag)
|
|
version_line="$(sed -n 's/^var version = "v\{0,1\}\([^"]*\)".*/\1/p' "$ROOT/internal/app/version.go" | head -1)"
|
|
if [ -z "$version_line" ] || [ "$version_line" = "dev" ]; then
|
|
err "could not resolve package version - set DWS_PACKAGE_VERSION or create a git tag"
|
|
fi
|
|
printf '%s\n' "$version_line"
|
|
}
|
|
|
|
resolve_release_base_url() {
|
|
version="$1"
|
|
if [ -n "$RELEASE_BASE_URL" ]; then
|
|
printf '%s\n' "${RELEASE_BASE_URL%/}"
|
|
return
|
|
fi
|
|
printf 'https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli/releases/download/v%s\n' "$version"
|
|
}
|
|
|
|
# ---------- npm staging ----------
|
|
|
|
stage_npm_package() {
|
|
version="$1"
|
|
DWS_PACKAGE_SOURCE_ROOT="$ROOT" \
|
|
DWS_PACKAGE_DIST_DIR="$DIST_DIR" \
|
|
"$ROOT/scripts/release/stage-npm-package.sh" "$version"
|
|
}
|
|
|
|
# ---------- Homebrew formula staging ----------
|
|
|
|
render_homebrew_formula() {
|
|
class_name="$1"
|
|
archive_url="$2"
|
|
skills_url="$3"
|
|
archive_sha="$4"
|
|
skills_sha="$5"
|
|
keg_only_line="$6"
|
|
output_path="$7"
|
|
|
|
sed \
|
|
-e "s|__CLASS_NAME__|$class_name|g" \
|
|
-e "s|__ARCHIVE_URL__|$archive_url|g" \
|
|
-e "s|__ARCHIVE_SHA256__|$archive_sha|g" \
|
|
-e "s|__SKILLS_URL__|$skills_url|g" \
|
|
-e "s|__SKILLS_SHA256__|$skills_sha|g" \
|
|
-e "s|__KEG_ONLY_LINE__|$keg_only_line|g" \
|
|
"$ROOT/build/homebrew.rb.tmpl" > "$output_path"
|
|
}
|
|
|
|
render_homebrew_release_formula() {
|
|
class_name="$1"
|
|
version="$2"
|
|
release_url_base="$3"
|
|
darwin_amd64_sha="$4"
|
|
darwin_arm64_sha="$5"
|
|
linux_amd64_sha="$6"
|
|
linux_arm64_sha="$7"
|
|
skills_sha="$8"
|
|
keg_only_line="$9"
|
|
channel_caveat="${10}"
|
|
output_path="${11}"
|
|
description="Automate DingTalk workspace tasks from the terminal"
|
|
if [ "$class_name" = "DingtalkWorkspaceCliBeta" ]; then
|
|
description="$description (beta channel)"
|
|
fi
|
|
|
|
sed \
|
|
-e "s|__CLASS_NAME__|$class_name|g" \
|
|
-e "s|__DESCRIPTION__|$description|g" \
|
|
-e "s|__VERSION__|$version|g" \
|
|
-e "s|__DARWIN_AMD64_URL__|$release_url_base/dws-darwin-amd64.tar.gz|g" \
|
|
-e "s|__DARWIN_AMD64_SHA256__|$darwin_amd64_sha|g" \
|
|
-e "s|__DARWIN_ARM64_URL__|$release_url_base/dws-darwin-arm64.tar.gz|g" \
|
|
-e "s|__DARWIN_ARM64_SHA256__|$darwin_arm64_sha|g" \
|
|
-e "s|__LINUX_AMD64_URL__|$release_url_base/dws-linux-amd64.tar.gz|g" \
|
|
-e "s|__LINUX_AMD64_SHA256__|$linux_amd64_sha|g" \
|
|
-e "s|__LINUX_ARM64_URL__|$release_url_base/dws-linux-arm64.tar.gz|g" \
|
|
-e "s|__LINUX_ARM64_SHA256__|$linux_arm64_sha|g" \
|
|
-e "s|__SKILLS_URL__|$release_url_base/dws-skills.zip|g" \
|
|
-e "s|__SKILLS_SHA256__|$skills_sha|g" \
|
|
-e "s|__KEG_ONLY_LINE__|$keg_only_line|g" \
|
|
-e "s|__CHANNEL_CAVEAT__|$channel_caveat|g" \
|
|
"$ROOT/build/homebrew-release.rb.tmpl" > "$output_path"
|
|
}
|
|
|
|
stage_homebrew_formula() {
|
|
version="$1"
|
|
host_os="$(detect_os)"
|
|
host_arch="$(detect_arch)"
|
|
archive_ext=".tar.gz"
|
|
formula_dir="$DIST_DIR/homebrew"
|
|
archive_path="$DIST_DIR/dws-${host_os}-${host_arch}${archive_ext}"
|
|
release_url_base="$(resolve_release_base_url "$version")"
|
|
archive_sha="$(sha256_file "$archive_path")"
|
|
skills_sha="$(sha256_file "$DIST_DIR/dws-skills.zip")"
|
|
|
|
darwin_amd64="$DIST_DIR/dws-darwin-amd64.tar.gz"
|
|
darwin_arm64="$DIST_DIR/dws-darwin-arm64.tar.gz"
|
|
linux_amd64="$DIST_DIR/dws-linux-amd64.tar.gz"
|
|
linux_arm64="$DIST_DIR/dws-linux-arm64.tar.gz"
|
|
|
|
mkdir -p "$formula_dir"
|
|
|
|
if [ ! -f "$archive_path" ]; then
|
|
err "host archive missing for homebrew formula: $archive_path"
|
|
fi
|
|
for release_archive in "$darwin_amd64" "$darwin_arm64" "$linux_amd64" "$linux_arm64"; do
|
|
if [ ! -f "$release_archive" ]; then
|
|
err "release archive missing for Homebrew formula: $release_archive"
|
|
fi
|
|
done
|
|
|
|
render_homebrew_formula \
|
|
"DingtalkWorkspaceCliLocal" \
|
|
"file://$archive_path" \
|
|
"file://$DIST_DIR/dws-skills.zip" \
|
|
"$archive_sha" \
|
|
"$skills_sha" \
|
|
' keg_only "Local verification formula to avoid linking conflicts"' \
|
|
"$formula_dir/dingtalk-workspace-cli-local.rb"
|
|
|
|
formula_class="DingtalkWorkspaceCli"
|
|
formula_path="$formula_dir/dingtalk-workspace-cli.rb"
|
|
keg_only_line=""
|
|
channel_caveat=""
|
|
case "$version" in
|
|
*-*)
|
|
formula_class="DingtalkWorkspaceCliBeta"
|
|
formula_path="$formula_dir/dingtalk-workspace-cli-beta.rb"
|
|
keg_only_line=' keg_only "it is the beta channel and conflicts with dingtalk-workspace-cli"'
|
|
channel_caveat=' This beta is keg-only. Add #{opt_bin} to PATH to use its `dws` binary.'
|
|
;;
|
|
esac
|
|
|
|
render_homebrew_release_formula \
|
|
"$formula_class" \
|
|
"$version" \
|
|
"$release_url_base" \
|
|
"$(sha256_file "$darwin_amd64")" \
|
|
"$(sha256_file "$darwin_arm64")" \
|
|
"$(sha256_file "$linux_amd64")" \
|
|
"$(sha256_file "$linux_arm64")" \
|
|
"$skills_sha" \
|
|
"$keg_only_line" \
|
|
"$channel_caveat" \
|
|
"$formula_path"
|
|
}
|
|
|
|
# ---------- skills zip ----------
|
|
|
|
create_skills_zip() {
|
|
skills_zip="$DIST_DIR/dws-skills.zip"
|
|
rm -f "$skills_zip"
|
|
|
|
staging="$(mktemp -d)"
|
|
# Layout inside dws-skills.zip:
|
|
# <root>/SKILL.md + references/ + scripts/ ← copy of mono/, kept at root
|
|
# for backward compatibility
|
|
# with older install scripts
|
|
# that look for SKILL.md at
|
|
# the zip root.
|
|
# <root>/mono/ ← explicit mono source tree
|
|
# <root>/multi/ ← multi source tree (one
|
|
# subdir per product skill)
|
|
cp -R "$ROOT/skills/mono/." "$staging/"
|
|
mkdir -p "$staging/mono"
|
|
cp -R "$ROOT/skills/mono/." "$staging/mono/"
|
|
mkdir -p "$staging/multi"
|
|
if [ -d "$ROOT/skills/multi" ]; then
|
|
cp -R "$ROOT/skills/multi/." "$staging/multi/"
|
|
fi
|
|
|
|
(
|
|
cd "$staging"
|
|
env -u LC_ALL -u LC_CTYPE LANG=C LC_ALL=C LC_CTYPE=C zip -qr "$skills_zip" .
|
|
)
|
|
|
|
rm -rf "$staging"
|
|
}
|
|
|
|
# ---------- darwin signing ----------
|
|
#
|
|
# Unsigned arm64 binaries are SIGKILL'd by amfid on Apple Silicon (macOS 11+).
|
|
# Official releases use an Apple Developer ID certificate loaded from GitHub
|
|
# Secrets. Fork/local builds retain ad-hoc signing so they remain runnable.
|
|
# We unpack each dws-darwin-*.tar.gz, sign the dws binary, repack deterministically,
|
|
# and rewrite the corresponding line in checksums.txt.
|
|
|
|
configure_darwin_signing() {
|
|
case "$REQUIRE_DEVELOPER_ID_SIGNING" in
|
|
1|true|yes) require_developer_id=1 ;;
|
|
0|false|no|"") require_developer_id=0 ;;
|
|
*) err "invalid DWS_REQUIRE_DEVELOPER_ID_SIGNING value: $REQUIRE_DEVELOPER_ID_SIGNING" ;;
|
|
esac
|
|
|
|
if [ -n "$APPLE_CERTIFICATE_P12" ] || [ -n "$APPLE_CERTIFICATE_PASSWORD_FILE" ]; then
|
|
[ -n "$APPLE_CERTIFICATE_P12" ] || err "DWS_APPLE_CERTIFICATE_P12 is required when Developer ID signing is configured"
|
|
[ -n "$APPLE_CERTIFICATE_PASSWORD_FILE" ] || err "DWS_APPLE_CERTIFICATE_PASSWORD_FILE is required when Developer ID signing is configured"
|
|
[ -f "$APPLE_CERTIFICATE_P12" ] || err "Developer ID P12 file not found: $APPLE_CERTIFICATE_P12"
|
|
[ -f "$APPLE_CERTIFICATE_PASSWORD_FILE" ] || err "Developer ID password file not found: $APPLE_CERTIFICATE_PASSWORD_FILE"
|
|
command -v rcodesign >/dev/null 2>&1 || err "rcodesign is required for Developer ID signing"
|
|
DARWIN_SIGNING_MODE="developer-id"
|
|
return
|
|
fi
|
|
|
|
if [ "$require_developer_id" -eq 1 ]; then
|
|
err "Developer ID signing is required but DWS_APPLE_CERTIFICATE_P12 and DWS_APPLE_CERTIFICATE_PASSWORD_FILE are not configured"
|
|
fi
|
|
DARWIN_SIGNING_MODE="ad-hoc"
|
|
}
|
|
|
|
sign_one_darwin_binary() {
|
|
bin="$1"
|
|
if [ "$DARWIN_SIGNING_MODE" = "developer-id" ]; then
|
|
rcodesign sign \
|
|
--p12-file "$APPLE_CERTIFICATE_P12" \
|
|
--p12-password-file "$APPLE_CERTIFICATE_PASSWORD_FILE" \
|
|
--for-notarization \
|
|
"$bin"
|
|
return
|
|
fi
|
|
if command -v codesign >/dev/null 2>&1; then
|
|
codesign --force --sign - "$bin"
|
|
return
|
|
fi
|
|
if command -v rcodesign >/dev/null 2>&1; then
|
|
rcodesign sign "$bin"
|
|
return
|
|
fi
|
|
err "neither codesign nor rcodesign found — install rcodesign (cargo install apple-codesign) to ad-hoc sign darwin builds"
|
|
}
|
|
|
|
update_checksum_entry() {
|
|
filename="$1"
|
|
new_sha="$2"
|
|
checksum_path="$DIST_DIR/checksums.txt"
|
|
[ -f "$checksum_path" ] || return 0
|
|
tmp="$(mktemp)"
|
|
grep -v " ${filename}\$" "$checksum_path" > "$tmp" 2>/dev/null || true
|
|
printf '%s %s\n' "$new_sha" "$filename" >> "$tmp"
|
|
mv "$tmp" "$checksum_path"
|
|
}
|
|
|
|
sign_darwin_archives() {
|
|
work="$(mktemp -d)"
|
|
found_any=0
|
|
for archive in "$DIST_DIR"/dws-darwin-*.tar.gz; do
|
|
[ -f "$archive" ] || continue
|
|
found_any=1
|
|
name="$(basename "$archive")"
|
|
say " signing $name"
|
|
|
|
stage="$work/${name%.tar.gz}"
|
|
rm -rf "$stage"
|
|
mkdir -p "$stage"
|
|
tar -xzf "$archive" -C "$stage"
|
|
|
|
bin="$stage/dws"
|
|
if [ ! -f "$bin" ]; then
|
|
err "dws binary not found inside $name after extraction"
|
|
fi
|
|
sign_one_darwin_binary "$bin"
|
|
|
|
# Repack deterministically when GNU tar is available. macOS ships BSD tar,
|
|
# which lacks --sort/--mtime/--owner; fall back to a portable archive there
|
|
# so local package builds still complete.
|
|
if command -v gtar >/dev/null 2>&1; then
|
|
(
|
|
cd "$stage" \
|
|
&& gtar --sort=name --owner=0 --group=0 --numeric-owner \
|
|
--mtime='2020-01-01 00:00Z' \
|
|
-czf "$archive.new" .
|
|
)
|
|
else
|
|
(
|
|
cd "$stage" \
|
|
&& COPYFILE_DISABLE=1 tar -czf "$archive.new" .
|
|
)
|
|
fi
|
|
mv "$archive.new" "$archive"
|
|
|
|
update_checksum_entry "$name" "$(sha256_file "$archive")"
|
|
done
|
|
rm -rf "$work"
|
|
if [ "$found_any" -eq 0 ]; then
|
|
say " (no darwin archives found, skipping)"
|
|
fi
|
|
}
|
|
|
|
write_checksums() {
|
|
# Keep this idempotent: workflow retries must not leave duplicate entries.
|
|
if [ -f "$DIST_DIR/dws-skills.zip" ]; then
|
|
update_checksum_entry "dws-skills.zip" "$(sha256_file "$DIST_DIR/dws-skills.zip")"
|
|
fi
|
|
}
|
|
|
|
# ---------- main ----------
|
|
|
|
version="$(resolve_version)"
|
|
configure_darwin_signing
|
|
|
|
if [ "$DARWIN_SIGNING_MODE" = "developer-id" ]; then
|
|
say "==> Developer ID signing darwin binaries"
|
|
else
|
|
say "==> Ad-hoc signing darwin binaries"
|
|
fi
|
|
sign_darwin_archives
|
|
|
|
say "==> Creating skills zip"
|
|
create_skills_zip
|
|
|
|
say "==> Updating checksums"
|
|
write_checksums
|
|
|
|
say "==> Staging npm package (v$version)"
|
|
stage_npm_package "$version"
|
|
|
|
say "==> Rendering Homebrew formula (v$version)"
|
|
stage_homebrew_formula "$version"
|
|
|
|
say ""
|
|
say "Post-goreleaser packaging complete:"
|
|
say " skills: $DIST_DIR/dws-skills.zip"
|
|
say " npm: $DIST_DIR/npm/dingtalk-workspace-cli/"
|
|
say " homebrew: $DIST_DIR/homebrew/"
|