Retire legacy implementations and flatten managed layout
This commit is contained in:
@@ -1,175 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""asc_api.py — minimal App Store Connect API helper for ad-hoc device registration.
|
||||
|
||||
Used by scripts/dist-ios-adhoc.sh to register friends' device UDIDs before building an
|
||||
ad-hoc IPA. Talks to the App Store Connect API directly: signs an ES256 JWT with your
|
||||
Team Key .p8 (via the `cryptography` package — no PyJWT/requests needed) and calls the
|
||||
REST endpoints with urllib from the standard library.
|
||||
|
||||
Subcommands:
|
||||
register register one device UDID (idempotent — an already-registered UDID is OK)
|
||||
list list all registered devices (and the count, against the 100/year cap)
|
||||
|
||||
Auth is the same for both, supplied via flags (the wrapper script passes them from the
|
||||
ASC_KEY_ID / ASC_ISSUER_ID / ASC_KEY_PATH env vars):
|
||||
--key-id the Key ID of the App Store Connect API key
|
||||
--issuer-id the Issuer ID (Users and Access -> Integrations)
|
||||
--key path to the AuthKey_XXXXXXXXXX.p8 file
|
||||
|
||||
Examples:
|
||||
python3 scripts/asc_api.py list \
|
||||
--key-id ABC123 --issuer-id 11111111-2222-... --key ~/.appstoreconnect/AuthKey_ABC123.p8
|
||||
python3 scripts/asc_api.py register --udid 00008110-0011... --name "My iPhone" \
|
||||
--key-id ABC123 --issuer-id 11111111-2222-... --key ~/.appstoreconnect/AuthKey_ABC123.p8
|
||||
|
||||
The .p8 is created at App Store Connect -> Users and Access -> Integrations ->
|
||||
App Store Connect API -> Team Keys, with Admin or App Manager access. Keep it out of the
|
||||
repo.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec, utils
|
||||
|
||||
API_BASE = "https://api.appstoreconnect.apple.com"
|
||||
|
||||
|
||||
def _b64url(data: bytes) -> str:
|
||||
"""base64url without padding, as JWT requires."""
|
||||
return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
|
||||
|
||||
|
||||
def make_jwt(key_id: str, issuer_id: str, key_path: str) -> str:
|
||||
"""Build a short-lived ES256 JWT for the App Store Connect API."""
|
||||
with open(key_path, "rb") as fh:
|
||||
private_key = serialization.load_pem_private_key(fh.read(), password=None)
|
||||
if not isinstance(private_key, ec.EllipticCurvePrivateKey):
|
||||
sys.exit(f"error: {key_path} is not an EC private key (.p8 from App Store Connect)")
|
||||
|
||||
now = int(time.time())
|
||||
header = {"alg": "ES256", "kid": key_id, "typ": "JWT"}
|
||||
# exp must be <= 20 minutes out; 10 minutes is comfortable.
|
||||
payload = {"iss": issuer_id, "iat": now, "exp": now + 600, "aud": "appstoreconnect-v1"}
|
||||
|
||||
signing_input = f"{_b64url(json.dumps(header).encode())}.{_b64url(json.dumps(payload).encode())}"
|
||||
der_sig = private_key.sign(signing_input.encode("ascii"), ec.ECDSA(hashes.SHA256()))
|
||||
# JWS wants raw r||s (two 32-byte big-endian ints), not the ASN.1/DER openssl emits.
|
||||
r, s = utils.decode_dss_signature(der_sig)
|
||||
raw_sig = r.to_bytes(32, "big") + s.to_bytes(32, "big")
|
||||
return f"{signing_input}.{_b64url(raw_sig)}"
|
||||
|
||||
|
||||
def _request(method: str, path: str, token: str, body: dict | None = None):
|
||||
"""Perform an authenticated API call. Returns (status_code, parsed_json|None)."""
|
||||
url = path if path.startswith("http") else f"{API_BASE}{path}"
|
||||
data = json.dumps(body).encode() if body is not None else None
|
||||
req = urllib.request.Request(url, data=data, method=method)
|
||||
req.add_header("Authorization", f"Bearer {token}")
|
||||
if data is not None:
|
||||
req.add_header("Content-Type", "application/json")
|
||||
try:
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
raw = resp.read()
|
||||
return resp.status, (json.loads(raw) if raw else None)
|
||||
except urllib.error.HTTPError as exc:
|
||||
raw = exc.read()
|
||||
try:
|
||||
parsed = json.loads(raw) if raw else None
|
||||
except json.JSONDecodeError:
|
||||
parsed = {"_raw": raw.decode("utf-8", "replace")}
|
||||
return exc.code, parsed
|
||||
|
||||
|
||||
def _errors_text(payload) -> str:
|
||||
if isinstance(payload, dict) and payload.get("errors"):
|
||||
return "; ".join(
|
||||
f"{e.get('title', '')}: {e.get('detail', '')}".strip(": ")
|
||||
for e in payload["errors"]
|
||||
)
|
||||
return json.dumps(payload)
|
||||
|
||||
|
||||
def cmd_register(args, token: str) -> int:
|
||||
body = {
|
||||
"data": {
|
||||
"type": "devices",
|
||||
"attributes": {
|
||||
"name": args.name or args.udid,
|
||||
"platform": "IOS",
|
||||
"udid": args.udid,
|
||||
},
|
||||
}
|
||||
}
|
||||
status, payload = _request("POST", "/v1/devices", token, body)
|
||||
if status in (200, 201):
|
||||
print(f" registered: {args.udid} ({args.name or args.udid})")
|
||||
return 0
|
||||
|
||||
# A UDID that already exists comes back as a 409 conflict, or a 422 with an error
|
||||
# detail mentioning the device already exists. Either way it's fine — idempotent.
|
||||
text = _errors_text(payload)
|
||||
if status == 409 or "already exist" in text.lower() or "already been taken" in text.lower():
|
||||
print(f" already registered: {args.udid}")
|
||||
return 0
|
||||
|
||||
print(f"error: failed to register {args.udid} (HTTP {status}): {text}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
def cmd_list(args, token: str) -> int:
|
||||
path = "/v1/devices?limit=200&sort=name"
|
||||
rows = []
|
||||
while path:
|
||||
status, payload = _request("GET", path, token)
|
||||
if status != 200:
|
||||
print(f"error: list failed (HTTP {status}): {_errors_text(payload)}", file=sys.stderr)
|
||||
return 1
|
||||
for d in payload.get("data", []):
|
||||
a = d.get("attributes", {})
|
||||
rows.append((a.get("platform", "?"), a.get("status", "?"),
|
||||
a.get("udid", "?"), a.get("name", "")))
|
||||
path = (payload.get("links") or {}).get("next")
|
||||
|
||||
ios = [r for r in rows if r[0] == "IOS"]
|
||||
for platform, dev_status, udid, name in rows:
|
||||
print(f" [{platform:7}] {dev_status:8} {udid} {name}")
|
||||
print(f"\n {len(rows)} device(s) total, {len(ios)} iOS (cap is 100 iOS/membership year)")
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
def add_auth(p):
|
||||
p.add_argument("--key-id", required=True)
|
||||
p.add_argument("--issuer-id", required=True)
|
||||
p.add_argument("--key", required=True, help="path to AuthKey_*.p8")
|
||||
|
||||
p_reg = sub.add_parser("register", help="register one device UDID (idempotent)")
|
||||
add_auth(p_reg)
|
||||
p_reg.add_argument("--udid", required=True)
|
||||
p_reg.add_argument("--name", default=None)
|
||||
|
||||
p_list = sub.add_parser("list", help="list registered devices")
|
||||
add_auth(p_list)
|
||||
|
||||
args = parser.parse_args()
|
||||
token = make_jwt(args.key_id, args.issuer_id, args.key)
|
||||
if args.command == "register":
|
||||
return cmd_register(args, token)
|
||||
if args.command == "list":
|
||||
return cmd_list(args, token)
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# build-all.sh — build every artifact this host can produce and stage it under
|
||||
# dist/. Convenience wrapper over the per-artifact scripts in scripts/.
|
||||
#
|
||||
# On macOS: server + libs (macOS slice + xcframework) + macos-client (.app)
|
||||
# optionally: iOS client for simulator (pass --ios-client)
|
||||
# On Windows: server + libs (windows DLL) + windows-client (.exe folder)
|
||||
# On Linux: server only (no native client presets target Linux)
|
||||
#
|
||||
# Usage:
|
||||
# scripts/build-all.sh # build everything host can, into ./dist
|
||||
# scripts/build-all.sh --dist /out
|
||||
# scripts/build-all.sh --no-configure # passed through to each step
|
||||
# scripts/build-all.sh --skip-client # skip the GUI client(s)
|
||||
# scripts/build-all.sh --skip-libs # skip the library artifacts
|
||||
# scripts/build-all.sh --skip-server # skip the server
|
||||
# scripts/build-all.sh --ios-client # also build iOS simulator client (macOS only)
|
||||
# scripts/build-all.sh -h|--help
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VC_SCRIPT_NAME="build-all"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
PASS_ARGS=()
|
||||
SKIP_CLIENT=false
|
||||
SKIP_LIBS=false
|
||||
SKIP_SERVER=false
|
||||
BUILD_IOS_CLIENT=false
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dist) PASS_ARGS+=( --dist "$2" ); vc_set_dist "$2"; shift 2 ;;
|
||||
--no-configure) PASS_ARGS+=( --no-configure ); shift ;;
|
||||
--skip-client) SKIP_CLIENT=true; shift ;;
|
||||
--skip-libs) SKIP_LIBS=true; shift ;;
|
||||
--skip-server) SKIP_SERVER=true; shift ;;
|
||||
--ios-client) BUILD_IOS_CLIENT=true; shift ;;
|
||||
-h|--help) vc_print_help "$0"; exit 0 ;;
|
||||
*) vc_die "unknown arg: $1 (try --help)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
HOST="$(vc_host_os)"
|
||||
vc_log "host: $HOST dist: $VC_DIST_DIR"
|
||||
|
||||
run() {
|
||||
local name="$1"; shift
|
||||
vc_step "→ $name $*"
|
||||
"$SCRIPT_DIR/$name" "$@" "${PASS_ARGS[@]}"
|
||||
}
|
||||
|
||||
if ! $SKIP_SERVER; then
|
||||
run build-server.sh
|
||||
fi
|
||||
|
||||
if ! $SKIP_LIBS; then
|
||||
if [[ "$HOST" == "macos" ]]; then
|
||||
run build-libs.sh --platform macos
|
||||
elif [[ "$HOST" == "windows" ]]; then
|
||||
run build-libs.sh --platform windows
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! $SKIP_CLIENT; then
|
||||
if [[ "$HOST" == "macos" ]]; then
|
||||
run build-macos-client.sh
|
||||
$BUILD_IOS_CLIENT && run build-ios-client.sh
|
||||
elif [[ "$HOST" == "windows" ]]; then
|
||||
run build-windows-client.sh
|
||||
fi
|
||||
fi
|
||||
|
||||
vc_ok "all done -> $VC_DIST_DIR"
|
||||
vc_log "contents:"
|
||||
( cd "$VC_DIST_DIR" && find . -maxdepth 3 | sort | sed 's/^/ /' ) || true
|
||||
@@ -1,101 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# build-ios-client.sh — build the iOS SwiftUI client (VoiceCatiOS.app) for the
|
||||
# iOS simulator and stage it into dist/ios-client/.
|
||||
#
|
||||
# Two steps:
|
||||
# 1. Build VoiceCatCore.xcframework with all 3 slices (macOS + iOS device +
|
||||
# iOS simulator) via clients/apple/scripts/build-xcframework.sh --all.
|
||||
# The XCFramework must include the ios-arm64-simulator slice so the Swift
|
||||
# Package binary target resolves when xcodebuild compiles the iOS app.
|
||||
# 2. xcodebuild the VoiceCatiOS target against the iOS Simulator SDK, with
|
||||
# SYMROOT and OBJROOT pointed at the same directory so the Swift Package
|
||||
# and the app target find each other's build products.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/build-ios-client.sh # Debug sim build into ./dist
|
||||
# scripts/build-ios-client.sh --dist /out
|
||||
# scripts/build-ios-client.sh --no-configure # skip cmake configure (xcframework step)
|
||||
# scripts/build-ios-client.sh --config Debug # Xcode config (default Debug)
|
||||
# scripts/build-ios-client.sh -h|--help
|
||||
#
|
||||
# Output:
|
||||
# clients/apple/iOS/build/Debug-iphonesimulator/VoiceCatiOS.app
|
||||
# dist/ios-client/VoiceCatiOS.app
|
||||
#
|
||||
# To install and launch on a simulator after building, use:
|
||||
# scripts/run-ios-simulator.sh
|
||||
#
|
||||
# macOS only. Requires VCPKG_ROOT and Xcode (with iOS Simulator runtime installed).
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VC_SCRIPT_NAME="build-ios-client"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
DO_CONFIGURE=true
|
||||
XCODE_CONFIG="Debug"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dist) vc_set_dist "$2"; shift 2 ;;
|
||||
--no-configure) DO_CONFIGURE=false; shift ;;
|
||||
--config) XCODE_CONFIG="$2"; shift 2 ;;
|
||||
-h|--help) vc_print_help "$0"; exit 0 ;;
|
||||
*) vc_die "unknown arg: $1 (try --help)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
vc_require_macos
|
||||
vc_log "dist dir: $VC_DIST_DIR config: $XCODE_CONFIG"
|
||||
|
||||
# ── Step 1: XCFramework (all slices) ──────────────────────────────────────────
|
||||
# The iOS simulator build requires the ios-arm64-simulator slice, so we always
|
||||
# build all three slices. --no-configure skips cmake configure but still runs
|
||||
# cmake --build (which is fast with a warm build cache).
|
||||
vc_step "build VoiceCatCore.xcframework (all slices: macOS + iOS device + iOS sim)"
|
||||
XCFW_ARGS=( --all )
|
||||
$DO_CONFIGURE || XCFW_ARGS+=( --no-configure )
|
||||
"$VC_REPO_ROOT/clients/apple/scripts/build-xcframework.sh" "${XCFW_ARGS[@]}"
|
||||
XCFW="$VC_REPO_ROOT/clients/apple/VoiceCatCore.xcframework"
|
||||
[[ -d "$XCFW" ]] || vc_die "xcframework not found at $XCFW"
|
||||
vc_ok "xcframework ready -> $XCFW"
|
||||
|
||||
# ── Step 2: xcodebuild for iOS Simulator ──────────────────────────────────────
|
||||
# We use -target (not -scheme + -destination) to avoid the requirement of a
|
||||
# matching simulator runtime in xcrun simctl. When using -target, xcodebuild
|
||||
# skips destination resolution and builds directly against the requested SDK.
|
||||
#
|
||||
# SYMROOT and OBJROOT are both pinned to the same directory so the Swift Package
|
||||
# (VoiceCatCore) and the app target (VoiceCatiOS) resolve each other's products.
|
||||
# Without this, the package builds to clients/apple/build/ while the app target
|
||||
# looks in clients/apple/iOS/build/ — the module import fails with "unable to
|
||||
# resolve module dependency: 'VoiceCatCore'".
|
||||
#
|
||||
# CODE_SIGNING_ALLOWED=NO avoids provisioning-profile errors for local sim builds.
|
||||
XCODEPROJ="$VC_REPO_ROOT/clients/apple/iOS/VoiceCatiOS.xcodeproj"
|
||||
BUILD_DIR="$VC_REPO_ROOT/clients/apple/iOS/build"
|
||||
SIM_SDK_VER="$(xcrun --sdk iphonesimulator --show-sdk-version 2>/dev/null)"
|
||||
SIM_SDK="iphonesimulator${SIM_SDK_VER}"
|
||||
vc_step "xcodebuild VoiceCatiOS ($XCODE_CONFIG / $SIM_SDK)"
|
||||
xcodebuild \
|
||||
-project "$XCODEPROJ" \
|
||||
-target VoiceCatiOS \
|
||||
-sdk "$SIM_SDK" \
|
||||
-configuration "$XCODE_CONFIG" \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
ARCHS=arm64 \
|
||||
ONLY_ACTIVE_ARCH=YES \
|
||||
SYMROOT="$BUILD_DIR" \
|
||||
OBJROOT="$BUILD_DIR" \
|
||||
build
|
||||
|
||||
APP="$BUILD_DIR/${XCODE_CONFIG}-iphonesimulator/VoiceCatiOS.app"
|
||||
[[ -d "$APP" ]] || vc_die "VoiceCatiOS.app not found at $APP"
|
||||
vc_ok "built -> $APP"
|
||||
|
||||
# ── Stage ─────────────────────────────────────────────────────────────────────
|
||||
vc_step "stage -> $VC_DIST_DIR/ios-client"
|
||||
STAGE="$(vc_dist_subdir ios-client)"
|
||||
cp -R "$APP" "$STAGE/"
|
||||
vc_ok "VoiceCatiOS.app -> $STAGE/ ($(du -sh "$APP" | cut -f1))"
|
||||
vc_ok "done -> $STAGE"
|
||||
@@ -1,174 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# build-libs.sh — build libvoicecat for each supported client platform and
|
||||
# stage the artifacts under dist/lib/.
|
||||
#
|
||||
# Artifacts per platform:
|
||||
# macos → dist/lib/macos/ libvoicecat.a + libvoicecat-fat.a + voicecat.h + module.modulemap
|
||||
# ios → dist/lib/ios-device/ (arm64-ios slice)
|
||||
# ios-sim → dist/lib/ios-sim/ (arm64-ios-sim slice)
|
||||
# windows → dist/lib/windows/ voicecat.dll + voicecat.h
|
||||
# xcframework → dist/lib/VoiceCatCore.xcframework/ (stitched Apple slices)
|
||||
#
|
||||
# The Apple slices + XCFramework are produced by clients/apple/scripts/
|
||||
# build-xcframework.sh (the validated flow); this script drives it and stages
|
||||
# the outputs. The Windows DLL comes from the `windows-client` preset.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/build-libs.sh # host default (macos on macOS, windows on Windows)
|
||||
# scripts/build-libs.sh --platform macos
|
||||
# scripts/build-libs.sh --platform ios
|
||||
# scripts/build-libs.sh --platform ios-sim
|
||||
# scripts/build-libs.sh --platform windows
|
||||
# scripts/build-libs.sh --all-apple # macos + ios + ios-sim + xcframework (macOS host)
|
||||
# scripts/build-libs.sh --all # everything buildable on this host
|
||||
# scripts/build-libs.sh --dist /out --no-configure
|
||||
# scripts/build-libs.sh -h|--help
|
||||
#
|
||||
# Platform availability: macos / ios / ios-sim require a macOS host; windows
|
||||
# requires a Windows host. Requires VCPKG_ROOT.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VC_SCRIPT_NAME="build-libs"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
PLATFORMS=()
|
||||
DO_CONFIGURE=true
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dist) vc_set_dist "$2"; shift 2 ;;
|
||||
--no-configure) DO_CONFIGURE=false; shift ;;
|
||||
--platform) PLATFORMS+=( "$2" ); shift 2 ;;
|
||||
--all-apple) PLATFORMs_all_apple=1; shift ;;
|
||||
--all) PLATFORMS=( all-host ); shift ;;
|
||||
-h|--help) vc_print_help "$0"; exit 0 ;;
|
||||
*) vc_die "unknown arg: $1 (try --help)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
HOST="$(vc_host_os)"
|
||||
|
||||
# --all-apple expands to the three Apple platforms.
|
||||
if [[ -n "${PLATFORMs_all_apple:-}" ]]; then
|
||||
PLATFORMS=( macos ios ios-sim )
|
||||
fi
|
||||
|
||||
# Default: host-appropriate single platform.
|
||||
if [[ ${#PLATFORMS[@]} -eq 0 ]]; then
|
||||
case "$HOST" in
|
||||
macos) PLATFORMS=( macos ) ;;
|
||||
windows) PLATFORMS=( windows ) ;;
|
||||
*) vc_die "no default library platform for host '$HOST'. Pass --platform explicitly." ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Expand the meta-target.
|
||||
if [[ " ${PLATFORMS[*]} " == *" all-host "* ]]; then
|
||||
PLATFORMS=()
|
||||
case "$HOST" in
|
||||
macos) PLATFORMS=( macos ios ios-sim ) ;;
|
||||
windows) PLATFORMS=( windows ) ;;
|
||||
*) vc_die "--all: nothing buildable on host '$HOST'." ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
vc_log "dist dir: $VC_DIST_DIR"
|
||||
vc_log "platforms: ${PLATFORMS[*]}"
|
||||
|
||||
# Validate platform availability against host.
|
||||
for p in "${PLATFORMS[@]}"; do
|
||||
case "$p" in
|
||||
macos|ios|ios-sim) [[ "$HOST" == "macos" ]] || vc_die "platform '$p' requires a macOS host." ;;
|
||||
windows) [[ "$HOST" == "windows" ]] || vc_die "platform '$p' requires a Windows host." ;;
|
||||
*) vc_die "unknown platform '$p' (try --help)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
HAVE_APPLE=false
|
||||
HAVE_WINDOWS=false
|
||||
for p in "${PLATFORMS[@]}"; do
|
||||
case "$p" in
|
||||
macos|ios|ios-sim) HAVE_APPLE=true ;;
|
||||
windows) HAVE_WINDOWS=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ── Apple slices + XCFramework ──────────────────────────────────────────────────
|
||||
# Stage one slice's .a + headers + module map into dist/lib/<sub>.
|
||||
stage_apple_slice() {
|
||||
local preset="$1" sub="$2"
|
||||
local lib="$VC_REPO_ROOT/build/$preset/lib/libvoicecat.a"
|
||||
local fat="$VC_REPO_ROOT/build/$preset/lib/libvoicecat-fat.a"
|
||||
local out; out="$(vc_dist_subdir "lib/$sub")"
|
||||
[[ -f "$lib" ]] || vc_die "expected $lib not found (did the xcframework build run?)"
|
||||
cp "$lib" "$out/"
|
||||
[[ -f "$fat" ]] && cp "$fat" "$out/" || true
|
||||
cp "$VC_REPO_ROOT/core/include/voicecat.h" "$out/"
|
||||
cat > "$out/module.modulemap" <<'MM'
|
||||
module VoiceCatC {
|
||||
header "voicecat.h"
|
||||
export *
|
||||
}
|
||||
MM
|
||||
vc_ok "$sub: libvoicecat.a ($(vc_file_size "$lib") bytes) -> $out"
|
||||
}
|
||||
|
||||
if $HAVE_APPLE; then
|
||||
vc_resolve_vcpkg_root apple-dev
|
||||
vc_step "build Apple slices via build-xcframework.sh"
|
||||
|
||||
ALL_THREE=false
|
||||
if [[ " ${PLATFORMS[*]} " == *" macos "* && " ${PLATFORMS[*]} " == *" ios "* && " ${PLATFORMS[*]} " == *" ios-sim "* ]]; then
|
||||
ALL_THREE=true
|
||||
fi
|
||||
|
||||
xcfw_invoke() {
|
||||
local args=()
|
||||
$DO_CONFIGURE || args+=( --no-configure )
|
||||
args+=( "$@" )
|
||||
"$VC_REPO_ROOT/clients/apple/scripts/build-xcframework.sh" "${args[@]}"
|
||||
}
|
||||
|
||||
if $ALL_THREE; then
|
||||
xcfw_invoke --all
|
||||
stage_apple_slice apple-dev macos
|
||||
stage_apple_slice apple-ios ios-device
|
||||
stage_apple_slice apple-ios-sim ios-sim
|
||||
else
|
||||
for p in "${PLATFORMS[@]}"; do
|
||||
case "$p" in
|
||||
macos) xcfw_invoke --preset apple-dev; stage_apple_slice apple-dev macos ;;
|
||||
ios) xcfw_invoke --preset apple-ios; stage_apple_slice apple-ios ios-device ;;
|
||||
ios-sim) xcfw_invoke --preset apple-ios-sim; stage_apple_slice apple-ios-sim ios-sim ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
# Stage the stitched XCFramework (whatever slices the last call produced).
|
||||
XCFW="$VC_REPO_ROOT/clients/apple/VoiceCatCore.xcframework"
|
||||
[[ -d "$XCFW" ]] || vc_die "xcframework not found at $XCFW"
|
||||
LIB_DIR="$VC_DIST_DIR/lib"
|
||||
mkdir -p "$LIB_DIR"
|
||||
rm -rf "$LIB_DIR/VoiceCatCore.xcframework"
|
||||
cp -R "$XCFW" "$LIB_DIR/"
|
||||
vc_ok "VoiceCatCore.xcframework -> $LIB_DIR/"
|
||||
fi
|
||||
|
||||
# ── Windows DLL ─────────────────────────────────────────────────────────────────
|
||||
if $HAVE_WINDOWS; then
|
||||
vc_resolve_vcpkg_root windows-client
|
||||
vc_step "build Windows DLL (preset: windows-client)"
|
||||
if $DO_CONFIGURE; then
|
||||
cmake --preset windows-client
|
||||
fi
|
||||
cmake --build --preset windows-client
|
||||
DLL="$VC_REPO_ROOT/build/windows-client/bin/voicecat.dll"
|
||||
[[ -f "$DLL" ]] || vc_die "expected $DLL not found"
|
||||
OUT="$(vc_dist_subdir lib/windows)"
|
||||
cp "$DLL" "$OUT/"
|
||||
cp "$VC_REPO_ROOT/core/include/voicecat.h" "$OUT/"
|
||||
vc_ok "windows: voicecat.dll ($(vc_file_size "$DLL") bytes) -> $OUT"
|
||||
fi
|
||||
|
||||
vc_ok "done -> $VC_DIST_DIR/lib"
|
||||
@@ -1,88 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# build-linux-binaries.sh — build stripped Linux server binaries locally using Docker.
|
||||
#
|
||||
# PRIMARY path: push to main (or trigger manually from the GitHub Actions tab) and
|
||||
# download the artifacts from .github/workflows/build-linux.yml — no local disk
|
||||
# pressure, both amd64 and arm64 handled in the cloud.
|
||||
#
|
||||
# LOCAL path (this script): uses Docker + buildx with BuildKit cache. Requires
|
||||
# ~10–15 GB of free disk for the vcpkg build cache. Fine on a Linux dev machine;
|
||||
# on Windows/macOS prefer the GitHub Actions path to avoid filling your Docker VM disk.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/build-linux-binaries.sh # build both arches
|
||||
# ./scripts/build-linux-binaries.sh amd64 # build one arch only
|
||||
# ./scripts/build-linux-binaries.sh arm64
|
||||
#
|
||||
# Output:
|
||||
# dist/linux-amd64/{voicecat-server,voicecat-admin}
|
||||
# dist/linux-arm64/{voicecat-server,voicecat-admin}
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
DIST_DIR="${REPO_ROOT}/dist"
|
||||
|
||||
# ── arg parsing ────────────────────────────────────────────────────────────────
|
||||
case "${1:-both}" in
|
||||
amd64) ARCHS=("amd64") ;;
|
||||
arm64) ARCHS=("arm64") ;;
|
||||
both) ARCHS=("amd64" "arm64") ;;
|
||||
*) echo "Usage: $0 [amd64|arm64|both]" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
# ── pre-flight ─────────────────────────────────────────────────────────────────
|
||||
if ! command -v docker &>/dev/null; then
|
||||
echo "Error: docker not found." >&2
|
||||
echo "Install Docker Desktop: https://docs.docker.com/get-docker/" >&2
|
||||
echo "Or use GitHub Actions (push to main and download artifacts)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker buildx version &>/dev/null; then
|
||||
echo "Error: docker buildx not available." >&2
|
||||
echo "Docker Desktop ships with buildx. On Linux: docker buildx install" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Note: first run compiles vcpkg packages from source (~10-15 GB build cache)."
|
||||
echo "On Windows/macOS, consider using GitHub Actions instead (Actions → Build Linux Binaries → Run workflow)."
|
||||
echo ""
|
||||
|
||||
# ── ensure a builder with multi-platform support ───────────────────────────────
|
||||
BUILDER="voicecat-builder"
|
||||
if ! docker buildx inspect "${BUILDER}" &>/dev/null; then
|
||||
echo "→ Creating buildx builder '${BUILDER}' (docker-container driver)..."
|
||||
docker buildx create --name "${BUILDER}" --driver docker-container --bootstrap
|
||||
fi
|
||||
docker buildx use "${BUILDER}"
|
||||
|
||||
# ── build each arch ────────────────────────────────────────────────────────────
|
||||
for ARCH in "${ARCHS[@]}"; do
|
||||
PLATFORM="linux/${ARCH}"
|
||||
OUT_DIR="${DIST_DIR}/linux-${ARCH}"
|
||||
mkdir -p "${OUT_DIR}"
|
||||
|
||||
echo "══ Building ${PLATFORM} ══════════════════════════════════════════════════"
|
||||
if [[ "${ARCH}" == "arm64" ]] && [[ "$(uname -m)" != "aarch64" ]]; then
|
||||
echo " (Running under QEMU on a non-arm64 host — will be slow)"
|
||||
fi
|
||||
|
||||
docker buildx build \
|
||||
--platform "${PLATFORM}" \
|
||||
--target export \
|
||||
--output "type=local,dest=${OUT_DIR}" \
|
||||
--progress plain \
|
||||
"${REPO_ROOT}"
|
||||
|
||||
echo "→ ${PLATFORM} binaries:"
|
||||
ls -lh "${OUT_DIR}/"
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "════════════════════════════════════════════════════════════════════════════"
|
||||
echo "Done. Binaries in dist/:"
|
||||
for ARCH in "${ARCHS[@]}"; do
|
||||
ls -lh "${DIST_DIR}/linux-${ARCH}/"
|
||||
done
|
||||
@@ -1,65 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# build-macos-client.sh — build the macOS AppKit client (VoiceCatMac.app) and
|
||||
# stage it into dist/macos-client/.
|
||||
#
|
||||
# Two steps:
|
||||
# 1. Build VoiceCatCore.xcframework (macOS slice) by invoking the existing
|
||||
# clients/apple/scripts/build-xcframework.sh — that builds libvoicecat.a
|
||||
# (apple-dev preset), merges vcpkg deps into a fat .a, and stitches the
|
||||
# XCFramework the Swift Package consumes.
|
||||
# 2. xcodebuild the VoiceCatMac scheme into a known derivedDataPath, then
|
||||
# copy VoiceCatMac.app into dist/macos-client/.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/build-macos-client.sh # build + stage into ./dist/macos-client
|
||||
# scripts/build-macos-client.sh --dist /out
|
||||
# scripts/build-macos-client.sh --no-configure # skip cmake configure (xcframework step)
|
||||
# scripts/build-macos-client.sh --config Debug # Xcode build config (default Release)
|
||||
# scripts/build-macos-client.sh -h|--help
|
||||
#
|
||||
# macOS only. Requires VCPKG_ROOT and Xcode.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VC_SCRIPT_NAME="build-macos-client"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
DO_CONFIGURE=true
|
||||
XCODE_CONFIG="Release"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dist) vc_set_dist "$2"; shift 2 ;;
|
||||
--no-configure) DO_CONFIGURE=false; shift ;;
|
||||
--config) XCODE_CONFIG="$2"; shift 2 ;;
|
||||
-h|--help) vc_print_help "$0"; exit 0 ;;
|
||||
*) vc_die "unknown arg: $1 (try --help)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
vc_require_macos
|
||||
vc_log "dist dir: $VC_DIST_DIR"
|
||||
|
||||
vc_step "build VoiceCatCore.xcframework (macOS slice)"
|
||||
XCFW_ARGS=()
|
||||
$DO_CONFIGURE || XCFW_ARGS+=( --no-configure )
|
||||
"$VC_REPO_ROOT/clients/apple/scripts/build-xcframework.sh" "${XCFW_ARGS[@]}"
|
||||
XCFW="$VC_REPO_ROOT/clients/apple/VoiceCatCore.xcframework"
|
||||
[[ -d "$XCFW" ]] || vc_die "xcframework not found at $XCFW"
|
||||
vc_ok "xcframework ready -> $XCFW"
|
||||
|
||||
vc_step "xcodebuild VoiceCatMac ($XCODE_CONFIG)"
|
||||
XCODEPROJ="$VC_REPO_ROOT/clients/apple/macOS/VoiceCatMac.xcodeproj"
|
||||
DERIVED="$VC_REPO_ROOT/build/macos-app"
|
||||
xcodebuild -project "$XCODEPROJ" -scheme VoiceCatMac \
|
||||
-configuration "$XCODE_CONFIG" -derivedDataPath "$DERIVED" build
|
||||
|
||||
APP="$DERIVED/Build/Products/$XCODE_CONFIG/VoiceCatMac.app"
|
||||
[[ -d "$APP" ]] || vc_die "VoiceCatMac.app not found at $APP"
|
||||
vc_ok "built -> $APP"
|
||||
|
||||
vc_step "stage -> $VC_DIST_DIR/macos-client"
|
||||
STAGE="$(vc_dist_subdir macos-client)"
|
||||
cp -R "$APP" "$STAGE/"
|
||||
vc_ok "VoiceCatMac.app -> $STAGE/ ($(du -sh "$APP" | cut -f1))"
|
||||
vc_ok "done -> $STAGE"
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/zsh
|
||||
set -euo pipefail
|
||||
|
||||
script_dir=${0:A:h}
|
||||
build_one() {
|
||||
local sdk=$1
|
||||
local rid=$2
|
||||
local sdk_path
|
||||
sdk_path=$(xcrun --sdk "$sdk" --show-sdk-path)
|
||||
local compiler
|
||||
compiler=$(xcrun --sdk "$sdk" --find clang)
|
||||
local build_dir="$script_dir/../artifacts/native-build-$rid-cmake"
|
||||
cmake -S "$script_dir/../native/media" -B "$build_dir" \
|
||||
-DCMAKE_SYSTEM_NAME=iOS \
|
||||
-DCMAKE_C_COMPILER="$compiler" \
|
||||
-DCMAKE_OSX_SYSROOT="$sdk_path" \
|
||||
-DCMAKE_OSX_ARCHITECTURES=arm64 \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=18.0 \
|
||||
-DVOICECAT_DOTNET_RID="$rid" \
|
||||
-DVOICECAT_BUNDLED_OPUS=ON
|
||||
cmake --build "$build_dir" --config Release --target voicecat_media --parallel 2
|
||||
cmake --install "$build_dir" --config Release \
|
||||
--component DotnetMedia --prefix "$script_dir/../artifacts/native"
|
||||
local staged="$script_dir/../artifacts/native/runtimes/$rid/native/libvoicecat_media.a"
|
||||
local opus="$build_dir/_deps/opus-build/libopus.a"
|
||||
local rnnoise="$build_dir/librnnoise.a"
|
||||
local combined="$staged.combined"
|
||||
xcrun --sdk "$sdk" libtool -static -o "$combined" "$build_dir/libvoicecat_media.a" "$opus" "$rnnoise"
|
||||
mv "$combined" "$staged"
|
||||
}
|
||||
|
||||
build_one iphoneos ios-arm64
|
||||
build_one iphonesimulator iossimulator-arm64
|
||||
@@ -0,0 +1,18 @@
|
||||
param(
|
||||
[string]$BuildDirectory = "$PSScriptRoot/../artifacts/native-build",
|
||||
[string]$RuntimeIdentifier = [System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier,
|
||||
[string]$Generator,
|
||||
[string]$CCompiler
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$configure = @('-S', "$PSScriptRoot/../native/media", '-B', $BuildDirectory,
|
||||
'-DCMAKE_BUILD_TYPE=Release', "-DVOICECAT_DOTNET_RID=$RuntimeIdentifier")
|
||||
if ($Generator) { $configure += @('-G', $Generator) }
|
||||
if ($CCompiler) { $configure += "-DCMAKE_C_COMPILER=$CCompiler" }
|
||||
& cmake @configure
|
||||
if ($LASTEXITCODE) { throw "Native configure failed: $LASTEXITCODE" }
|
||||
& cmake --build $BuildDirectory --config Release --target voicecat_media --parallel 2
|
||||
if ($LASTEXITCODE) { throw "Native build failed: $LASTEXITCODE" }
|
||||
& cmake --install $BuildDirectory --config Release --component DotnetMedia --prefix "$PSScriptRoot/../artifacts/native"
|
||||
if ($LASTEXITCODE) { throw "Native staging failed: $LASTEXITCODE" }
|
||||
@@ -1,62 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# build-server.sh — build the production-shaped voicecat-server (+ voicecat-admin)
|
||||
# and stage the binaries into dist/server/.
|
||||
#
|
||||
# Uses the `server-release` CMake preset (Release, stripped, no tests — see
|
||||
# docs/building.md §5). Works on Windows, Linux, and macOS; the vcpkg triplet
|
||||
# is auto-resolved by cmake/voicecat-toolchain.cmake.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/build-server.sh # build + stage into ./dist/server
|
||||
# scripts/build-server.sh --dist /out # stage into /out/server
|
||||
# scripts/build-server.sh --no-configure # skip cmake configure, just rebuild + stage
|
||||
# scripts/build-server.sh -h|--help # show this help
|
||||
#
|
||||
# Override the default dist dir (<repo>/dist) with --dist or VOICECAT_DIST_DIR.
|
||||
# Requires VCPKG_ROOT (or a pre-configured build/server-release cache).
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VC_SCRIPT_NAME="build-server"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
DO_CONFIGURE=true
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dist) vc_set_dist "$2"; shift 2 ;;
|
||||
--no-configure) DO_CONFIGURE=false; shift ;;
|
||||
-h|--help) vc_print_help "$0"; exit 0 ;;
|
||||
*) vc_die "unknown arg: $1 (try --help)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
vc_log "dist dir: $VC_DIST_DIR"
|
||||
vc_resolve_vcpkg_root server-release
|
||||
|
||||
vc_step "configure + build (preset: server-release)"
|
||||
if $DO_CONFIGURE; then
|
||||
cmake --preset server-release
|
||||
fi
|
||||
cmake --build --preset server-release
|
||||
|
||||
BIN_DIR="$VC_REPO_ROOT/build/server-release/bin"
|
||||
EXE_EXT=""
|
||||
[[ "$(vc_host_os)" == "windows" ]] && EXE_EXT=".exe"
|
||||
|
||||
vc_log "staging binaries -> $VC_DIST_DIR/server"
|
||||
STAGE="$(vc_dist_subdir server)"
|
||||
|
||||
for b in voicecat-server voicecat-admin; do
|
||||
f="$BIN_DIR/$b$EXE_EXT"
|
||||
if [[ ! -f "$f" ]]; then
|
||||
# voicecat-admin only builds when vcpkg deps are on (server-release has
|
||||
# them on), so it should exist — but be lenient about admin on odd configs.
|
||||
[[ "$b" == "voicecat-admin" ]] && { vc_log "note: $b not found, skipping"; continue; }
|
||||
vc_die "expected output not found: $f"
|
||||
fi
|
||||
cp "$f" "$STAGE/"
|
||||
vc_ok "$b$EXE_EXT -> $STAGE/$(basename "$f") ($(vc_file_size "$f") bytes)"
|
||||
done
|
||||
|
||||
vc_ok "done -> $STAGE"
|
||||
@@ -1,73 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# build-windows-client.sh — build the Windows WinForms client and stage a
|
||||
# runnable folder into dist/windows-client/.
|
||||
#
|
||||
# Two steps:
|
||||
# 1. Build the native voicecat.dll via the `windows-client` CMake preset
|
||||
# (MinGW, static runtime — no libgcc_s_seh-1.dll / libstdc++-6.dll deps).
|
||||
# 2. `dotnet publish` the C# app (clients/windows/VoiceCat.App). The app's
|
||||
# Directory.Build.props copies voicecat.dll into the output automatically.
|
||||
#
|
||||
# The staged folder contains VoiceCat.App.exe + voicecat.dll + all .NET deps,
|
||||
# ready to run. By default it is framework-dependent (needs the .NET 10 runtime
|
||||
# installed); pass --self-contained for a folder that runs without .NET.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/build-windows-client.sh # build + stage into ./dist/windows-client
|
||||
# scripts/build-windows-client.sh --dist /out
|
||||
# scripts/build-windows-client.sh --no-configure # skip cmake configure (DLL step)
|
||||
# scripts/build-windows-client.sh --self-contained
|
||||
# scripts/build-windows-client.sh -h|--help
|
||||
#
|
||||
# Windows only (MSYS2/MinGW). Requires VCPKG_ROOT and the .NET 10 SDK.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VC_SCRIPT_NAME="build-windows-client"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
DO_CONFIGURE=true
|
||||
SELF_CONTAINED=false
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dist) vc_set_dist "$2"; shift 2 ;;
|
||||
--no-configure) DO_CONFIGURE=false; shift ;;
|
||||
--self-contained) SELF_CONTAINED=true; shift ;;
|
||||
-h|--help) vc_print_help "$0"; exit 0 ;;
|
||||
*) vc_die "unknown arg: $1 (try --help)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
vc_require_windows
|
||||
vc_log "dist dir: $VC_DIST_DIR"
|
||||
vc_resolve_vcpkg_root windows-client
|
||||
|
||||
vc_step "build native DLL (preset: windows-client)"
|
||||
if $DO_CONFIGURE; then
|
||||
cmake --preset windows-client
|
||||
fi
|
||||
cmake --build --preset windows-client
|
||||
DLL="$VC_REPO_ROOT/build/windows-client/bin/voicecat.dll"
|
||||
[[ -f "$DLL" ]] || vc_die "expected output not found: $DLL"
|
||||
vc_ok "voicecat.dll -> $DLL ($(vc_file_size "$DLL") bytes)"
|
||||
|
||||
vc_step "publish C# app (dotnet publish)"
|
||||
APP_PROJ="$VC_REPO_ROOT/clients/windows/VoiceCat.App/VoiceCat.App.csproj"
|
||||
PUBLISH_DIR="$VC_REPO_ROOT/build/windows-client/publish"
|
||||
rm -rf "$PUBLISH_DIR"
|
||||
mkdir -p "$PUBLISH_DIR"
|
||||
|
||||
PUBLISH_ARGS=(dotnet publish "$APP_PROJ" -c Release -o "$PUBLISH_DIR")
|
||||
if $SELF_CONTAINED; then
|
||||
PUBLISH_ARGS+=( -r win-x64 --self-contained true )
|
||||
fi
|
||||
"${PUBLISH_ARGS[@]}"
|
||||
APP_EXE="$PUBLISH_DIR/VoiceCat.App.exe"
|
||||
[[ -f "$APP_EXE" ]] || vc_die "publish output missing VoiceCat.App.exe in $PUBLISH_DIR"
|
||||
|
||||
vc_step "stage -> $VC_DIST_DIR/windows-client"
|
||||
STAGE="$(vc_dist_subdir windows-client)"
|
||||
cp -R "$PUBLISH_DIR/." "$STAGE/"
|
||||
vc_ok "VoiceCat.App.exe + deps -> $STAGE (voicecat.dll included)"
|
||||
vc_ok "done -> $STAGE"
|
||||
@@ -0,0 +1,34 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$allowed = @('MIT', 'BSD-2-Clause', 'BSD-3-Clause', 'Apache-2.0', 'ISC', '0BSD', 'MPL-2.0')
|
||||
$seen = @{}
|
||||
foreach ($lockPath in (Get-ChildItem -LiteralPath "$PSScriptRoot/.." -Filter 'packages*.lock.json' -Recurse)) {
|
||||
$lock = Get-Content -Raw -LiteralPath $lockPath.FullName | ConvertFrom-Json
|
||||
$assets = Get-Content -Raw -LiteralPath (Join-Path $lockPath.DirectoryName 'obj/project.assets.json') | ConvertFrom-Json
|
||||
foreach ($framework in $lock.dependencies.PSObject.Properties) {
|
||||
foreach ($package in $framework.Value.PSObject.Properties) {
|
||||
if ($package.Value.type -eq 'Project') { continue }
|
||||
$id = $package.Name.ToLowerInvariant()
|
||||
$version = $package.Value.resolved
|
||||
if ($seen.ContainsKey("$id/$version")) { continue }
|
||||
$seen["$id/$version"] = $true
|
||||
$nuspec = $null
|
||||
foreach ($folder in $assets.packageFolders.PSObject.Properties.Name) {
|
||||
$candidate = Join-Path $folder "$id/$version/$id.nuspec"
|
||||
if (Test-Path -LiteralPath $candidate) { $nuspec = $candidate; break }
|
||||
}
|
||||
if (!$nuspec) { throw "Restore dependencies before auditing $id/$version." }
|
||||
[xml]$spec = Get-Content -Raw -LiteralPath $nuspec
|
||||
$license = $spec.package.metadata.license
|
||||
if ($license.type -eq 'expression' -and $allowed -contains $license.InnerText) { continue }
|
||||
# This pinned package contains public-domain SQLite builds; no NuGet license metadata.
|
||||
if ($id -eq 'sourcegear.sqlite3' -and $version -eq '3.50.4.2' -and
|
||||
$spec.package.metadata.projectUrl -eq 'https://sqlite.org/' -and
|
||||
$spec.package.metadata.repository.commit -eq '9a2d8281d8f714fe54f7cbcd122479d17b533e89') { continue }
|
||||
# This legacy pinned package predates NuGet license expressions (Apache-2.0).
|
||||
if ($id -eq 'xunit.abstractions' -and $version -eq '2.0.3' -and
|
||||
$spec.package.metadata.licenseUrl -eq 'https://raw.githubusercontent.com/xunit/xunit/master/license.txt') { continue }
|
||||
throw "Unapproved license for $id/$version. Review before changing the allowlist."
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Output "Checked $($seen.Count) package licenses: approved allowlist passed."
|
||||
@@ -1,118 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# common.sh — shared helpers for the scripts/ build scripts.
|
||||
#
|
||||
# Source this from the other scripts/ build scripts (after setting SCRIPT_DIR):
|
||||
#
|
||||
# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# VC_SCRIPT_NAME="build-foo" # optional, for nicer log prefixes
|
||||
# source "$SCRIPT_DIR/common.sh"
|
||||
#
|
||||
# What it provides:
|
||||
# - repo-root + dist-dir resolution (default <repo>/dist; override with
|
||||
# --dist <path> via vc_set_dist, or the VOICECAT_DIST_DIR env var).
|
||||
# - VCPKG_ROOT detection (env var first, then an existing CMake cache).
|
||||
# - host-platform guards (vc_require_macos / vc_require_windows / vc_host_os).
|
||||
# - uniform [tag] logging + a portable file-size helper.
|
||||
# - vc_dist_subdir <name>: create (clean) and echo $DIST/<name>.
|
||||
# - vc_print_help <file>: print a script's leading "# " comment block as the
|
||||
# --help text (so each script's header comment IS its help).
|
||||
#
|
||||
# Not meant to be run directly.
|
||||
|
||||
# This file lives in scripts/, so the repo root is one level up.
|
||||
VC_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VC_REPO_ROOT="$(cd "$VC_SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Display name for log lines (defaults to the caller's filename).
|
||||
VC_SCRIPT_NAME="${VC_SCRIPT_NAME:-$(basename "${BASH_SOURCE[1]:-$0}")}"
|
||||
|
||||
# Dist dir — env override resolved here; --dist updates it via vc_set_dist.
|
||||
VC_DIST_DIR="${VOICECAT_DIST_DIR:-$VC_REPO_ROOT/dist}"
|
||||
|
||||
# ── Logging ─────────────────────────────────────────────────────────────────────
|
||||
vc_log() { printf '\033[1;34m[%s]\033[0m %s\n' "$VC_SCRIPT_NAME" "$*"; }
|
||||
vc_step() { printf '\033[1;36m[%s] === %s ===\033[0m\n' "$VC_SCRIPT_NAME" "$*"; }
|
||||
vc_ok() { printf '\033[1;32m[%s]\033[0m %s\n' "$VC_SCRIPT_NAME" "$*"; }
|
||||
vc_err() { printf '\033[1;31m[%s] error:\033[0m %s\n' "$VC_SCRIPT_NAME" "$*" >&2; }
|
||||
vc_die() { vc_err "$*"; exit 1; }
|
||||
|
||||
# Portable file size (macOS `stat -f%z` vs coreutils `stat -c%s`).
|
||||
vc_file_size() {
|
||||
stat -f%z "$1" 2>/dev/null || stat -c%s "$1" 2>/dev/null
|
||||
}
|
||||
|
||||
# ── Dist dir ────────────────────────────────────────────────────────────────────
|
||||
# Set/override the dist directory (called when a script sees --dist <path>).
|
||||
vc_set_dist() {
|
||||
VC_DIST_DIR="$1"
|
||||
mkdir -p "$VC_DIST_DIR"
|
||||
}
|
||||
|
||||
# Create (clean) and echo a subdirectory under dist/.
|
||||
vc_dist_subdir() {
|
||||
local path="$VC_DIST_DIR/$1"
|
||||
rm -rf "$path"
|
||||
mkdir -p "$path"
|
||||
echo "$path"
|
||||
}
|
||||
|
||||
# ── Platform helpers ────────────────────────────────────────────────────────────
|
||||
vc_host_os() {
|
||||
case "$(uname -s)" in
|
||||
Darwin) echo macos ;;
|
||||
MINGW*|MSYS*|CYGWIN*) echo windows ;;
|
||||
Linux) echo linux ;;
|
||||
*) echo unknown ;;
|
||||
esac
|
||||
}
|
||||
|
||||
vc_require_macos() {
|
||||
[[ "$(uname -s)" == "Darwin" ]] \
|
||||
|| vc_die "this script must be run on macOS (uname -s = $(uname -s))."
|
||||
}
|
||||
|
||||
vc_require_windows() {
|
||||
local s; s="$(uname -s)"
|
||||
[[ "$s" == MINGW* || "$s" == MSYS* || "$s" == CYGWIN* ]] \
|
||||
|| vc_die "this script must be run on Windows (MSYS2/MinGW). uname -s = $s."
|
||||
}
|
||||
|
||||
# ── VCPKG_ROOT ──────────────────────────────────────────────────────────────────
|
||||
# Resolution order: the env var, else an existing CMake cache (Z_VCPKG_ROOT_DIR) so a
|
||||
# developer who already configured a preset doesn't need VCPKG_ROOT in their shell env,
|
||||
# else the bundled submodule at <repo-root>/vcpkg. $1 = the preset whose cache to probe
|
||||
# as a fallback (e.g. "dev" or "apple-dev").
|
||||
vc_resolve_vcpkg_root() {
|
||||
local fallback_preset="${1:-dev}"
|
||||
if [[ -z "${VCPKG_ROOT:-}" ]]; then
|
||||
local cache="$VC_REPO_ROOT/build/$fallback_preset/CMakeCache.txt"
|
||||
if [[ -f "$cache" ]]; then
|
||||
local detected
|
||||
detected="$(grep -m1 '^Z_VCPKG_ROOT_DIR:INTERNAL=' "$cache" | cut -d= -f2- || true)"
|
||||
if [[ -n "$detected" && -d "$detected" ]]; then
|
||||
export VCPKG_ROOT="$detected"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [[ -z "${VCPKG_ROOT:-}" ]]; then
|
||||
local bundled="$VC_REPO_ROOT/vcpkg"
|
||||
if [[ -f "$bundled/scripts/buildsystems/vcpkg.cmake" ]]; then
|
||||
export VCPKG_ROOT="$bundled"
|
||||
fi
|
||||
fi
|
||||
if [[ -z "${VCPKG_ROOT:-}" || ! -d "$VCPKG_ROOT" ]]; then
|
||||
vc_die "VCPKG_ROOT is not set or does not exist.
|
||||
either init the bundled submodule:
|
||||
git submodule update --init vcpkg
|
||||
or point at an external vcpkg checkout:
|
||||
export VCPKG_ROOT=/path/to/vcpkg"
|
||||
fi
|
||||
vc_log "VCPKG_ROOT=$VCPKG_ROOT"
|
||||
}
|
||||
|
||||
# ── Help ────────────────────────────────────────────────────────────────────────
|
||||
# Print a script's leading "# " comment block (skipping the shebang) as help.
|
||||
vc_print_help() {
|
||||
awk 'NR==1{next} /^#[[:space:]]?/{sub(/^#[[:space:]]?/,""); print; next} {exit}' "$1"
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# dist-ios-adhoc.sh — build an ad-hoc IPA of the iOS client and the files needed to
|
||||
# install it on registered devices from an HTTPS web page (itms-services OTA install).
|
||||
#
|
||||
# This is for handing the app to a handful of friends BEFORE TestFlight. Ad-hoc builds
|
||||
# only run on devices whose UDID is registered in your Apple Developer account, so the
|
||||
# script registers UDIDs (via the App Store Connect API) before it signs.
|
||||
#
|
||||
# Pipeline:
|
||||
# 1. Register each --udid with App Store Connect (idempotent; skip with --skip-register).
|
||||
# 2. Build VoiceCatCore.xcframework (iOS device slice).
|
||||
# 3. xcodebuild archive (Release, generic/platform=iOS), letting Xcode auto-create the
|
||||
# ad-hoc Distribution cert + profile via -allowProvisioningUpdates + the API key.
|
||||
# 4. xcodebuild -exportArchive with method=release-testing (Xcode's modern name for
|
||||
# "ad-hoc") -> VoiceCatiOS.ipa.
|
||||
# 5. Generate manifest.plist (OTA install manifest) + index.html (install page).
|
||||
# 6. Stage VoiceCatiOS.ipa + manifest.plist + index.html into dist/ios-adhoc/.
|
||||
#
|
||||
# You then upload those three files to your HTTPS host and open index.html on the iPhone.
|
||||
#
|
||||
# Prerequisites (one-time, not scriptable):
|
||||
# - Paid Apple Developer Program membership (Team ID is already set in the project).
|
||||
# - An App Store Connect API "Team Key" (.p8) with Admin or App Manager access:
|
||||
# App Store Connect -> Users and Access -> Integrations -> App Store Connect API.
|
||||
# Note its Key ID and Issuer ID. Keep the .p8 out of the repo.
|
||||
# - Each friend's device UDID (read it in Finder with the iPhone connected to a Mac).
|
||||
#
|
||||
# Auth — supply via env vars (or the matching flags):
|
||||
# ASC_KEY_ID App Store Connect API Key ID (--key-id)
|
||||
# ASC_ISSUER_ID App Store Connect API Issuer ID (--issuer-id)
|
||||
# ASC_KEY_PATH path to AuthKey_XXXXXXXXXX.p8 (--key)
|
||||
#
|
||||
# Usage:
|
||||
# scripts/dist-ios-adhoc.sh --udid <UDID> --name "Friend iPhone" \
|
||||
# --base-url https://example.com/voicecat
|
||||
# scripts/dist-ios-adhoc.sh --udids-file friends.csv --base-url https://example.com/vc
|
||||
# scripts/dist-ios-adhoc.sh --skip-register --base-url https://example.com/vc # rebuild only
|
||||
# scripts/dist-ios-adhoc.sh --dist /out --no-configure
|
||||
# scripts/dist-ios-adhoc.sh -h|--help
|
||||
#
|
||||
# Flags:
|
||||
# --udid <UDID> device to register (repeatable). Pair with an optional --name.
|
||||
# --name <label> label for the immediately-preceding --udid (default: the UDID).
|
||||
# --udids-file <file> CSV of "udid,name" lines (one device per line; blank/# lines skipped).
|
||||
# --base-url <url> public HTTPS folder you'll upload the output to. Used to fill
|
||||
# manifest.plist + index.html. If omitted, a __BASE_URL__ placeholder
|
||||
# is written and you must edit both files before they work.
|
||||
# --skip-register don't touch App Store Connect; just rebuild/export/stage.
|
||||
# --key-id / --issuer-id / --key override the ASC_* env vars.
|
||||
# --dist <path> output root (default <repo>/dist). Files land in <dist>/ios-adhoc/.
|
||||
# --no-configure skip the cmake configure step in the xcframework build.
|
||||
# -h, --help this help.
|
||||
#
|
||||
# macOS only. Requires Xcode, VCPKG_ROOT (or a configured build/apple-dev cache), and the
|
||||
# Python `cryptography` package (already present in a standard macOS Python 3).
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VC_SCRIPT_NAME="dist-ios-adhoc"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
# ── Project constants (see clients/apple/iOS/VoiceCatiOS.xcodeproj) ───────────────
|
||||
TEAM_ID="FJV8L966W4"
|
||||
APP_BUNDLE_ID="cat.voice.VoiceCatiOS"
|
||||
SCHEME="VoiceCatiOS"
|
||||
APP_TITLE="VoiceCat"
|
||||
XCODEPROJ="$VC_REPO_ROOT/clients/apple/iOS/VoiceCatiOS.xcodeproj"
|
||||
|
||||
# ── Args ──────────────────────────────────────────────────────────────────────────
|
||||
KEY_ID="${ASC_KEY_ID:-}"
|
||||
ISSUER_ID="${ASC_ISSUER_ID:-}"
|
||||
KEY_PATH="${ASC_KEY_PATH:-}"
|
||||
BASE_URL=""
|
||||
SKIP_REGISTER=false
|
||||
DO_CONFIGURE=true
|
||||
UDIDS=() # parallel arrays: UDIDS[i] / UDID_NAMES[i]
|
||||
UDID_NAMES=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--udid) UDIDS+=("$2"); UDID_NAMES+=("$2"); shift 2 ;;
|
||||
--name) [[ ${#UDID_NAMES[@]} -gt 0 ]] || vc_die "--name must follow a --udid"
|
||||
UDID_NAMES[${#UDID_NAMES[@]}-1]="$2"; shift 2 ;;
|
||||
--udids-file) [[ -f "$2" ]] || vc_die "udids file not found: $2"
|
||||
while IFS=',' read -r u n _; do
|
||||
u="${u// /}"
|
||||
[[ -z "$u" || "$u" == \#* ]] && continue
|
||||
UDIDS+=("$u"); UDID_NAMES+=("${n:-$u}")
|
||||
done < "$2"
|
||||
shift 2 ;;
|
||||
--base-url) BASE_URL="${2%/}"; shift 2 ;;
|
||||
--skip-register) SKIP_REGISTER=true; shift ;;
|
||||
--key-id) KEY_ID="$2"; shift 2 ;;
|
||||
--issuer-id) ISSUER_ID="$2"; shift 2 ;;
|
||||
--key) KEY_PATH="$2"; shift 2 ;;
|
||||
--dist) vc_set_dist "$2"; shift 2 ;;
|
||||
--no-configure) DO_CONFIGURE=false; shift ;;
|
||||
-h|--help) vc_print_help "$0"; exit 0 ;;
|
||||
*) vc_die "unknown arg: $1 (try --help)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
vc_require_macos
|
||||
|
||||
# Auth is needed for device registration AND for -allowProvisioningUpdates (Xcode uses the
|
||||
# same key to mint the ad-hoc Distribution cert/profile).
|
||||
[[ -n "$KEY_ID" ]] || vc_die "missing API Key ID (set ASC_KEY_ID or pass --key-id)"
|
||||
[[ -n "$ISSUER_ID" ]] || vc_die "missing Issuer ID (set ASC_ISSUER_ID or pass --issuer-id)"
|
||||
[[ -n "$KEY_PATH" ]] || vc_die "missing .p8 path (set ASC_KEY_PATH or pass --key)"
|
||||
[[ -f "$KEY_PATH" ]] || vc_die "API key file not found: $KEY_PATH"
|
||||
python3 -c "import cryptography" 2>/dev/null \
|
||||
|| vc_die "Python 'cryptography' package not available: pip3 install cryptography"
|
||||
|
||||
if [[ -z "$BASE_URL" ]]; then
|
||||
BASE_URL="__BASE_URL__"
|
||||
vc_log "no --base-url given: manifest.plist + index.html will use the __BASE_URL__"
|
||||
vc_log "placeholder — edit both files to your real HTTPS folder before uploading."
|
||||
fi
|
||||
|
||||
OUT="$(vc_dist_subdir ios-adhoc)"
|
||||
ARCHIVE="$OUT/VoiceCatiOS.xcarchive"
|
||||
EXPORT_DIR="$OUT/export"
|
||||
DERIVED="$OUT/DerivedData"
|
||||
IPA="$OUT/VoiceCatiOS.ipa"
|
||||
|
||||
# ── Step 1: register devices ───────────────────────────────────────────────────────
|
||||
if $SKIP_REGISTER; then
|
||||
vc_step "skipping device registration (--skip-register)"
|
||||
elif [[ ${#UDIDS[@]} -eq 0 ]]; then
|
||||
vc_step "no --udid/--udids-file given — skipping device registration"
|
||||
vc_log "(devices must already be registered, or this IPA won't install for them)"
|
||||
else
|
||||
vc_step "register ${#UDIDS[@]} device(s) with App Store Connect"
|
||||
for i in "${!UDIDS[@]}"; do
|
||||
python3 "$SCRIPT_DIR/asc_api.py" register \
|
||||
--key-id "$KEY_ID" --issuer-id "$ISSUER_ID" --key "$KEY_PATH" \
|
||||
--udid "${UDIDS[$i]}" --name "${UDID_NAMES[$i]}"
|
||||
done
|
||||
vc_ok "device registration complete"
|
||||
fi
|
||||
|
||||
# ── Step 2: xcframework (iOS device slice) ──────────────────────────────────────────
|
||||
vc_step "build VoiceCatCore.xcframework (iOS device slice)"
|
||||
vc_resolve_vcpkg_root "apple-dev"
|
||||
XCFW_ARGS=( --preset apple-ios )
|
||||
$DO_CONFIGURE || XCFW_ARGS+=( --no-configure )
|
||||
"$VC_REPO_ROOT/clients/apple/scripts/build-xcframework.sh" "${XCFW_ARGS[@]}"
|
||||
XCFW="$VC_REPO_ROOT/clients/apple/VoiceCatCore.xcframework"
|
||||
[[ -d "$XCFW" ]] || vc_die "xcframework not found at $XCFW"
|
||||
vc_ok "xcframework ready -> $XCFW"
|
||||
|
||||
# ── Step 3: archive ─────────────────────────────────────────────────────────────────
|
||||
vc_step "xcodebuild archive ($SCHEME / Release / generic iOS)"
|
||||
xcodebuild archive \
|
||||
-project "$XCODEPROJ" \
|
||||
-scheme "$SCHEME" \
|
||||
-configuration Release \
|
||||
-destination 'generic/platform=iOS' \
|
||||
-archivePath "$ARCHIVE" \
|
||||
-derivedDataPath "$DERIVED" \
|
||||
-allowProvisioningUpdates \
|
||||
-authenticationKeyPath "$KEY_PATH" \
|
||||
-authenticationKeyID "$KEY_ID" \
|
||||
-authenticationKeyIssuerID "$ISSUER_ID"
|
||||
[[ -d "$ARCHIVE" ]] || vc_die "archive not produced at $ARCHIVE"
|
||||
vc_ok "archived -> $ARCHIVE"
|
||||
|
||||
# ── Step 4: export ad-hoc IPA ───────────────────────────────────────────────────────
|
||||
# method "release-testing" is Xcode 15.3+/26's name for the old "ad-hoc" method.
|
||||
# manageAppGroups lets automatic signing carry the group.cat.voice.VoiceCat capability
|
||||
# (used by the ReplayKit broadcast extension) into the generated profiles.
|
||||
EXPORT_PLIST="$OUT/ExportOptions.plist"
|
||||
cat > "$EXPORT_PLIST" <<PLIST
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>method</key> <string>release-testing</string>
|
||||
<key>teamID</key> <string>$TEAM_ID</string>
|
||||
<key>signingStyle</key> <string>automatic</string>
|
||||
<key>manageAppGroups</key> <true/>
|
||||
<key>stripSwiftSymbols</key> <true/>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
|
||||
vc_step "xcodebuild -exportArchive (method=release-testing / ad-hoc)"
|
||||
xcodebuild -exportArchive \
|
||||
-archivePath "$ARCHIVE" \
|
||||
-exportPath "$EXPORT_DIR" \
|
||||
-exportOptionsPlist "$EXPORT_PLIST" \
|
||||
-allowProvisioningUpdates \
|
||||
-authenticationKeyPath "$KEY_PATH" \
|
||||
-authenticationKeyID "$KEY_ID" \
|
||||
-authenticationKeyIssuerID "$ISSUER_ID"
|
||||
|
||||
EXPORTED_IPA="$(find "$EXPORT_DIR" -maxdepth 1 -name '*.ipa' | head -n1)"
|
||||
[[ -n "$EXPORTED_IPA" ]] || vc_die "no .ipa found in $EXPORT_DIR"
|
||||
mv "$EXPORTED_IPA" "$IPA"
|
||||
vc_ok "exported -> $IPA ($(du -h "$IPA" | cut -f1))"
|
||||
|
||||
# ── Step 5: read version + generate manifest.plist & index.html ─────────────────────
|
||||
APP_IN_ARCHIVE="$(find "$ARCHIVE/Products/Applications" -maxdepth 1 -name '*.app' | head -n1)"
|
||||
SHORT_VER="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' \
|
||||
"$APP_IN_ARCHIVE/Info.plist" 2>/dev/null || echo "1.0")"
|
||||
|
||||
MANIFEST="$OUT/manifest.plist"
|
||||
cat > "$MANIFEST" <<PLIST
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>items</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>assets</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>kind</key> <string>software-package</string>
|
||||
<key>url</key> <string>$BASE_URL/VoiceCatiOS.ipa</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>metadata</key>
|
||||
<dict>
|
||||
<key>bundle-identifier</key> <string>$APP_BUNDLE_ID</string>
|
||||
<key>bundle-version</key> <string>$SHORT_VER</string>
|
||||
<key>kind</key> <string>software</string>
|
||||
<key>title</key> <string>$APP_TITLE</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
|
||||
INDEX="$OUT/index.html"
|
||||
cat > "$INDEX" <<HTML
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Install $APP_TITLE</title>
|
||||
<style>
|
||||
body { font: 17px -apple-system, system-ui, sans-serif; margin: 0; padding: 2.5rem 1.5rem;
|
||||
color: #111; background: #f5f5f7; -webkit-text-size-adjust: 100%; }
|
||||
main { max-width: 30rem; margin: 0 auto; }
|
||||
h1 { font-size: 1.6rem; }
|
||||
.btn { display: block; text-align: center; text-decoration: none; margin: 1.5rem 0;
|
||||
padding: 0.9rem 1rem; border-radius: 0.8rem; background: #0a84ff; color: #fff;
|
||||
font-weight: 600; }
|
||||
.note { font-size: 0.9rem; color: #555; line-height: 1.5; }
|
||||
code { background: #e5e5ea; padding: 0.1rem 0.3rem; border-radius: 0.3rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>$APP_TITLE — test build</h1>
|
||||
<p>Tap below on your <strong>iPhone</strong> to install (version $SHORT_VER).</p>
|
||||
<a class="btn" href="itms-services://?action=download-manifest&url=$BASE_URL/manifest.plist">
|
||||
Install $APP_TITLE
|
||||
</a>
|
||||
<p class="note">
|
||||
Only works on devices whose UDID was registered for this build, on iOS 18 or later.
|
||||
If nothing happens, make sure you opened this page in <strong>Safari</strong> and that
|
||||
it is served over <strong>HTTPS</strong>. After installing, the app launches normally —
|
||||
no "trust developer" step is needed.
|
||||
</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
|
||||
# ── Done ────────────────────────────────────────────────────────────────────────────
|
||||
vc_ok "staged install files in $OUT:"
|
||||
printf ' %s\n' "VoiceCatiOS.ipa" "manifest.plist" "index.html"
|
||||
echo
|
||||
if [[ "$BASE_URL" == "__BASE_URL__" ]]; then
|
||||
vc_log "NEXT: replace __BASE_URL__ in manifest.plist and index.html with your HTTPS"
|
||||
vc_log " folder URL, then upload all three files there."
|
||||
else
|
||||
vc_log "NEXT: upload the three files above to $BASE_URL/"
|
||||
vc_log " then open $BASE_URL/index.html in Safari on a registered iPhone."
|
||||
fi
|
||||
@@ -0,0 +1,17 @@
|
||||
[CmdletBinding(SupportsShouldProcess)]
|
||||
param(
|
||||
[string[]]$Runtime = @('win-x64', 'linux-x64'),
|
||||
[string]$Output
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if ($Runtime.Count -eq 0) { throw 'At least one runtime must be specified.' }
|
||||
if ($Output -and $Runtime.Count -ne 1) { throw '-Output can only be used when publishing one runtime.' }
|
||||
|
||||
foreach ($targetRuntime in $Runtime) {
|
||||
$targetOutput = if ($Output) { $Output } else { "$PSScriptRoot/../artifacts/server/$targetRuntime" }
|
||||
if (-not $PSCmdlet.ShouldProcess($targetOutput, "Publish VoiceCat.Server for $targetRuntime")) { continue }
|
||||
|
||||
dotnet publish "$PSScriptRoot/../src/VoiceCat.Server/VoiceCat.Server.csproj" -c Release -r $targetRuntime --self-contained true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:PublishTrimmed=false -p:RestoreLockedMode=true "-p:NuGetLockFilePath=packages.publish.$targetRuntime.lock.json" -o $targetOutput
|
||||
if ($LASTEXITCODE -ne 0) { throw "Managed server publish failed for $targetRuntime." }
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# run-ios-simulator.sh — install and launch VoiceCatiOS on an iPhone simulator.
|
||||
#
|
||||
# Picks a simulator in this order:
|
||||
# 1. Any already-booted iPhone simulator.
|
||||
# 2. The first available iPhone simulator (boots it, opens Simulator.app).
|
||||
# Pass --device or --udid to pin a specific device.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/run-ios-simulator.sh # install + launch (Debug build)
|
||||
# scripts/run-ios-simulator.sh --config Release
|
||||
# scripts/run-ios-simulator.sh --device "iPhone 16 Pro"
|
||||
# scripts/run-ios-simulator.sh --udid <UUID>
|
||||
# scripts/run-ios-simulator.sh --build # run build-ios-client.sh first
|
||||
# scripts/run-ios-simulator.sh --log # stream app logs after launch
|
||||
# scripts/run-ios-simulator.sh -h|--help
|
||||
#
|
||||
# The app is installed via `xcrun simctl install` and launched via
|
||||
# `xcrun simctl launch`. With --log, log lines stream to stdout until Ctrl+C
|
||||
# (this does not kill the app).
|
||||
#
|
||||
# Bundle ID: cat.voice.VoiceCatiOS
|
||||
# macOS only. Requires Xcode.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VC_SCRIPT_NAME="run-ios-simulator"
|
||||
source "$SCRIPT_DIR/common.sh"
|
||||
|
||||
XCODE_CONFIG="Debug"
|
||||
DEVICE_FILTER=""
|
||||
DEVICE_UDID=""
|
||||
DO_BUILD=false
|
||||
DO_LOG=false
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--config) XCODE_CONFIG="$2"; shift 2 ;;
|
||||
--device) DEVICE_FILTER="$2"; shift 2 ;;
|
||||
--udid) DEVICE_UDID="$2"; shift 2 ;;
|
||||
--build) DO_BUILD=true; shift ;;
|
||||
--log) DO_LOG=true; shift ;;
|
||||
-h|--help) vc_print_help "$0"; exit 0 ;;
|
||||
*) vc_die "unknown arg: $1 (try --help)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
vc_require_macos
|
||||
|
||||
# ── Optionally build first ─────────────────────────────────────────────────────
|
||||
if $DO_BUILD; then
|
||||
vc_step "building VoiceCatiOS"
|
||||
"$SCRIPT_DIR/build-ios-client.sh" --config "$XCODE_CONFIG"
|
||||
fi
|
||||
|
||||
# ── Locate the .app ───────────────────────────────────────────────────────────
|
||||
APP="$VC_REPO_ROOT/clients/apple/iOS/build/${XCODE_CONFIG}-iphonesimulator/VoiceCatiOS.app"
|
||||
if [[ ! -d "$APP" ]]; then
|
||||
vc_err "VoiceCatiOS.app not found at:"
|
||||
vc_err " $APP"
|
||||
vc_die "build it first with: scripts/build-ios-client.sh (or pass --build)"
|
||||
fi
|
||||
vc_log "app: $APP"
|
||||
|
||||
# ── Find a simulator ──────────────────────────────────────────────────────────
|
||||
# Resolve: explicit UDID > explicit name filter > booted iPhone > any iPhone.
|
||||
find_sim() {
|
||||
# Each line from simctl looks like:
|
||||
# " iPhone 16 (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX) (Shutdown)"
|
||||
# The || true at the end prevents set -e from killing the script when no
|
||||
# matching device exists (grep -oE exits 1 on no match).
|
||||
local filter="${1:-}" state="${2:-}"
|
||||
local pattern="iPhone"
|
||||
[[ -n "$filter" ]] && pattern="$filter"
|
||||
xcrun simctl list devices available 2>/dev/null \
|
||||
| grep -E "$pattern" \
|
||||
| { [[ -n "$state" ]] && grep "$state" || cat; } \
|
||||
| head -1 \
|
||||
| grep -oE '[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}' \
|
||||
| head -1 || true
|
||||
}
|
||||
|
||||
sim_name() {
|
||||
xcrun simctl list devices available 2>/dev/null \
|
||||
| grep "$1" | head -1 \
|
||||
| sed 's/[[:space:]]*('"$1"').*//' | sed 's/^[[:space:]]*//' || true
|
||||
}
|
||||
|
||||
if [[ -n "$DEVICE_UDID" ]]; then
|
||||
UDID="$DEVICE_UDID"
|
||||
elif [[ -n "$DEVICE_FILTER" ]]; then
|
||||
UDID="$(find_sim "$DEVICE_FILTER")"
|
||||
[[ -n "$UDID" ]] || vc_die "no available simulator matching '$DEVICE_FILTER'"
|
||||
else
|
||||
# Prefer a booted simulator; boot one if none is active.
|
||||
UDID="$(find_sim "" "Booted")"
|
||||
if [[ -z "$UDID" ]]; then
|
||||
UDID="$(find_sim "")"
|
||||
[[ -n "$UDID" ]] || vc_die "no iPhone simulator found.
|
||||
Install an iOS runtime: Xcode > Settings > Platforms > iOS."
|
||||
NAME="$(sim_name "$UDID")"
|
||||
vc_step "booting simulator: $NAME ($UDID)"
|
||||
xcrun simctl boot "$UDID"
|
||||
open -a Simulator
|
||||
# Wait for the simulator to finish booting before installing.
|
||||
vc_log "waiting for simulator to boot…"
|
||||
xcrun simctl bootstatus "$UDID" -b
|
||||
fi
|
||||
fi
|
||||
|
||||
NAME="$(sim_name "$UDID")"
|
||||
vc_log "simulator: $NAME ($UDID)"
|
||||
|
||||
# Bring Simulator.app to the foreground (no-op if already open).
|
||||
open -a Simulator
|
||||
|
||||
# ── Install ───────────────────────────────────────────────────────────────────
|
||||
vc_step "installing VoiceCatiOS"
|
||||
xcrun simctl install "$UDID" "$APP"
|
||||
vc_ok "installed -> $UDID"
|
||||
|
||||
# ── Launch ────────────────────────────────────────────────────────────────────
|
||||
BUNDLE_ID="cat.voice.VoiceCatiOS"
|
||||
vc_step "launching $BUNDLE_ID"
|
||||
xcrun simctl launch "$UDID" "$BUNDLE_ID"
|
||||
vc_ok "launched — VoiceCat is running on $NAME"
|
||||
|
||||
# ── Optional log streaming ────────────────────────────────────────────────────
|
||||
if $DO_LOG; then
|
||||
vc_log "streaming logs (Ctrl+C to stop — does not kill the app)"
|
||||
xcrun simctl spawn "$UDID" log stream --predicate "subsystem contains \"VoiceCat\""
|
||||
fi
|
||||
@@ -0,0 +1,41 @@
|
||||
param(
|
||||
[string]$HostName = "127.0.0.1",
|
||||
[int]$Port = 8384,
|
||||
[double]$Minutes = 30,
|
||||
[int]$Pairs = 2,
|
||||
[string]$CliDll = "$PSScriptRoot/../src/VoiceCat.Cli/bin/Release/net10.0/VoiceCat.Cli.dll"
|
||||
)
|
||||
$ErrorActionPreference = "Stop"
|
||||
if ($Minutes -le 0 -or $Pairs -lt 1) { throw "Minutes and Pairs must be positive." }
|
||||
$tempRoot = [IO.Path]::GetFullPath([IO.Path]::GetTempPath())
|
||||
$work = [IO.Path]::GetFullPath((Join-Path $tempRoot ("voicecat-soak-" + [Guid]::NewGuid().ToString("N"))))
|
||||
if (-not $work.StartsWith($tempRoot, [StringComparison]::OrdinalIgnoreCase) -or (Split-Path $work -Leaf) -notlike 'voicecat-soak-*') { throw "Unsafe soak workspace path." }
|
||||
[IO.Directory]::CreateDirectory($work) | Out-Null
|
||||
$deadline = [DateTime]::UtcNow.AddMinutes($Minutes)
|
||||
$cycles = 0
|
||||
try {
|
||||
while ([DateTime]::UtcNow -lt $deadline) {
|
||||
$processes = @()
|
||||
for ($pair = 0; $pair -lt $Pairs; $pair++) {
|
||||
$stamp = "$cycles-$pair"
|
||||
foreach ($side in 0,1) {
|
||||
$name = "Soak-$stamp-$side"; $send = "message-$stamp-$side"; $expect = "message-$stamp-$([int](1-$side))"
|
||||
$start = [Diagnostics.ProcessStartInfo]::new("dotnet")
|
||||
$start.UseShellExecute = $false; $start.RedirectStandardOutput = $true; $start.RedirectStandardError = $true; $start.CreateNoWindow = $true
|
||||
$arguments = @($CliDll,"--host",$HostName,"--port","$Port","--nickname",$name,"--pins",(Join-Path $work "$name.pins"),"--trust-first","--voice","--expect-voice","--send-text",$send,"--expect-text",$expect,"--start-delay-ms","1000","--timeout-seconds","20")
|
||||
$start.Arguments = ($arguments | ForEach-Object { '"' + $_.Replace('"','\"') + '"' }) -join ' '
|
||||
$processes += [Diagnostics.Process]::Start($start)
|
||||
}
|
||||
}
|
||||
foreach ($process in $processes) {
|
||||
$stdout = $process.StandardOutput.ReadToEndAsync(); $stderr = $process.StandardError.ReadToEndAsync()
|
||||
if (-not $process.WaitForExit(30000)) { $process.Kill($true); throw "Soak client timed out." }
|
||||
if ($process.ExitCode -ne 0) { throw "Soak client failed: $($stderr.Result) $($stdout.Result)" }
|
||||
$process.Dispose()
|
||||
}
|
||||
$cycles++
|
||||
Write-Host "Completed soak cycle $cycles ($($Pairs * 2) clients)."
|
||||
}
|
||||
Write-Host "Soak passed: $cycles cycles, $($cycles * $Pairs * 2) client sessions."
|
||||
}
|
||||
finally { if (Test-Path $work) { Remove-Item -LiteralPath $work -Recurse -Force } }
|
||||
Reference in New Issue
Block a user