freecode-sh 0.1.0-alpha.12 → 0.1.0-alpha.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/freecode.mjs +127 -340
- package/install.sh +641 -0
- package/package.json +10 -22
- package/THIRD_PARTY_NOTICES.md +0 -115
- package/engine.manifest.json +0 -8
- package/release-manifest.json +0 -38
- package/standalone.tsx +0 -28
package/install.sh
ADDED
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
# Free Code native installer for macOS.
|
|
4
|
+
# Review this script before running it: https://freecode.sh/install
|
|
5
|
+
|
|
6
|
+
set -eu
|
|
7
|
+
|
|
8
|
+
DEFAULT_CHANNEL="alpha"
|
|
9
|
+
DEFAULT_DOWNLOAD_BASE_URL="https://releases.freecode.sh/releases"
|
|
10
|
+
DEFAULT_CHANNEL_BASE_URL="https://releases.freecode.sh/channels"
|
|
11
|
+
EXPECTED_TEAM_IDENTIFIER="GD597P8KJ2"
|
|
12
|
+
LEGACY_ALPHA6_MANIFEST_SHA256="1bf63e0d6114b12d61c07094936bd2606ae82ce87d5b054fbfb01c5f3d3646d8"
|
|
13
|
+
LAUNCHER_RELATIVE_PATH="bin/freecode"
|
|
14
|
+
INTERFACE_RELATIVE_PATH="libexec/freecode/freecode-tui"
|
|
15
|
+
ENGINE_RELATIVE_PATH="libexec/freecode/freecode-engine"
|
|
16
|
+
|
|
17
|
+
VERSION="${FREECODE_INSTALL_VERSION:-}"
|
|
18
|
+
CHANNEL="${FREECODE_INSTALL_CHANNEL:-$DEFAULT_CHANNEL}"
|
|
19
|
+
DOWNLOAD_BASE_URL="${FREECODE_INSTALL_BASE_URL:-$DEFAULT_DOWNLOAD_BASE_URL}"
|
|
20
|
+
CHANNEL_BASE_URL="${FREECODE_CHANNEL_BASE_URL:-$DEFAULT_CHANNEL_BASE_URL}"
|
|
21
|
+
INSTALL_ROOT="${FREECODE_INSTALL_ROOT:-${HOME:?HOME is required}/.local/share/freecode}"
|
|
22
|
+
BIN_DIR="${FREECODE_BIN_DIR:-${HOME}/.local/bin}"
|
|
23
|
+
OS_NAME="${FREECODE_INSTALL_OS:-$(uname -s)}"
|
|
24
|
+
MACHINE="${FREECODE_INSTALL_ARCH:-$(uname -m)}"
|
|
25
|
+
TEMP_DIR=""
|
|
26
|
+
STAGE_DIR=""
|
|
27
|
+
INSTALL_LOCK_PATH=""
|
|
28
|
+
INSTALL_LOCK_HELD="false"
|
|
29
|
+
INSTALL_LOCK_OWNER_TOKEN=""
|
|
30
|
+
INSTALL_LOCK_GUARD_HELD="false"
|
|
31
|
+
ALLOW_UNSIGNED_INSTALL="${FREECODE_ALLOW_UNSIGNED_INSTALL:-false}"
|
|
32
|
+
EMBEDDED_INSTALL="${FREECODE_INSTALL_EMBEDDED:-false}"
|
|
33
|
+
LAUNCH_AFTER_INSTALL="${FREECODE_INSTALL_LAUNCH:-true}"
|
|
34
|
+
RELEASE_MODE=""
|
|
35
|
+
PATH_PROFILE=""
|
|
36
|
+
PATH_CONFIGURED="false"
|
|
37
|
+
|
|
38
|
+
usage() {
|
|
39
|
+
cat <<'EOF'
|
|
40
|
+
Install Free Code's native macOS release.
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
curl -fsSL https://freecode.sh/install.sh | sh
|
|
44
|
+
curl -fsSL https://freecode.sh/install.sh | sh -s -- --version 0.1.0-alpha.1
|
|
45
|
+
|
|
46
|
+
Options:
|
|
47
|
+
--version VERSION Install an exact immutable version.
|
|
48
|
+
--channel CHANNEL Resolve a channel (default: alpha).
|
|
49
|
+
--base-url URL Override the immutable release download base URL.
|
|
50
|
+
--channel-base-url URL Override the channel manifest base URL.
|
|
51
|
+
--install-root PATH Version store (default: ~/.local/share/freecode).
|
|
52
|
+
--bin-dir PATH Executable link directory (default: ~/.local/bin).
|
|
53
|
+
--no-launch Install without opening Free Code.
|
|
54
|
+
-h, --help Show this help.
|
|
55
|
+
|
|
56
|
+
Environment equivalents:
|
|
57
|
+
FREECODE_INSTALL_VERSION, FREECODE_INSTALL_CHANNEL,
|
|
58
|
+
FREECODE_INSTALL_BASE_URL, FREECODE_CHANNEL_BASE_URL,
|
|
59
|
+
FREECODE_INSTALL_ROOT, FREECODE_BIN_DIR, FREECODE_INSTALL_LAUNCH
|
|
60
|
+
|
|
61
|
+
Internal testing only:
|
|
62
|
+
FREECODE_ALLOW_UNSIGNED_INSTALL=true permits an explicitly marked unsigned
|
|
63
|
+
release outside the alpha channel. Never use this for a normal install.
|
|
64
|
+
EOF
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fail() {
|
|
68
|
+
printf 'freecode installer: %s\n' "$*" >&2
|
|
69
|
+
exit 1
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
cleanup() {
|
|
73
|
+
if [ -n "$TEMP_DIR" ] && [ -d "$TEMP_DIR" ]; then
|
|
74
|
+
rm -rf "$TEMP_DIR"
|
|
75
|
+
fi
|
|
76
|
+
if [ -n "$STAGE_DIR" ] && [ -d "$STAGE_DIR" ]; then
|
|
77
|
+
rm -rf "$STAGE_DIR"
|
|
78
|
+
fi
|
|
79
|
+
release_install_lock
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
require_value() {
|
|
83
|
+
[ "$#" -ge 2 ] || fail "$1 requires a value"
|
|
84
|
+
[ -n "$2" ] || fail "$1 requires a non-empty value"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
release_install_lock() {
|
|
88
|
+
if [ "$INSTALL_LOCK_HELD" = "true" ]; then
|
|
89
|
+
lock_owner=$(sed -n '1p' "$INSTALL_LOCK_PATH" 2>/dev/null || true)
|
|
90
|
+
lock_owner_token=$(sed -n '2p' "$INSTALL_LOCK_PATH" 2>/dev/null || true)
|
|
91
|
+
if [ "$lock_owner" = "$$" ] && [ "$lock_owner_token" = "$INSTALL_LOCK_OWNER_TOKEN" ]; then
|
|
92
|
+
rm -f "$INSTALL_LOCK_PATH"
|
|
93
|
+
fi
|
|
94
|
+
fi
|
|
95
|
+
INSTALL_LOCK_HELD="false"
|
|
96
|
+
if [ "$INSTALL_LOCK_GUARD_HELD" = "true" ]; then
|
|
97
|
+
exec 9>&-
|
|
98
|
+
INSTALL_LOCK_GUARD_HELD="false"
|
|
99
|
+
fi
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
create_install_lock() {
|
|
103
|
+
(
|
|
104
|
+
umask 077
|
|
105
|
+
set -C
|
|
106
|
+
printf '%s\n%s\n' "$$" "$INSTALL_LOCK_OWNER_TOKEN" > "$INSTALL_LOCK_PATH"
|
|
107
|
+
) 2>/dev/null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
process_start_token() {
|
|
111
|
+
LC_ALL=C ps -o lstart= -p "$1" 2>/dev/null \
|
|
112
|
+
| sed -n '1{s/^[[:space:]]*//;s/[[:space:]]*$//;p;}'
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
acquire_install_lock() {
|
|
116
|
+
INSTALL_LOCK_PATH="$INSTALL_ROOT/.update-lock"
|
|
117
|
+
INSTALL_LOCK_OWNER_TOKEN=$(process_start_token "$$")
|
|
118
|
+
[ -n "$INSTALL_LOCK_OWNER_TOKEN" ] \
|
|
119
|
+
|| fail "could not identify the installer process for update locking"
|
|
120
|
+
mkdir -p "$INSTALL_ROOT"
|
|
121
|
+
exec 9>"$INSTALL_LOCK_PATH.guard"
|
|
122
|
+
# Descriptor-mode locks remain attached to fd 9 after the helper exits and
|
|
123
|
+
# are held until release_install_lock closes the descriptor.
|
|
124
|
+
if [ -x /usr/bin/lockf ]; then
|
|
125
|
+
guard_acquired=false
|
|
126
|
+
/usr/bin/lockf -s -t 120 9 && guard_acquired=true
|
|
127
|
+
elif [ "$(uname -s)" = "Linux" ] && command -v flock >/dev/null 2>&1; then
|
|
128
|
+
# The Linux CI host emulates Darwin to exercise this macOS-only installer.
|
|
129
|
+
guard_acquired=false
|
|
130
|
+
flock -w 120 9 && guard_acquired=true
|
|
131
|
+
else
|
|
132
|
+
exec 9>&-
|
|
133
|
+
fail "the system lock utility is unavailable"
|
|
134
|
+
fi
|
|
135
|
+
if [ "$guard_acquired" != "true" ]; then
|
|
136
|
+
exec 9>&-
|
|
137
|
+
fail "another Free Code install or update is still in progress"
|
|
138
|
+
fi
|
|
139
|
+
INSTALL_LOCK_GUARD_HELD="true"
|
|
140
|
+
attempts=0
|
|
141
|
+
while ! create_install_lock; do
|
|
142
|
+
# Read both the regular-file protocol and the short-lived directory format
|
|
143
|
+
# used by development builds before the lock contract was unified.
|
|
144
|
+
if [ -d "$INSTALL_LOCK_PATH" ]; then
|
|
145
|
+
lock_owner=$(sed -n '1p' "$INSTALL_LOCK_PATH/pid" 2>/dev/null || true)
|
|
146
|
+
lock_owner_token=""
|
|
147
|
+
else
|
|
148
|
+
lock_owner=$(sed -n '1p' "$INSTALL_LOCK_PATH" 2>/dev/null || true)
|
|
149
|
+
lock_owner_token=$(sed -n '2p' "$INSTALL_LOCK_PATH" 2>/dev/null || true)
|
|
150
|
+
fi
|
|
151
|
+
case "$lock_owner" in
|
|
152
|
+
''|*[!0-9]*) ;;
|
|
153
|
+
*)
|
|
154
|
+
if ! kill -0 "$lock_owner" 2>/dev/null; then
|
|
155
|
+
rm -rf "$INSTALL_LOCK_PATH"
|
|
156
|
+
continue
|
|
157
|
+
fi
|
|
158
|
+
if [ -n "$lock_owner_token" ]; then
|
|
159
|
+
current_owner_token=$(process_start_token "$lock_owner")
|
|
160
|
+
if [ -n "$current_owner_token" ] && [ "$current_owner_token" != "$lock_owner_token" ]; then
|
|
161
|
+
rm -rf "$INSTALL_LOCK_PATH"
|
|
162
|
+
continue
|
|
163
|
+
fi
|
|
164
|
+
fi
|
|
165
|
+
;;
|
|
166
|
+
esac
|
|
167
|
+
attempts=$((attempts + 1))
|
|
168
|
+
[ "$attempts" -lt 120 ] \
|
|
169
|
+
|| fail "another Free Code install or update is still in progress"
|
|
170
|
+
sleep 1
|
|
171
|
+
done
|
|
172
|
+
INSTALL_LOCK_HELD="true"
|
|
173
|
+
|
|
174
|
+
if [ -n "${FREECODE_TEST_HOLD_INSTALL_LOCK_SECONDS:-}" ]; then
|
|
175
|
+
case "$DOWNLOAD_BASE_URL" in
|
|
176
|
+
file://*) sleep "$FREECODE_TEST_HOLD_INSTALL_LOCK_SECONDS" ;;
|
|
177
|
+
*) fail "the test install delay is permitted only with local file URLs" ;;
|
|
178
|
+
esac
|
|
179
|
+
fi
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
configure_profile() {
|
|
183
|
+
profile=$1
|
|
184
|
+
path_line=$2
|
|
185
|
+
legacy_path_line=${3:-}
|
|
186
|
+
profile_dir=$(dirname "$profile")
|
|
187
|
+
|
|
188
|
+
[ -d "$profile_dir" ] || return 1
|
|
189
|
+
if [ -e "$profile" ] || [ -L "$profile" ]; then
|
|
190
|
+
[ -f "$profile" ] || return 1
|
|
191
|
+
fi
|
|
192
|
+
if grep -Fqx "$path_line" "$profile" 2>/dev/null \
|
|
193
|
+
|| { [ -n "$legacy_path_line" ] && grep -Fqx "$legacy_path_line" "$profile" 2>/dev/null; }
|
|
194
|
+
then
|
|
195
|
+
return 0
|
|
196
|
+
fi
|
|
197
|
+
if [ -s "$profile" ] && ! printf '\n' >> "$profile"; then
|
|
198
|
+
return 1
|
|
199
|
+
fi
|
|
200
|
+
printf '# Free Code\n%s\n' "$path_line" >> "$profile"
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
configure_path() {
|
|
204
|
+
[ "$BIN_DIR" = "$HOME/.local/bin" ] || return 0
|
|
205
|
+
|
|
206
|
+
guarded_path_line='case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) export PATH="$HOME/.local/bin:$PATH" ;; esac'
|
|
207
|
+
legacy_path_line='export PATH="$HOME/.local/bin:$PATH"'
|
|
208
|
+
detected_shell=${SHELL:-/bin/zsh}
|
|
209
|
+
|
|
210
|
+
case "$detected_shell" in
|
|
211
|
+
*/zsh)
|
|
212
|
+
zsh_config_dir=${ZDOTDIR:-$HOME}
|
|
213
|
+
if configure_profile "$zsh_config_dir/.zprofile" "$guarded_path_line" "$legacy_path_line" \
|
|
214
|
+
&& configure_profile "$zsh_config_dir/.zshrc" "$guarded_path_line" "$legacy_path_line"
|
|
215
|
+
then
|
|
216
|
+
PATH_PROFILE="$zsh_config_dir/.zprofile and $zsh_config_dir/.zshrc"
|
|
217
|
+
PATH_CONFIGURED="true"
|
|
218
|
+
fi
|
|
219
|
+
;;
|
|
220
|
+
*/bash)
|
|
221
|
+
if [ -e "$HOME/.bash_profile" ] || [ -L "$HOME/.bash_profile" ]; then
|
|
222
|
+
bash_login_profile="$HOME/.bash_profile"
|
|
223
|
+
elif [ -e "$HOME/.bash_login" ] || [ -L "$HOME/.bash_login" ]; then
|
|
224
|
+
bash_login_profile="$HOME/.bash_login"
|
|
225
|
+
elif [ -e "$HOME/.profile" ] || [ -L "$HOME/.profile" ]; then
|
|
226
|
+
bash_login_profile="$HOME/.profile"
|
|
227
|
+
else
|
|
228
|
+
bash_login_profile="$HOME/.bash_profile"
|
|
229
|
+
fi
|
|
230
|
+
if configure_profile "$bash_login_profile" "$guarded_path_line" "$legacy_path_line" \
|
|
231
|
+
&& configure_profile "$HOME/.bashrc" "$guarded_path_line" "$legacy_path_line"
|
|
232
|
+
then
|
|
233
|
+
PATH_PROFILE="$bash_login_profile and $HOME/.bashrc"
|
|
234
|
+
PATH_CONFIGURED="true"
|
|
235
|
+
fi
|
|
236
|
+
;;
|
|
237
|
+
*/fish)
|
|
238
|
+
fish_config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/fish/conf.d"
|
|
239
|
+
if mkdir -p "$fish_config_dir" 2>/dev/null \
|
|
240
|
+
&& configure_profile "$fish_config_dir/freecode.fish" \
|
|
241
|
+
'contains -- "$HOME/.local/bin" $PATH; or set -gx PATH "$HOME/.local/bin" $PATH'
|
|
242
|
+
then
|
|
243
|
+
PATH_PROFILE="$fish_config_dir/freecode.fish"
|
|
244
|
+
PATH_CONFIGURED="true"
|
|
245
|
+
fi
|
|
246
|
+
;;
|
|
247
|
+
esac
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
trap cleanup EXIT HUP INT TERM
|
|
251
|
+
|
|
252
|
+
while [ "$#" -gt 0 ]; do
|
|
253
|
+
case "$1" in
|
|
254
|
+
--version)
|
|
255
|
+
require_value "$@"
|
|
256
|
+
VERSION=$2
|
|
257
|
+
shift 2
|
|
258
|
+
;;
|
|
259
|
+
--channel)
|
|
260
|
+
require_value "$@"
|
|
261
|
+
CHANNEL=$2
|
|
262
|
+
shift 2
|
|
263
|
+
;;
|
|
264
|
+
--base-url)
|
|
265
|
+
require_value "$@"
|
|
266
|
+
DOWNLOAD_BASE_URL=$2
|
|
267
|
+
shift 2
|
|
268
|
+
;;
|
|
269
|
+
--channel-base-url)
|
|
270
|
+
require_value "$@"
|
|
271
|
+
CHANNEL_BASE_URL=$2
|
|
272
|
+
shift 2
|
|
273
|
+
;;
|
|
274
|
+
--install-root)
|
|
275
|
+
require_value "$@"
|
|
276
|
+
INSTALL_ROOT=$2
|
|
277
|
+
shift 2
|
|
278
|
+
;;
|
|
279
|
+
--bin-dir)
|
|
280
|
+
require_value "$@"
|
|
281
|
+
BIN_DIR=$2
|
|
282
|
+
shift 2
|
|
283
|
+
;;
|
|
284
|
+
--no-launch)
|
|
285
|
+
LAUNCH_AFTER_INSTALL="false"
|
|
286
|
+
shift
|
|
287
|
+
;;
|
|
288
|
+
-h|--help)
|
|
289
|
+
usage
|
|
290
|
+
exit 0
|
|
291
|
+
;;
|
|
292
|
+
--)
|
|
293
|
+
shift
|
|
294
|
+
break
|
|
295
|
+
;;
|
|
296
|
+
*)
|
|
297
|
+
fail "unknown option: $1"
|
|
298
|
+
;;
|
|
299
|
+
esac
|
|
300
|
+
done
|
|
301
|
+
|
|
302
|
+
[ "$#" -eq 0 ] || fail "unexpected positional arguments: $*"
|
|
303
|
+
command -v curl >/dev/null 2>&1 || fail "curl is required"
|
|
304
|
+
command -v tar >/dev/null 2>&1 || fail "tar is required"
|
|
305
|
+
command -v shasum >/dev/null 2>&1 || fail "shasum is required"
|
|
306
|
+
|
|
307
|
+
case "$ALLOW_UNSIGNED_INSTALL" in
|
|
308
|
+
true|false) ;;
|
|
309
|
+
*) fail "FREECODE_ALLOW_UNSIGNED_INSTALL must be exactly true or false" ;;
|
|
310
|
+
esac
|
|
311
|
+
|
|
312
|
+
case "$EMBEDDED_INSTALL" in
|
|
313
|
+
true|false) ;;
|
|
314
|
+
*) fail "FREECODE_INSTALL_EMBEDDED must be exactly true or false" ;;
|
|
315
|
+
esac
|
|
316
|
+
|
|
317
|
+
case "$LAUNCH_AFTER_INSTALL" in
|
|
318
|
+
true|false) ;;
|
|
319
|
+
*) fail "FREECODE_INSTALL_LAUNCH must be exactly true or false" ;;
|
|
320
|
+
esac
|
|
321
|
+
|
|
322
|
+
case "$OS_NAME" in
|
|
323
|
+
Darwin) ;;
|
|
324
|
+
*) fail "only macOS is supported right now (detected $OS_NAME)" ;;
|
|
325
|
+
esac
|
|
326
|
+
|
|
327
|
+
case "$MACHINE" in
|
|
328
|
+
arm64|aarch64)
|
|
329
|
+
ARCH="arm64"
|
|
330
|
+
;;
|
|
331
|
+
x86_64|amd64)
|
|
332
|
+
# Prefer the native Apple Silicon build when this shell is running under Rosetta.
|
|
333
|
+
if [ "${FREECODE_INSTALL_ARCH:-}" = "" ] && [ "$(sysctl -in sysctl.proc_translated 2>/dev/null || true)" = "1" ]; then
|
|
334
|
+
ARCH="arm64"
|
|
335
|
+
else
|
|
336
|
+
ARCH="x64"
|
|
337
|
+
fi
|
|
338
|
+
;;
|
|
339
|
+
*)
|
|
340
|
+
fail "unsupported Mac architecture: $MACHINE"
|
|
341
|
+
;;
|
|
342
|
+
esac
|
|
343
|
+
|
|
344
|
+
validate_version() {
|
|
345
|
+
printf '%s\n' "$1" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$' \
|
|
346
|
+
|| fail "invalid release version: $1"
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
validate_channel() {
|
|
350
|
+
printf '%s\n' "$1" | grep -Eq '^[0-9A-Za-z][0-9A-Za-z._-]*$' \
|
|
351
|
+
|| fail "invalid release channel: $1"
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
validate_release_security() {
|
|
355
|
+
manifest_path=$1
|
|
356
|
+
context=$2
|
|
357
|
+
release_mode=$(sed -n 's/.*"releaseMode"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$manifest_path" | head -n 1)
|
|
358
|
+
manifest_channel=$(sed -n 's/.*"channel"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$manifest_path" | head -n 1)
|
|
359
|
+
case "$release_mode" in
|
|
360
|
+
signed-notarized)
|
|
361
|
+
RELEASE_MODE="signed-notarized"
|
|
362
|
+
;;
|
|
363
|
+
unsigned-override)
|
|
364
|
+
if [ "$CHANNEL" = "alpha" ] && [ "$manifest_channel" = "alpha" ]; then
|
|
365
|
+
printf 'WARNING: %s is an unsigned Free Code alpha and is not Apple notarized. Its SHA-256 checksum will still be verified.\n' "$context" >&2
|
|
366
|
+
elif [ "$ALLOW_UNSIGNED_INSTALL" = "true" ]; then
|
|
367
|
+
printf 'WARNING: %s declares unsigned-override security metadata outside the alpha channel.\n' "$context" >&2
|
|
368
|
+
else
|
|
369
|
+
fail "$context is unsigned and is not an alpha release; refusing to install it"
|
|
370
|
+
fi
|
|
371
|
+
RELEASE_MODE="unsigned-override"
|
|
372
|
+
;;
|
|
373
|
+
*) fail "$context does not contain valid signed-notarized release security metadata" ;;
|
|
374
|
+
esac
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
verify_release_signatures() {
|
|
378
|
+
root=$1
|
|
379
|
+
if [ "$RELEASE_MODE" = "unsigned-override" ]; then
|
|
380
|
+
printf 'WARNING: skipping Apple signature and Team ID verification for this explicitly marked unsigned release.\n' >&2
|
|
381
|
+
return
|
|
382
|
+
fi
|
|
383
|
+
|
|
384
|
+
[ -x /usr/bin/codesign ] || fail "/usr/bin/codesign is required to verify this release"
|
|
385
|
+
for executable in \
|
|
386
|
+
"$root/$LAUNCHER_RELATIVE_PATH" \
|
|
387
|
+
"$root/$INTERFACE_RELATIVE_PATH" \
|
|
388
|
+
"$root/$ENGINE_RELATIVE_PATH"
|
|
389
|
+
do
|
|
390
|
+
/usr/bin/codesign --verify --strict --verbose=2 "$executable" \
|
|
391
|
+
|| fail "Apple signature verification failed for $executable"
|
|
392
|
+
SIGNATURE_DETAILS=$(/usr/bin/codesign --display --verbose=4 "$executable" 2>&1) \
|
|
393
|
+
|| fail "could not inspect Apple signature for $executable"
|
|
394
|
+
TEAM_IDENTIFIER=$(printf '%s\n' "$SIGNATURE_DETAILS" | sed -n 's/^TeamIdentifier=//p' | head -n 1)
|
|
395
|
+
[ "$TEAM_IDENTIFIER" = "$EXPECTED_TEAM_IDENTIFIER" ] \
|
|
396
|
+
|| fail "unexpected Apple TeamIdentifier for $executable (expected $EXPECTED_TEAM_IDENTIFIER, received ${TEAM_IDENTIFIER:-missing})"
|
|
397
|
+
done
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
write_release_public_key() {
|
|
401
|
+
destination=$1
|
|
402
|
+
if [ -n "${FREECODE_TEST_RELEASE_PUBLIC_KEY_FILE:-}" ]; then
|
|
403
|
+
case "$DOWNLOAD_BASE_URL" in
|
|
404
|
+
file://*) cp "$FREECODE_TEST_RELEASE_PUBLIC_KEY_FILE" "$destination" ;;
|
|
405
|
+
*) fail "the test release key override is permitted only with local file URLs" ;;
|
|
406
|
+
esac
|
|
407
|
+
return
|
|
408
|
+
fi
|
|
409
|
+
cat > "$destination" <<'EOF'
|
|
410
|
+
-----BEGIN PUBLIC KEY-----
|
|
411
|
+
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA8so4sWArOV0GXihYvoSo
|
|
412
|
+
xDLI1yMoILnVfCjYQwe4CKQ9EC10JV30cntMJZWXvXZS6STqQGIjyWisa6Op4nId
|
|
413
|
+
OuXKDjaEcQIeftgZBzzyGrMmAMC3W57VAi3d+w/F2XnkXBaH3NOphfftF6Fxt3Q0
|
|
414
|
+
2CxN3PjuuFaOGytKsYF5uDLCvEb+MpzJlD3HZoBzhhMCAoY6wmCdlgaDxD20UFdy
|
|
415
|
+
EigF5wwpcUWpYmbYMFp+6SLe/Fi8IrYirwiCv5eLur6PzFFcwFrJgUeZAdxmGuM8
|
|
416
|
+
eJzZlc7mK0+gzi8KpC2FZ9eOx1SafPE4An4+u8rZ7WA1Qk5eCAhNU9lm+JAeeKcC
|
|
417
|
+
XzhQpPUafybJg75dIbIayOC3tf5mn+X+Vl5+uY19nT2qcJsjgUeIcqbMs9FGXiIA
|
|
418
|
+
2G292N5WppyiDcx8yAIG6rOrAUGzHMYCrhvxQLAx2KP5QfC53VZ3La7yZOATY5ic
|
|
419
|
+
F78qSSBrKDrT42vinbosbX++Wr34YB6pXhiZF6jHPHrxAgMBAAE=
|
|
420
|
+
-----END PUBLIC KEY-----
|
|
421
|
+
EOF
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
authenticate_release_manifest() {
|
|
425
|
+
manifest_path=$1
|
|
426
|
+
signature_path=$2
|
|
427
|
+
context=$3
|
|
428
|
+
if [ -s "$signature_path" ] && \
|
|
429
|
+
/usr/bin/openssl dgst -sha256 \
|
|
430
|
+
-verify "$RELEASE_PUBLIC_KEY" \
|
|
431
|
+
-signature "$signature_path" \
|
|
432
|
+
"$manifest_path" >/dev/null 2>&1
|
|
433
|
+
then
|
|
434
|
+
return
|
|
435
|
+
fi
|
|
436
|
+
|
|
437
|
+
manifest_sha=$(shasum -a 256 "$manifest_path" | awk '{ print $1 }')
|
|
438
|
+
if [ "$manifest_sha" = "$LEGACY_ALPHA6_MANIFEST_SHA256" ]; then
|
|
439
|
+
printf 'WARNING: %s predates release-manifest signatures; accepting its exact pinned alpha.6 manifest.\n' "$context" >&2
|
|
440
|
+
return
|
|
441
|
+
fi
|
|
442
|
+
fail "$context does not have a valid Free Code release signature"
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
manifest_asset_value() {
|
|
446
|
+
manifest_path=$1
|
|
447
|
+
target=$2
|
|
448
|
+
field=$3
|
|
449
|
+
awk -v target="$target" -v field="$field" '
|
|
450
|
+
index($0, "\"" target "\"") && index($0, ":") { in_target = 1; next }
|
|
451
|
+
in_target && index($0, "\"" field "\"") {
|
|
452
|
+
value = $0
|
|
453
|
+
sub(/^[^:]*:[[:space:]]*/, "", value)
|
|
454
|
+
sub(/,[[:space:]]*$/, "", value)
|
|
455
|
+
sub(/^"/, "", value)
|
|
456
|
+
sub(/"$/, "", value)
|
|
457
|
+
print value
|
|
458
|
+
exit
|
|
459
|
+
}
|
|
460
|
+
in_target && /^[[:space:]]*}[,]?[[:space:]]*$/ { exit }
|
|
461
|
+
' "$manifest_path"
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
DOWNLOAD_BASE_URL=${DOWNLOAD_BASE_URL%/}
|
|
465
|
+
CHANNEL_BASE_URL=${CHANNEL_BASE_URL%/}
|
|
466
|
+
validate_channel "$CHANNEL"
|
|
467
|
+
|
|
468
|
+
TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/freecode-install.XXXXXX")
|
|
469
|
+
RELEASE_PUBLIC_KEY="$TEMP_DIR/release-signing-public.pem"
|
|
470
|
+
write_release_public_key "$RELEASE_PUBLIC_KEY"
|
|
471
|
+
|
|
472
|
+
if [ -z "$VERSION" ]; then
|
|
473
|
+
CHANNEL_MANIFEST="$TEMP_DIR/channel.json"
|
|
474
|
+
CHANNEL_SIGNATURE="$TEMP_DIR/channel.json.sig"
|
|
475
|
+
CHANNEL_URL="$CHANNEL_BASE_URL/$CHANNEL.json"
|
|
476
|
+
printf 'Resolving Free Code channel %s...\n' "$CHANNEL"
|
|
477
|
+
curl -fsSL --retry 3 --connect-timeout 15 "$CHANNEL_URL" -o "$CHANNEL_MANIFEST" \
|
|
478
|
+
|| fail "could not download channel manifest: $CHANNEL_URL"
|
|
479
|
+
curl -fsSL --retry 3 --connect-timeout 15 "$CHANNEL_URL.sig" -o "$CHANNEL_SIGNATURE" 2>/dev/null \
|
|
480
|
+
|| rm -f "$CHANNEL_SIGNATURE"
|
|
481
|
+
authenticate_release_manifest "$CHANNEL_MANIFEST" "$CHANNEL_SIGNATURE" "channel $CHANNEL"
|
|
482
|
+
VERSION=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$CHANNEL_MANIFEST" | head -n 1)
|
|
483
|
+
[ -n "$VERSION" ] || fail "channel manifest does not contain a version"
|
|
484
|
+
validate_release_security "$CHANNEL_MANIFEST" "channel $CHANNEL"
|
|
485
|
+
fi
|
|
486
|
+
validate_version "$VERSION"
|
|
487
|
+
|
|
488
|
+
mkdir -p "$INSTALL_ROOT"
|
|
489
|
+
acquire_install_lock
|
|
490
|
+
|
|
491
|
+
ASSET="freecode-darwin-$ARCH.tar.gz"
|
|
492
|
+
TAG="v$VERSION"
|
|
493
|
+
RELEASE_URL="$DOWNLOAD_BASE_URL/$TAG"
|
|
494
|
+
ARCHIVE="$TEMP_DIR/$ASSET"
|
|
495
|
+
RELEASE_MANIFEST="$TEMP_DIR/release-manifest.json"
|
|
496
|
+
RELEASE_SIGNATURE="$TEMP_DIR/release-manifest.json.sig"
|
|
497
|
+
|
|
498
|
+
curl -fsSL --retry 3 --connect-timeout 15 "$RELEASE_URL/release-manifest.json" -o "$RELEASE_MANIFEST" \
|
|
499
|
+
|| fail "could not download release manifest: $RELEASE_URL/release-manifest.json"
|
|
500
|
+
curl -fsSL --retry 3 --connect-timeout 15 "$RELEASE_URL/release-manifest.json.sig" -o "$RELEASE_SIGNATURE" 2>/dev/null \
|
|
501
|
+
|| rm -f "$RELEASE_SIGNATURE"
|
|
502
|
+
authenticate_release_manifest "$RELEASE_MANIFEST" "$RELEASE_SIGNATURE" "release $VERSION"
|
|
503
|
+
|
|
504
|
+
MANIFEST_VERSION=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$RELEASE_MANIFEST" | head -n 1)
|
|
505
|
+
[ "$MANIFEST_VERSION" = "$VERSION" ] \
|
|
506
|
+
|| fail "release manifest version ${MANIFEST_VERSION:-missing} does not match requested version $VERSION"
|
|
507
|
+
validate_release_security "$RELEASE_MANIFEST" "release $VERSION"
|
|
508
|
+
|
|
509
|
+
MANIFEST_TARGET="darwin-$ARCH"
|
|
510
|
+
MANIFEST_ARCHIVE=$(manifest_asset_value "$RELEASE_MANIFEST" "$MANIFEST_TARGET" archive)
|
|
511
|
+
[ "$MANIFEST_ARCHIVE" = "$ASSET" ] \
|
|
512
|
+
|| fail "release manifest does not declare the expected archive $ASSET"
|
|
513
|
+
EXPECTED_SHA=$(manifest_asset_value "$RELEASE_MANIFEST" "$MANIFEST_TARGET" sha256)
|
|
514
|
+
printf '%s\n' "$EXPECTED_SHA" | grep -Eq '^[0-9a-fA-F]{64}$' \
|
|
515
|
+
|| fail "release checksum is not a SHA-256 digest"
|
|
516
|
+
EXPECTED_SIZE=$(manifest_asset_value "$RELEASE_MANIFEST" "$MANIFEST_TARGET" size)
|
|
517
|
+
printf '%s\n' "$EXPECTED_SIZE" | grep -Eq '^[1-9][0-9]*$' \
|
|
518
|
+
|| fail "release manifest does not contain a valid archive size"
|
|
519
|
+
|
|
520
|
+
printf 'Downloading Free Code %s for darwin-%s...\n' "$VERSION" "$ARCH"
|
|
521
|
+
curl -fsSL --retry 3 --connect-timeout 15 "$RELEASE_URL/$ASSET" -o "$ARCHIVE" \
|
|
522
|
+
|| fail "could not download release asset: $RELEASE_URL/$ASSET"
|
|
523
|
+
ACTUAL_SIZE=$(wc -c < "$ARCHIVE" | tr -d '[:space:]')
|
|
524
|
+
[ "$ACTUAL_SIZE" = "$EXPECTED_SIZE" ] \
|
|
525
|
+
|| fail "download size verification failed for $ASSET"
|
|
526
|
+
ACTUAL_SHA=$(shasum -a 256 "$ARCHIVE" | awk '{ print $1 }')
|
|
527
|
+
[ "$ACTUAL_SHA" = "$EXPECTED_SHA" ] \
|
|
528
|
+
|| fail "checksum verification failed for $ASSET"
|
|
529
|
+
|
|
530
|
+
if tar -tzf "$ARCHIVE" | grep -Eq '(^/|(^|/)\.\.(/|$))'; then
|
|
531
|
+
fail "release archive contains an unsafe path"
|
|
532
|
+
fi
|
|
533
|
+
|
|
534
|
+
mkdir -p "$INSTALL_ROOT/versions" "$BIN_DIR"
|
|
535
|
+
VERSION_DIR="$INSTALL_ROOT/versions/$VERSION"
|
|
536
|
+
NEW_INSTALL="false"
|
|
537
|
+
|
|
538
|
+
if [ ! -d "$VERSION_DIR" ]; then
|
|
539
|
+
STAGE_DIR=$(mktemp -d "$INSTALL_ROOT/.install-$VERSION.XXXXXX")
|
|
540
|
+
tar -xzf "$ARCHIVE" -C "$STAGE_DIR"
|
|
541
|
+
[ -x "$STAGE_DIR/$LAUNCHER_RELATIVE_PATH" ] \
|
|
542
|
+
|| fail "release archive is missing executable $LAUNCHER_RELATIVE_PATH"
|
|
543
|
+
[ -x "$STAGE_DIR/$INTERFACE_RELATIVE_PATH" ] \
|
|
544
|
+
|| fail "release archive is missing $INTERFACE_RELATIVE_PATH"
|
|
545
|
+
[ -x "$STAGE_DIR/$ENGINE_RELATIVE_PATH" ] \
|
|
546
|
+
|| fail "release archive is missing $ENGINE_RELATIVE_PATH"
|
|
547
|
+
verify_release_signatures "$STAGE_DIR"
|
|
548
|
+
mv "$STAGE_DIR" "$VERSION_DIR"
|
|
549
|
+
STAGE_DIR=""
|
|
550
|
+
NEW_INSTALL="true"
|
|
551
|
+
else
|
|
552
|
+
[ -x "$VERSION_DIR/$LAUNCHER_RELATIVE_PATH" ] \
|
|
553
|
+
|| fail "existing install is incomplete: $VERSION_DIR"
|
|
554
|
+
[ -x "$VERSION_DIR/$INTERFACE_RELATIVE_PATH" ] \
|
|
555
|
+
|| fail "existing install is incomplete: $VERSION_DIR/$INTERFACE_RELATIVE_PATH"
|
|
556
|
+
[ -x "$VERSION_DIR/$ENGINE_RELATIVE_PATH" ] \
|
|
557
|
+
|| fail "existing install is incomplete: $VERSION_DIR/$ENGINE_RELATIVE_PATH"
|
|
558
|
+
verify_release_signatures "$VERSION_DIR"
|
|
559
|
+
fi
|
|
560
|
+
|
|
561
|
+
PREVIOUS_LINK=""
|
|
562
|
+
if [ -L "$BIN_DIR/freecode" ]; then
|
|
563
|
+
PREVIOUS_LINK=$(readlink "$BIN_DIR/freecode")
|
|
564
|
+
elif [ -e "$BIN_DIR/freecode" ]; then
|
|
565
|
+
fail "refusing to replace non-symlink executable: $BIN_DIR/freecode"
|
|
566
|
+
fi
|
|
567
|
+
LINK_TEMP="$BIN_DIR/.freecode-link.$$"
|
|
568
|
+
rm -f "$LINK_TEMP"
|
|
569
|
+
ln -s "$VERSION_DIR/$LAUNCHER_RELATIVE_PATH" "$LINK_TEMP"
|
|
570
|
+
mv -f "$LINK_TEMP" "$BIN_DIR/freecode"
|
|
571
|
+
|
|
572
|
+
rollback_activation() {
|
|
573
|
+
rm -f "$BIN_DIR/freecode"
|
|
574
|
+
if [ -n "$PREVIOUS_LINK" ]; then
|
|
575
|
+
ln -s "$PREVIOUS_LINK" "$BIN_DIR/freecode"
|
|
576
|
+
fi
|
|
577
|
+
if [ "$NEW_INSTALL" = "true" ]; then
|
|
578
|
+
rm -rf "$VERSION_DIR"
|
|
579
|
+
fi
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
printf 'Verifying Free Code %s...\n' "$VERSION"
|
|
583
|
+
VERSION_OUTPUT=$("$BIN_DIR/freecode" --version 2>&1) || {
|
|
584
|
+
rollback_activation
|
|
585
|
+
fail "freecode --version failed; restored the previous install"
|
|
586
|
+
}
|
|
587
|
+
printf '%s\n' "$VERSION_OUTPUT"
|
|
588
|
+
[ "$(printf '%s\n' "$VERSION_OUTPUT" | sed -n '1p')" = "freecode-sh $VERSION" ] || {
|
|
589
|
+
rollback_activation
|
|
590
|
+
fail "freecode --version reported the wrong release; restored the previous install"
|
|
591
|
+
}
|
|
592
|
+
if ! "$BIN_DIR/freecode" doctor; then
|
|
593
|
+
rollback_activation
|
|
594
|
+
fail "freecode doctor failed; restored the previous install"
|
|
595
|
+
fi
|
|
596
|
+
|
|
597
|
+
rm -f "$INSTALL_ROOT/current-version"
|
|
598
|
+
printf '%s\n' "$BIN_DIR" > "$INSTALL_ROOT/bin-dir"
|
|
599
|
+
printf '%s\n' "$CHANNEL" > "$INSTALL_ROOT/channel"
|
|
600
|
+
printf '%s\n' "$DOWNLOAD_BASE_URL" > "$INSTALL_ROOT/release-base-url"
|
|
601
|
+
printf '%s\n' "$CHANNEL_BASE_URL" > "$INSTALL_ROOT/channel-base-url"
|
|
602
|
+
|
|
603
|
+
release_install_lock
|
|
604
|
+
|
|
605
|
+
printf '\nFree Code %s installed successfully.\n' "$VERSION"
|
|
606
|
+
printf 'Executable: %s/freecode\n' "$BIN_DIR"
|
|
607
|
+
|
|
608
|
+
if [ "$EMBEDDED_INSTALL" = "true" ]; then
|
|
609
|
+
exit 0
|
|
610
|
+
fi
|
|
611
|
+
|
|
612
|
+
configure_path
|
|
613
|
+
|
|
614
|
+
if [ "$PATH_CONFIGURED" = "true" ]; then
|
|
615
|
+
printf 'Added Free Code to your PATH in %s.\n' "$PATH_PROFILE"
|
|
616
|
+
else
|
|
617
|
+
printf '\nFree Code could not update your shell profile automatically.\n'
|
|
618
|
+
printf 'Add this line to your shell profile for future Terminal windows:\n'
|
|
619
|
+
printf ' export PATH="%s:$PATH"\n' "$BIN_DIR"
|
|
620
|
+
fi
|
|
621
|
+
|
|
622
|
+
printf '\nVerify the install:\n'
|
|
623
|
+
printf ' %s/freecode --version\n' "$BIN_DIR"
|
|
624
|
+
printf ' %s/freecode doctor\n' "$BIN_DIR"
|
|
625
|
+
printf '\nUninstall:\n'
|
|
626
|
+
printf ' rm -f "%s/freecode" && rm -rf "%s"\n' "$BIN_DIR" "$INSTALL_ROOT"
|
|
627
|
+
|
|
628
|
+
if [ "$LAUNCH_AFTER_INSTALL" = "true" ]; then
|
|
629
|
+
TTY_DEVICE="${FREECODE_TEST_TTY_FILE:-}"
|
|
630
|
+
if [ -z "$TTY_DEVICE" ] && [ -t 1 ]; then
|
|
631
|
+
TTY_DEVICE="/dev/tty"
|
|
632
|
+
fi
|
|
633
|
+
if [ -n "$TTY_DEVICE" ] && [ -r "$TTY_DEVICE" ]; then
|
|
634
|
+
printf '\nStarting Free Code...\n'
|
|
635
|
+
if ! "$BIN_DIR/freecode" < "$TTY_DEVICE"; then
|
|
636
|
+
printf '\nFree Code closed before startup finished. The installation is still ready.\n' >&2
|
|
637
|
+
fi
|
|
638
|
+
else
|
|
639
|
+
printf '\nFree Code is installed. Open a Terminal and run freecode when you are ready.\n'
|
|
640
|
+
fi
|
|
641
|
+
fi
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "freecode-sh",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.15",
|
|
4
4
|
"description": "A free coding agent for your terminal.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "Free Code",
|
|
@@ -21,37 +21,25 @@
|
|
|
21
21
|
],
|
|
22
22
|
"type": "module",
|
|
23
23
|
"bin": {
|
|
24
|
-
"freecode": "./freecode.mjs"
|
|
24
|
+
"freecode": "./freecode.mjs",
|
|
25
|
+
"freecode-sh": "./freecode.mjs"
|
|
25
26
|
},
|
|
26
27
|
"files": [
|
|
27
28
|
"freecode.mjs",
|
|
28
|
-
"
|
|
29
|
-
"engine.manifest.json",
|
|
30
|
-
"release-manifest.json",
|
|
31
|
-
"README.md",
|
|
32
|
-
"THIRD_PARTY_NOTICES.md"
|
|
29
|
+
"install.sh"
|
|
33
30
|
],
|
|
34
31
|
"engines": {
|
|
35
32
|
"node": ">=20.9.0"
|
|
36
33
|
},
|
|
37
34
|
"os": [
|
|
38
|
-
"darwin"
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
"darwin"
|
|
36
|
+
],
|
|
37
|
+
"cpu": [
|
|
38
|
+
"arm64",
|
|
39
|
+
"x64"
|
|
41
40
|
],
|
|
42
41
|
"publishConfig": {
|
|
43
42
|
"access": "public",
|
|
44
|
-
"tag": "
|
|
45
|
-
},
|
|
46
|
-
"dependencies": {
|
|
47
|
-
"@opencode-ai/sdk": "1.17.11",
|
|
48
|
-
"@opentui/core": "0.4.5",
|
|
49
|
-
"@opentui/keymap": "0.4.5",
|
|
50
|
-
"@opentui/solid": "0.4.5",
|
|
51
|
-
"bun": "1.3.14",
|
|
52
|
-
"diff": "9.0.0",
|
|
53
|
-
"opencode-ai": "1.17.11",
|
|
54
|
-
"solid-js": "1.9.12",
|
|
55
|
-
"ws": "8.21.0"
|
|
43
|
+
"tag": "latest"
|
|
56
44
|
}
|
|
57
45
|
}
|