build.sh
bash
sha256:bd1eb24434d281fe65142038c1bf217493421f5a86880c22dff7e6037d2d4700
feat(companion): add production macOS distribution path
Human
minor
⚠ breaking
25 days ago
| 1 | #!/bin/bash |
| 2 | set -euo pipefail |
| 3 | |
| 4 | repo_root="$(cd "$(dirname "$0")/../../.." && pwd)" |
| 5 | configuration="release" |
| 6 | input_dir="$repo_root/build/release-inputs" |
| 7 | output_dir="$repo_root/build/phase7" |
| 8 | |
| 9 | while [[ $# -gt 0 ]]; do |
| 10 | case "$1" in |
| 11 | --configuration) configuration="$2"; shift 2 ;; |
| 12 | --inputs) input_dir="$2"; shift 2 ;; |
| 13 | --output) output_dir="$2"; shift 2 ;; |
| 14 | *) echo "unknown argument: $1" >&2; exit 2 ;; |
| 15 | esac |
| 16 | done |
| 17 | |
| 18 | if [[ "$input_dir" != /* ]]; then input_dir="$repo_root/$input_dir"; fi |
| 19 | if [[ "$output_dir" != /* ]]; then output_dir="$repo_root/$output_dir"; fi |
| 20 | |
| 21 | [[ "$(uname -s)" == "Darwin" ]] || { echo "macOS build host required" >&2; exit 1; } |
| 22 | [[ "$(uname -m)" == "arm64" ]] || { echo "arm64 build host required" >&2; exit 1; } |
| 23 | [[ -x "$input_dir/node/bin/node" ]] || { echo "verified Node input missing" >&2; exit 1; } |
| 24 | [[ -f "$input_dir/model/onnx/model_quantized.onnx" ]] || { echo "verified model input missing" >&2; exit 1; } |
| 25 | [[ -f "$repo_root/companion/runtime/package-lock.json" ]] || { echo "runtime package lock missing" >&2; exit 1; } |
| 26 | if [[ "${KNOWTATION_ALLOW_UNCOMMITTED_BUILD:-}" != "1" ]]; then |
| 27 | [[ "${KNOWTATION_SOURCE_MUSE_REVISION:-}" =~ ^sha256:[0-9a-f]{64}$ ]] || { |
| 28 | echo "exact KNOWTATION_SOURCE_MUSE_REVISION is required" >&2; exit 1; |
| 29 | } |
| 30 | [[ "${KNOWTATION_SOURCE_GIT_REVISION:-}" =~ ^[0-9a-f]{40}$ ]] || { |
| 31 | echo "exact KNOWTATION_SOURCE_GIT_REVISION is required" >&2; exit 1; |
| 32 | } |
| 33 | fi |
| 34 | |
| 35 | rm -rf "$output_dir" |
| 36 | mkdir -p "$output_dir/clang-cache" "$output_dir/swiftpm-cache" |
| 37 | export CLANG_MODULE_CACHE_PATH="$output_dir/clang-cache" |
| 38 | export SWIFTPM_MODULECACHE_OVERRIDE="$output_dir/swiftpm-cache" |
| 39 | |
| 40 | swift build \ |
| 41 | --disable-sandbox \ |
| 42 | --package-path "$repo_root/companion/macos" \ |
| 43 | --configuration "$configuration" \ |
| 44 | --scratch-path "$output_dir/swift" |
| 45 | bin_dir="$(swift build --disable-sandbox --package-path "$repo_root/companion/macos" --configuration "$configuration" --scratch-path "$output_dir/swift" --show-bin-path)" |
| 46 | |
| 47 | app="$output_dir/unsigned/Knowtation.app" |
| 48 | resources="$app/Contents/Resources" |
| 49 | runtime="$resources/runtime" |
| 50 | custody_app="$app/Contents/Library/LaunchServices/KnowtationCustodyAgent.app" |
| 51 | mkdir -p \ |
| 52 | "$app/Contents/MacOS" \ |
| 53 | "$app/Contents/Helpers" \ |
| 54 | "$app/Contents/Library/LaunchAgents" \ |
| 55 | "$custody_app/Contents/MacOS" \ |
| 56 | "$runtime" |
| 57 | |
| 58 | cp "$repo_root/release/macos/plists/App-Info.plist" "$app/Contents/Info.plist" |
| 59 | cp "$repo_root/release/macos/plists/CustodyAgent-Info.plist" "$custody_app/Contents/Info.plist" |
| 60 | cp "$repo_root/release/macos/plists/LaunchAgent.plist" "$app/Contents/Library/LaunchAgents/store.knowtation.companion.custody.plist" |
| 61 | cp "$bin_dir/Knowtation" "$app/Contents/MacOS/Knowtation" |
| 62 | cp "$bin_dir/KnowtationCustodyAgent" "$custody_app/Contents/MacOS/KnowtationCustodyAgent" |
| 63 | cp "$bin_dir/knowtation" "$app/Contents/Helpers/knowtation" |
| 64 | cp "$bin_dir/knowtation-mcp" "$app/Contents/Helpers/knowtation-mcp" |
| 65 | |
| 66 | ditto "$input_dir/node" "$runtime/node" |
| 67 | # The installed runtime executes only the fixed Node binary. Package managers, |
| 68 | # development headers, and their symlinks are build inputs and are not shipped. |
| 69 | rm -f "$runtime/node/bin/corepack" "$runtime/node/bin/npm" "$runtime/node/bin/npx" |
| 70 | rm -rf "$runtime/node/include" "$runtime/node/lib/node_modules" "$runtime/node/share" |
| 71 | ditto "$input_dir/model" "$runtime/models/all-MiniLM-L6-v2" |
| 72 | |
| 73 | runtime_spec="$repo_root/release/macos/runtime-files.json" |
| 74 | while IFS= read -r directory; do |
| 75 | mkdir -p "$runtime/$directory" |
| 76 | ditto "$repo_root/$directory" "$runtime/$directory" |
| 77 | done < <(node -e 'const j=require(process.argv[1]); for(const p of j.sourceDirectories) console.log(p)' "$runtime_spec") |
| 78 | |
| 79 | while IFS= read -r file; do |
| 80 | mkdir -p "$(dirname "$runtime/$file")" |
| 81 | cp "$repo_root/$file" "$runtime/$file" |
| 82 | done < <(node -e 'const j=require(process.argv[1]); for(const p of j.sourceFiles) console.log(p)' "$runtime_spec") |
| 83 | |
| 84 | while IFS= read -r directory; do |
| 85 | mkdir -p "$runtime/$directory" |
| 86 | ditto "$repo_root/$directory" "$runtime/$directory" |
| 87 | done < <(node -e 'const j=require(process.argv[1]); for(const p of j.assetDirectories) console.log(p)' "$runtime_spec") |
| 88 | |
| 89 | runtime_package="$output_dir/runtime-package" |
| 90 | mkdir -p "$runtime_package" |
| 91 | cp "$repo_root/companion/runtime/package.json" "$runtime_package/package.json" |
| 92 | cp "$repo_root/companion/runtime/package-lock.json" "$runtime_package/package-lock.json" |
| 93 | runtime_npm="$input_dir/node/bin/npm" |
| 94 | runtime_path="$input_dir/node/bin:/usr/bin:/bin:/usr/sbin:/sbin" |
| 95 | env PATH="$runtime_path" npm_config_cache="$output_dir/npm-cache" \ |
| 96 | "$runtime_npm" --prefix "$runtime_package" ci --omit=dev --ignore-scripts |
| 97 | env PATH="$runtime_path" npm_config_cache="$output_dir/npm-cache" npm_config_build_from_source=true \ |
| 98 | "$runtime_npm" --prefix "$runtime_package" rebuild argon2 better-sqlite3 |
| 99 | rm -rf \ |
| 100 | "$runtime_package/node_modules/.bin" \ |
| 101 | "$runtime_package/node_modules/argon2/prebuilds" \ |
| 102 | "$runtime_package/node_modules/sharp" \ |
| 103 | "$runtime_package/node_modules/onnxruntime-node" \ |
| 104 | "$runtime_package/node_modules/@img" |
| 105 | rm -f "$runtime_package/node_modules/better-sqlite3/build/Release/test_extension.node" |
| 106 | find "$runtime_package/node_modules" -maxdepth 1 -type d -name 'sqlite-vec-*' \ |
| 107 | ! -name 'sqlite-vec-darwin-arm64' -exec rm -rf {} + |
| 108 | ditto "$runtime_package/node_modules" "$runtime/node_modules" |
| 109 | cp "$repo_root/companion/runtime/package-lock.json" "$runtime/release-package-lock.json" |
| 110 | |
| 111 | verify_locked_file() { |
| 112 | local file="$1" |
| 113 | local expected="$2" |
| 114 | [[ -f "$file" ]] || { echo "locked runtime file missing: $file" >&2; exit 1; } |
| 115 | [[ "$(shasum -a 256 "$file" | awk '{print $1}')" == "$expected" ]] || { |
| 116 | echo "locked runtime file digest mismatch: $file" >&2; exit 1; |
| 117 | } |
| 118 | } |
| 119 | while IFS=$'\t' read -r relative expected; do |
| 120 | verify_locked_file "$runtime/$relative" "$expected" |
| 121 | done < <(node -e 'const j=require(process.argv[1]); for(const f of j.runtime.files) console.log(`${f.path}\t${f.sha256}`)' \ |
| 122 | "$repo_root/release/macos/release-lock.json") |
| 123 | while IFS=$'\t' read -r relative expected; do |
| 124 | verify_locked_file "$runtime/models/all-MiniLM-L6-v2/$relative" "$expected" |
| 125 | done < <(node -e 'const j=require(process.argv[1]); for(const f of j.model.files) console.log(`${f.path}\t${f.sha256}`)' \ |
| 126 | "$repo_root/release/macos/release-lock.json") |
| 127 | |
| 128 | if find "$runtime/node_modules" -type d \( -name sharp -o -name 'sharp-*' -o -name 'libvips-*' \) -print -quit | grep -q .; then |
| 129 | echo "excluded sharp/libvips dependency entered runtime closure" >&2 |
| 130 | exit 1 |
| 131 | fi |
| 132 | |
| 133 | for required_native in \ |
| 134 | "$runtime/node_modules/argon2/build/Release/argon2.node" \ |
| 135 | "$runtime/node_modules/better-sqlite3/build/Release/better_sqlite3.node" \ |
| 136 | "$runtime/node_modules/sqlite-vec-darwin-arm64/vec0.dylib"; do |
| 137 | [[ -f "$required_native" ]] || { echo "required arm64 runtime missing: $required_native" >&2; exit 1; } |
| 138 | done |
| 139 | while IFS= read -r -d '' native; do |
| 140 | [[ "$(lipo -archs "$native")" == "arm64" ]] || { |
| 141 | echo "non-arm64 native runtime entered closure: $native" >&2; exit 1; |
| 142 | } |
| 143 | done < <(find "$runtime" -type f \( -name '*.node' -o -name '*.dylib' \) -print0) |
| 144 | |
| 145 | node --experimental-vm-modules --no-warnings \ |
| 146 | "$repo_root/scripts/release/macos/verify-module-closure.mjs" --runtime "$runtime" |
| 147 | |
| 148 | export SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:?SOURCE_DATE_EPOCH is required}" |
| 149 | node "$repo_root/scripts/release/macos/generate-sbom.mjs" \ |
| 150 | --node-modules "$runtime/node_modules" \ |
| 151 | --package-lock "$repo_root/companion/runtime/package-lock.json" \ |
| 152 | --release-lock "$repo_root/release/macos/release-lock.json" \ |
| 153 | --node-license "$input_dir/node/LICENSE" \ |
| 154 | --license-templates "$repo_root/release/macos/licenses" \ |
| 155 | --license-overrides "$repo_root/release/macos/license-overrides.json" \ |
| 156 | --sbom "$resources/sbom.spdx.json" \ |
| 157 | --notices "$resources/THIRD-PARTY-NOTICES.txt" |
| 158 | |
| 159 | cp "$repo_root/release/macos/release-lock.json" "$resources/release-lock.json" |
| 160 | cp "$repo_root/release/macos/update-trust.json" "$resources/update-trust.json" |
| 161 | chmod 755 \ |
| 162 | "$app/Contents/MacOS/Knowtation" \ |
| 163 | "$app/Contents/Helpers/knowtation" \ |
| 164 | "$app/Contents/Helpers/knowtation-mcp" \ |
| 165 | "$custody_app/Contents/MacOS/KnowtationCustodyAgent" \ |
| 166 | "$runtime/node/bin/node" |
| 167 | |
| 168 | node "$repo_root/scripts/release/macos/generate-manifest.mjs" \ |
| 169 | --stage unsigned \ |
| 170 | --app "$app" \ |
| 171 | --output "$resources/runtime-manifest.json" |
| 172 | mkdir -p "$output_dir/manifests" |
| 173 | cp "$resources/runtime-manifest.json" "$output_dir/manifests/unsigned-runtime-manifest.json" |
| 174 | |
| 175 | echo "$app" |
File History
1 commit
sha256:bd1eb24434d281fe65142038c1bf217493421f5a86880c22dff7e6037d2d4700
feat(companion): add production macOS distribution path
Human
minor
⚠
25 days ago