Skip to content

Commit f149310

Browse files
vladfrangul2yshoCopilot
authored
fix: install script TTY handling on Unix (#1032)
Co-authored-by: Richard Solar <solar.richard@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 3fd9394 commit f149310

5 files changed

Lines changed: 466 additions & 111 deletions

File tree

scripts/build-cli-bundles.ts

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ At the time of writing (~2025-05-31), the bundles are created using Bun as the r
77
When node stabilizes SEA (https://nodejs.org/api/single-executable-applications.html) [and supports ESM -.-], this code can be adapted to build it using that instead (but cross-platform will be a CI experience)
88
*/
99

10+
import { readFileSync } from 'node:fs';
1011
import { readFile, rm, writeFile } from 'node:fs/promises';
1112
import { basename } from 'node:path';
1213

13-
import { $, fileURLToPath } from 'bun';
14+
import { $, type Build, build, fileURLToPath } from 'bun';
1415

1516
import { version } from '../package.json' with { type: 'json' };
1617

@@ -30,13 +31,14 @@ const targets = (() => {
3031
'bun-darwin-arm64-baseline',
3132
'bun-linux-x64-musl',
3233
'bun-linux-arm64-musl',
33-
'bun-linux-x64-musl-baseline',
34-
'bun-linux-arm64-musl-baseline',
35-
];
34+
// TODO: when adding native windows arm64 builds, remove these too
35+
'bun-linux-x64-musl-baseline' as never,
36+
'bun-linux-arm64-musl-baseline' as never,
37+
] satisfies Build.CompileTarget[];
3638
}
3739

3840
if (process.platform === 'win32') {
39-
return ['bun-windows-x64', 'bun-windows-x64-baseline'];
41+
return ['bun-windows-x64', 'bun-windows-x64-baseline'] satisfies Build.CompileTarget[];
4042
}
4143

4244
return [
@@ -50,9 +52,9 @@ const targets = (() => {
5052
'bun-darwin-arm64-baseline',
5153
'bun-linux-x64-musl',
5254
'bun-linux-arm64-musl',
53-
'bun-linux-x64-musl-baseline',
54-
'bun-linux-arm64-musl-baseline',
55-
];
55+
'bun-linux-x64-musl-baseline' as never,
56+
'bun-linux-arm64-musl-baseline' as never,
57+
] satisfies Build.CompileTarget[];
5658
})();
5759

5860
const entryPoints = [
@@ -77,6 +79,35 @@ await writeFile(metadataFile, newContent);
7779
for (const entryPoint of entryPoints) {
7880
const cliName = basename(entryPoint, '.ts');
7981

82+
const lines = readFileSync(entryPoint, 'utf-8').split('\n');
83+
lines.splice(1, 0, 'import "proxy-agent";');
84+
85+
// Step 1: create one fat JS file with node resolver to ensure no imports point to non-node export conditions
86+
const result = await build({
87+
entrypoints: [entryPoint],
88+
files: {
89+
[entryPoint]: lines.join('\n'),
90+
},
91+
outdir: fileURLToPath(new URL(`../bundles/fat-clis`, import.meta.url)),
92+
conditions: 'node',
93+
target: 'bun',
94+
sourcemap: 'none',
95+
});
96+
97+
const entrypointResultFilePath = result.outputs[0]!.path;
98+
99+
// Fix apify client js (it now lazy loads proxy-agent, which makes bun skip it from the bundle)
100+
{
101+
const entrypointResultFileContent = await result.outputs[0]!.text();
102+
103+
const newEntrypointResultFileContent = entrypointResultFileContent.replace(
104+
`(0, utils_1.dynamicNodeImport)("proxy-agent")`,
105+
`Promise.resolve().then(() => import_proxy_agent)`,
106+
);
107+
108+
await writeFile(entrypointResultFilePath, newEntrypointResultFileContent);
109+
}
110+
80111
for (const target of targets) {
81112
// eslint-disable-next-line prefer-const -- somehow it cannot tell that os and arch cannot be "const" while the rest are let
82113
let [, os, arch, musl, baseline] = target.split('-');
@@ -88,6 +119,7 @@ for (const entryPoint of entryPoints) {
88119

89120
// If we are building on Windows ARM64, even though the target is x64, we mark it as "arm64" (there are some weird errors when compiling on x64
90121
// and running on arm64). Hopefully bun will get arm64 native builds
122+
// TODO: Vlad remove this in a subsequent PR as Bun now has native arm64 windows builds
91123
if (os === 'windows' && process.platform === 'win32') {
92124
const systemType = await $`pwsh -c "(Get-CimInstance Win32_ComputerSystem).SystemType"`.text();
93125

@@ -108,8 +140,21 @@ for (const entryPoint of entryPoints) {
108140
const outFile = fileURLToPath(new URL(`../bundles/${fileName}`, import.meta.url));
109141

110142
console.log(`Building ${cliName} for ${target} (result: ${fileName})...`);
111-
// TODO: --sourcemap crashes for w/e reason and --bytecode doesn't support ESM (TLA to be exact)
112-
await $`bun build --compile --minify --target=${target} --outfile=${outFile} ${entryPoint}`;
143+
144+
// Step 2: create the final executable bundle
145+
await build({
146+
entrypoints: [entrypointResultFilePath],
147+
compile: {
148+
outfile: outFile,
149+
target,
150+
},
151+
format: 'esm',
152+
minify: {
153+
identifiers: true,
154+
keepNames: true,
155+
},
156+
bytecode: true,
157+
});
113158

114159
// Remove the arch override
115160
await writeFile(metadataFile, newContent);
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# This script should be used from the repo root like so: `cat scripts/install/dev-test-install.sh | bash`
5+
6+
# Reset
7+
Color_Off=''
8+
9+
# Regular Colors
10+
Red=''
11+
Dim='' # White
12+
13+
if [[ -t 1 ]]; then
14+
# Reset
15+
Color_Off='\033[0m' # Text Reset
16+
17+
# Regular Colors
18+
Red='\033[0;31m' # Red
19+
Dim='\033[0;2m' # White
20+
fi
21+
22+
error() {
23+
echo -e "${Red}error${Color_Off}:" "$@" >&2
24+
exit 1
25+
}
26+
27+
info() {
28+
echo -e "${Dim}$@ ${Color_Off}"
29+
}
30+
31+
platform=$(uname -ms)
32+
33+
case $platform in
34+
'Darwin x86_64')
35+
target=darwin-x64
36+
;;
37+
'Darwin arm64')
38+
target=darwin-arm64
39+
;;
40+
'Linux aarch64' | 'Linux arm64')
41+
target=linux-arm64
42+
;;
43+
'MINGW64'*)
44+
target=windows-x64
45+
;;
46+
'Linux x86_64' | *)
47+
target=linux-x64
48+
;;
49+
esac
50+
51+
case "$target" in
52+
'linux'*)
53+
if [ -f /etc/alpine-release ]; then
54+
target="$target-musl"
55+
fi
56+
;;
57+
esac
58+
59+
# If AVX2 isn't supported, use the -baseline build
60+
case "$target" in
61+
'darwin-x64'*)
62+
if [[ $(sysctl -a | grep machdep.cpu | grep AVX2) == '' ]]; then
63+
target="$target-baseline"
64+
fi
65+
;;
66+
'linux-x64'*)
67+
# If AVX2 isn't supported, use the -baseline build
68+
if [[ $(cat /proc/cpuinfo | grep avx2) = '' ]]; then
69+
target="$target-baseline"
70+
fi
71+
;;
72+
esac
73+
74+
install_env=APIFY_CLI_INSTALL
75+
install_dir=${!install_env:-$HOME/.apify}
76+
bin_dir=$install_dir/bin
77+
78+
if [[ ! -d $bin_dir ]]; then
79+
mkdir -p "$bin_dir" ||
80+
error "Failed to create install directory \"$bin_dir\""
81+
fi
82+
83+
# Ensure we are in the apify-cli root by checking for ./package.json
84+
if [[ ! -f ./package.json ]]; then
85+
error "Not in the apify-cli root"
86+
fi
87+
88+
echo "Install directory: $install_dir"
89+
echo "Bin directory: $bin_dir"
90+
91+
# Ensure we have bun installed
92+
if ! command -v bun &> /dev/null; then
93+
error "bun could not be found. Please install it from https://bun.sh/docs/installation"
94+
exit 1
95+
fi
96+
97+
# Ensure we have jq installed
98+
if ! command -v jq &> /dev/null; then
99+
error "jq could not be found. Please install it from https://stedolan.github.io/jq/"
100+
exit 1
101+
fi
102+
# Check package.json for the version
103+
version=$(jq -r '.version' package.json)
104+
echo "Version: $version"
105+
106+
info "Installing dependencies"
107+
yarn
108+
109+
info "Building bundles"
110+
yarn insert-cli-metadata && yarn build-bundles && git checkout -- src/lib/hooks/useCLIMetadata.ts
111+
112+
info "Installing bundles"
113+
114+
executable_names=("apify" "actor")
115+
116+
for executable_name in "${executable_names[@]}"; do
117+
output_filename="${executable_name}"
118+
119+
info "Installing $executable_name bundle for version $version and target $target"
120+
121+
cp "bundles/$executable_name-$version-$target" "$bin_dir/$output_filename"
122+
chmod +x "$bin_dir/$output_filename"
123+
done
124+
125+
if ! [ -t 0 ] && [ -r /dev/tty ]; then
126+
PROVIDED_INSTALL_DIR="$install_dir" FINAL_BIN_DIR="$bin_dir" APIFY_OPEN_TTY=1 "$bin_dir/apify" install
127+
else
128+
PROVIDED_INSTALL_DIR="$install_dir" FINAL_BIN_DIR="$bin_dir" "$bin_dir/apify" install
129+
fi

scripts/install/install.sh

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,15 @@ Color_Off=''
1919

2020
# Regular Colors
2121
Red=''
22-
Green=''
2322
Dim='' # White
2423

25-
# Bold
26-
Bold_White=''
27-
Bold_Green=''
28-
2924
if [[ -t 1 ]]; then
3025
# Reset
3126
Color_Off='\033[0m' # Text Reset
3227

3328
# Regular Colors
3429
Red='\033[0;31m' # Red
35-
Green='\033[0;32m' # Green
3630
Dim='\033[0;2m' # White
37-
38-
# Bold
39-
Bold_Green='\033[1;32m' # Bold Green
40-
Bold_White='\033[1m' # Bold White
4131
fi
4232

4333
error() {
@@ -49,14 +39,6 @@ info() {
4939
echo -e "${Dim}$@ ${Color_Off}"
5040
}
5141

52-
info_bold() {
53-
echo -e "${Bold_White}$@ ${Color_Off}"
54-
}
55-
56-
success() {
57-
echo -e "${Green}$@ ${Color_Off}"
58-
}
59-
6042
if [[ $# -gt 1 ]]; then
6143
error 'Too many arguments, only 1 is allowed. The first can be a specific tag of Apify CLI to install. (e.g. "0.28.0")'
6244
fi
@@ -97,14 +79,10 @@ if [[ $target = darwin-x64 ]]; then
9779
# redirect stderr to devnull to avoid error message when not running in Rosetta
9880
if [[ $(sysctl -n sysctl.proc_translated 2>/dev/null) = 1 ]]; then
9981
target=darwin-arm64
100-
info "Your shell is running in Rosetta 2. Downloading Apify CLI for $target instead"
82+
info "Your shell is running in Rosetta 2. Downloading Apify and Actor CLI for $target instead"
10183
fi
10284
fi
10385

104-
GITHUB=${GITHUB-"https://github.com"}
105-
106-
github_repo="$GITHUB/apify/apify-cli"
107-
10886
# If AVX2 isn't supported, use the -baseline build
10987
case "$target" in
11088
'darwin-x64'*)
@@ -190,20 +168,12 @@ for executable_name in "${executable_names[@]}"; do
190168
fi
191169
done
192170

193-
tildify() {
194-
if [[ $1 = $HOME/* ]]; then
195-
local replacement=\~/
196-
197-
echo "${1/$HOME\//$replacement}"
198-
else
199-
echo "$1"
200-
fi
201-
}
202-
203-
echo ''
204-
echo ''
205-
success "Apify and Actor CLI $version were installed successfully!"
206-
info "The binaries are located at $Bold_Green$(tildify "$bin_dir/apify") ${Dim}and $Bold_Green$(tildify "$bin_dir/actor")"
207-
208171
# Invoke the CLI to handle shell integrations nicely
209-
PROVIDED_INSTALL_DIR="$install_dir" FINAL_BIN_DIR="$bin_dir" "$bin_dir/apify" install
172+
# When running the script via `curl xxx | bash`, stdin is the script that gets consumed by bash.
173+
# If stdin is not a tty and we have a readable /dev/tty, tell Node.js to open /dev/tty itself
174+
# (shell-level redirects don't support raw mode properly for Node.js/Inquirer).
175+
if ! [ -t 0 ] && [ -r /dev/tty ]; then
176+
PROVIDED_INSTALL_DIR="$install_dir" FINAL_BIN_DIR="$bin_dir" APIFY_OPEN_TTY=1 "$bin_dir/apify" install
177+
else
178+
PROVIDED_INSTALL_DIR="$install_dir" FINAL_BIN_DIR="$bin_dir" "$bin_dir/apify" install
179+
fi

0 commit comments

Comments
 (0)