@etherscan-npm/cli 1.0.1 → 1.0.3

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.
@@ -1,211 +1,340 @@
1
- #!/bin/sh
2
-
3
- set -eu
4
-
5
- repository="etherscan/etherscan-cli"
6
- version=${ETHERSCAN_VERSION:-}
7
- install_dir=${ETHERSCAN_INSTALL_DIR:-"$HOME/.local/bin"}
8
- download_base=${ETHERSCAN_INSTALL_TEST_DOWNLOAD_BASE_URL:-}
9
- update_path=1
10
-
11
- usage() {
12
- cat <<'EOF'
13
- Install Etherscan CLI.
14
-
15
- Usage: install.sh [options]
16
-
17
- Options:
18
- --version VERSION Install a specific version (for example, v1.1.0).
19
- --install-dir DIRECTORY Install into DIRECTORY (default: ~/.local/bin).
20
- --no-path-update Do not update the shell profile.
21
- -h, --help Show this help.
22
- EOF
23
- }
24
-
25
- die() {
26
- printf 'error: %s\n' "$*" >&2
27
- exit 1
28
- }
29
-
30
- while [ "$#" -gt 0 ]; do
31
- case "$1" in
32
- --version)
33
- [ "$#" -ge 2 ] || die "--version requires a value"
34
- version=$2
35
- shift 2
36
- ;;
37
- --install-dir)
38
- [ "$#" -ge 2 ] || die "--install-dir requires a value"
39
- install_dir=$2
40
- shift 2
41
- ;;
42
- --no-path-update)
43
- update_path=0
44
- shift
45
- ;;
46
- -h|--help)
47
- usage
48
- exit 0
49
- ;;
50
- *)
51
- die "unknown option: $1"
52
- ;;
53
- esac
54
- done
55
-
56
- if printf '%s' "$install_dir" | LC_ALL=C grep '[[:cntrl:]]' >/dev/null 2>&1; then
57
- die "the installation directory cannot contain control characters"
58
- fi
59
-
60
- fetch_stdout() {
61
- url=$1
62
- if command -v curl >/dev/null 2>&1; then
63
- curl -fsSL -A etherscan-cli-installer "$url"
64
- elif command -v wget >/dev/null 2>&1; then
65
- wget -qO- --user-agent=etherscan-cli-installer "$url"
66
- else
67
- die "curl or wget is required"
68
- fi
69
- }
70
-
71
- fetch_file() {
72
- base=$1
73
- name=$2
74
- destination=$3
75
-
76
- if [ -d "$base" ]; then
77
- cp "$base/$name" "$destination"
78
- return
79
- fi
80
-
81
- case "$base" in
82
- file://*)
83
- cp "${base#file://}/$name" "$destination"
84
- ;;
85
- https://*)
86
- if command -v curl >/dev/null 2>&1; then
87
- curl -fsSL -A etherscan-cli-installer "$base/$name" -o "$destination"
88
- elif command -v wget >/dev/null 2>&1; then
89
- wget -q --user-agent=etherscan-cli-installer "$base/$name" -O "$destination"
90
- else
91
- die "curl or wget is required"
92
- fi
93
- ;;
94
- *)
95
- die "invalid download base URL or directory: $base"
96
- ;;
97
- esac
98
- }
99
-
100
- if [ -z "$version" ] || [ "$version" = latest ]; then
101
- [ -z "$download_base" ] || die "a version is required with the installer test download source"
102
- release_json=$(fetch_stdout "https://api.github.com/repos/$repository/releases/latest")
103
- version=$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | sed -n '1p')
104
- [ -n "$version" ] || die "could not resolve the latest Etherscan CLI version"
105
- fi
106
-
107
- case "$version" in
108
- v*) tag=$version; release_version=${version#v} ;;
109
- *) tag="v$version"; release_version=$version ;;
110
- esac
111
-
112
- printf '%s\n' "$tag" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?$' || die "invalid release version: $tag"
113
-
114
- system_name=${ETHERSCAN_INSTALL_TEST_OS:-$(uname -s)}
115
- case "$system_name" in
116
- Linux|linux) os=linux ;;
117
- Darwin|darwin) os=darwin ;;
118
- *) die "unsupported operating system: $system_name" ;;
119
- esac
120
-
121
- machine_arch=${ETHERSCAN_INSTALL_TEST_ARCH:-$(uname -m)}
122
- case "$machine_arch" in
123
- x86_64|amd64) arch=amd64 ;;
124
- arm64|aarch64) arch=arm64 ;;
125
- *) die "unsupported architecture: $machine_arch. Etherscan CLI supports amd64 and arm64." ;;
126
- esac
127
-
128
- archive_name="etherscan_${release_version}_${os}_${arch}.tar.gz"
129
- if [ -z "$download_base" ]; then
130
- download_base="https://github.com/$repository/releases/download/$tag"
131
- fi
132
-
133
- temp_dir=$(mktemp -d 2>/dev/null || mktemp -d -t etherscan-install)
134
- trap 'rm -rf "$temp_dir"' EXIT HUP INT TERM
135
- archive_path="$temp_dir/$archive_name"
136
- checksum_path="$temp_dir/checksums.txt"
137
- source_executable="$temp_dir/etherscan"
138
-
139
- printf 'Downloading Etherscan CLI %s for %s/%s...\n' "$release_version" "$os" "$arch"
140
- fetch_file "$download_base" "$archive_name" "$archive_path"
141
- fetch_file "$download_base" checksums.txt "$checksum_path"
142
-
143
- expected_hash=$(awk -v name="$archive_name" '$2 == name || $2 == ("*" name) { print tolower($1); exit }' "$checksum_path")
144
- [ -n "$expected_hash" ] || die "no checksum was published for $archive_name"
145
- printf '%s\n' "$expected_hash" | grep -Eq '^[0-9a-f]{64}$' || die "invalid checksum published for $archive_name"
146
-
147
- if command -v sha256sum >/dev/null 2>&1; then
148
- actual_hash=$(sha256sum "$archive_path" | awk '{ print tolower($1) }')
149
- elif command -v shasum >/dev/null 2>&1; then
150
- actual_hash=$(shasum -a 256 "$archive_path" | awk '{ print tolower($1) }')
151
- else
152
- die "sha256sum or shasum is required to verify the download"
153
- fi
154
-
155
- [ "$actual_hash" = "$expected_hash" ] || die "checksum verification failed for $archive_name"
156
-
157
- entry_count=$(tar -tzf "$archive_path" | awk '$0 == "etherscan" { count++ } END { print count + 0 }')
158
- [ "$entry_count" -eq 1 ] || die "$archive_name must contain exactly one root-level etherscan"
159
- tar -xOzf "$archive_path" etherscan >"$source_executable"
160
- [ -s "$source_executable" ] || die "$archive_name contains an empty etherscan executable"
161
-
162
- mkdir -p "$install_dir"
163
- staged_executable="$install_dir/.etherscan.new.$$"
164
- cp "$source_executable" "$staged_executable"
165
- chmod 0755 "$staged_executable"
166
- mv -f "$staged_executable" "$install_dir/etherscan"
167
-
168
- path_updated=0
169
- if [ "$update_path" -eq 1 ]; then
170
- case ":$PATH:" in
171
- *:"$install_dir":*) ;;
172
- *)
173
- shell_name=${SHELL:-sh}
174
- shell_name=${shell_name##*/}
175
- escaped_install_dir=$(printf '%s' "$install_dir" | sed 's/[\\"$`]/\\&/g')
176
- if [ "$shell_name" = fish ]; then
177
- profile="$HOME/.config/fish/config.fish"
178
- mkdir -p "$(dirname "$profile")"
179
- path_line="fish_add_path \"$escaped_install_dir\""
180
- else
181
- case "$shell_name" in
182
- zsh) profile="$HOME/.zshrc" ;;
183
- bash) profile="$HOME/.bashrc" ;;
184
- *) profile="$HOME/.profile" ;;
185
- esac
186
- path_line="export PATH=\"$escaped_install_dir:\$PATH\""
187
- fi
188
-
189
- # Match the exact line we would write (whole-line, fixed-string) so an
190
- # unrelated profile line that merely contains the path does not suppress
191
- # the update, and a genuine duplicate is not appended.
192
- if ! [ -f "$profile" ] || ! grep -Fx -e "$path_line" "$profile" >/dev/null 2>&1; then
193
- {
194
- printf '\n# Etherscan CLI\n'
195
- printf '%s\n' "$path_line"
196
- } >>"$profile"
197
- path_updated=1
198
- fi
199
- ;;
200
- esac
201
- fi
202
-
203
- printf '\nEtherscan CLI %s installed successfully.\n' "$release_version"
204
- printf 'Installed to: %s\n' "$install_dir/etherscan"
205
- if [ "$update_path" -eq 0 ]; then
206
- printf 'Add %s to PATH to run etherscan from any directory.\n' "$install_dir"
207
- elif [ "$path_updated" -eq 1 ]; then
208
- printf 'Open a new terminal, then run: etherscan version\n'
209
- else
210
- printf 'Run: etherscan version\n'
211
- fi
1
+ #!/bin/sh
2
+
3
+ set -eu
4
+
5
+ repository="etherscan/etherscan-cli"
6
+ version=${ETHERSCAN_VERSION:-}
7
+ install_dir=${ETHERSCAN_INSTALL_DIR:-"$HOME/.local/bin"}
8
+ download_base=${ETHERSCAN_INSTALL_TEST_DOWNLOAD_BASE_URL:-}
9
+ update_path=1
10
+ uninstall=0
11
+ marker_name=.etherscan-cli-path-added
12
+ marker_content=etherscan-cli:path-added:v1
13
+
14
+ usage() {
15
+ cat <<'EOF'
16
+ Install Etherscan CLI.
17
+
18
+ Usage: install.sh [options]
19
+
20
+ Options:
21
+ --version VERSION Install a specific version (for example, v1.1.0).
22
+ --install-dir DIRECTORY Install into DIRECTORY (default: ~/.local/bin).
23
+ --no-path-update Do not update the shell profile.
24
+ --uninstall Remove the CLI and saved configuration.
25
+ -h, --help Show this help.
26
+ EOF
27
+ }
28
+
29
+ die() {
30
+ printf 'error: %s\n' "$*" >&2
31
+ exit 1
32
+ }
33
+
34
+ while [ "$#" -gt 0 ]; do
35
+ case "$1" in
36
+ --version)
37
+ [ "$#" -ge 2 ] || die "--version requires a value"
38
+ version=$2
39
+ shift 2
40
+ ;;
41
+ --install-dir)
42
+ [ "$#" -ge 2 ] || die "--install-dir requires a value"
43
+ install_dir=$2
44
+ shift 2
45
+ ;;
46
+ --no-path-update)
47
+ update_path=0
48
+ shift
49
+ ;;
50
+ --uninstall)
51
+ uninstall=1
52
+ shift
53
+ ;;
54
+ -h|--help)
55
+ usage
56
+ exit 0
57
+ ;;
58
+ *)
59
+ die "unknown option: $1"
60
+ ;;
61
+ esac
62
+ done
63
+
64
+ if printf '%s' "$install_dir" | LC_ALL=C grep '[[:cntrl:]]' >/dev/null 2>&1; then
65
+ die "the installation directory cannot contain control characters"
66
+ fi
67
+
68
+ config_dir() {
69
+ if [ -n "${XDG_CONFIG_HOME:-}" ]; then
70
+ printf '%s\n' "$XDG_CONFIG_HOME/etherscan"
71
+ else
72
+ printf '%s\n' "$HOME/.etherscan"
73
+ fi
74
+ }
75
+
76
+ profile_has_block() {
77
+ profile=$1
78
+ target=$2
79
+ [ -f "$profile" ] && [ ! -L "$profile" ] || return 1
80
+ ETHERSCAN_PROFILE_TARGET=$target awk '
81
+ BEGIN { target = ENVIRON["ETHERSCAN_PROFILE_TARGET"] }
82
+ previous == "# Etherscan CLI" && $0 == target { found = 1; exit }
83
+ { previous = $0 }
84
+ END { exit found ? 0 : 1 }
85
+ ' "$profile"
86
+ }
87
+
88
+ remove_profile_block() {
89
+ profile=$1
90
+ target=$2
91
+ profile_has_block "$profile" "$target" || return 0
92
+ tmp=$(mktemp "${profile}.etherscan-uninstall.XXXXXX") || die "could not create a profile temporary file"
93
+ if ! cp -p "$profile" "$tmp"; then
94
+ rm -f "$tmp"
95
+ die "could not preserve permissions for $profile"
96
+ fi
97
+ if ! ETHERSCAN_PROFILE_TARGET=$target awk '
98
+ BEGIN { target = ENVIRON["ETHERSCAN_PROFILE_TARGET"] }
99
+ {
100
+ if (pending) {
101
+ if ($0 == target) { pending = 0; next }
102
+ print "# Etherscan CLI"
103
+ pending = 0
104
+ }
105
+ if ($0 == "# Etherscan CLI") { pending = 1; next }
106
+ print
107
+ }
108
+ END { if (pending) print "# Etherscan CLI" }
109
+ ' "$profile" >"$tmp"; then
110
+ rm -f "$tmp"
111
+ die "could not update $profile"
112
+ fi
113
+ mv -f "$tmp" "$profile"
114
+ printf 'Removed the PATH entry from %s\n' "$profile"
115
+ }
116
+
117
+ directory_empty_except_marker() {
118
+ directory=$1
119
+ marker=$2
120
+ [ -d "$directory" ] || return 0
121
+ for entry in "$directory"/.[!.]* "$directory"/..?* "$directory"/*; do
122
+ [ -e "$entry" ] || [ -L "$entry" ] || continue
123
+ [ "$entry" = "$marker" ] && continue
124
+ return 1
125
+ done
126
+ return 0
127
+ }
128
+
129
+ if [ "$uninstall" -eq 1 ]; then
130
+ binary="$install_dir/etherscan"
131
+ marker="$install_dir/$marker_name"
132
+ marker_valid=0
133
+ if [ -f "$marker" ] && [ ! -L "$marker" ] && [ "$(cat "$marker")" = "$marker_content" ]; then
134
+ marker_valid=1
135
+ fi
136
+
137
+ escaped_install_dir=$(printf '%s' "$install_dir" | sed 's/[\\"$`]/\\&/g')
138
+ posix_path_line="export PATH=\"$escaped_install_dir:\$PATH\""
139
+ fish_path_line="fish_add_path \"$escaped_install_dir\""
140
+ legacy_provenance=0
141
+ for candidate in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.profile"; do
142
+ if profile_has_block "$candidate" "$posix_path_line"; then
143
+ legacy_provenance=1
144
+ fi
145
+ done
146
+ if profile_has_block "$HOME/.config/fish/config.fish" "$fish_path_line"; then
147
+ legacy_provenance=1
148
+ fi
149
+
150
+ removed=0
151
+ if [ -e "$binary" ] || [ -L "$binary" ]; then
152
+ rm -f "$binary"
153
+ printf 'Removed %s\n' "$binary"
154
+ removed=1
155
+ fi
156
+
157
+ if [ "$update_path" -eq 1 ] && { [ "$marker_valid" -eq 1 ] || [ "$legacy_provenance" -eq 1 ]; } && directory_empty_except_marker "$install_dir" "$marker"; then
158
+ for candidate in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.profile"; do
159
+ remove_profile_block "$candidate" "$posix_path_line"
160
+ done
161
+ remove_profile_block "$HOME/.config/fish/config.fish" "$fish_path_line"
162
+ if [ "$marker_valid" -eq 1 ]; then
163
+ rm -f "$marker"
164
+ fi
165
+ rmdir "$install_dir" 2>/dev/null || true
166
+ removed=1
167
+ elif [ "$update_path" -eq 1 ] && [ -d "$install_dir" ]; then
168
+ printf 'Left %s on PATH because ownership was not proven or the directory is shared.\n' "$install_dir"
169
+ fi
170
+
171
+ etherscan_config=$(config_dir)
172
+ if [ -e "$etherscan_config" ] || [ -L "$etherscan_config" ]; then
173
+ rm -rf "$etherscan_config"
174
+ printf 'Removed %s\n' "$etherscan_config"
175
+ removed=1
176
+ fi
177
+ if [ "$removed" -eq 1 ]; then
178
+ printf 'Etherscan CLI uninstalled.\n'
179
+ else
180
+ printf 'Nothing to remove.\n'
181
+ fi
182
+ if [ -n "${ETHERSCAN_API_KEY:-}" ]; then
183
+ printf 'note: ETHERSCAN_API_KEY remains set; unset it in your shell.\n' >&2
184
+ fi
185
+ exit 0
186
+ fi
187
+
188
+ fetch_stdout() {
189
+ url=$1
190
+ if command -v curl >/dev/null 2>&1; then
191
+ curl -fsSL -A etherscan-cli-installer "$url"
192
+ elif command -v wget >/dev/null 2>&1; then
193
+ wget -qO- --user-agent=etherscan-cli-installer "$url"
194
+ else
195
+ die "curl or wget is required"
196
+ fi
197
+ }
198
+
199
+ fetch_file() {
200
+ base=$1
201
+ name=$2
202
+ destination=$3
203
+
204
+ if [ -d "$base" ]; then
205
+ cp "$base/$name" "$destination"
206
+ return
207
+ fi
208
+
209
+ case "$base" in
210
+ file://*)
211
+ cp "${base#file://}/$name" "$destination"
212
+ ;;
213
+ https://*)
214
+ if command -v curl >/dev/null 2>&1; then
215
+ curl -fsSL -A etherscan-cli-installer "$base/$name" -o "$destination"
216
+ elif command -v wget >/dev/null 2>&1; then
217
+ wget -q --user-agent=etherscan-cli-installer "$base/$name" -O "$destination"
218
+ else
219
+ die "curl or wget is required"
220
+ fi
221
+ ;;
222
+ *)
223
+ die "invalid download base URL or directory: $base"
224
+ ;;
225
+ esac
226
+ }
227
+
228
+ if [ -z "$version" ] || [ "$version" = latest ]; then
229
+ [ -z "$download_base" ] || die "a version is required with the installer test download source"
230
+ release_json=$(fetch_stdout "https://api.github.com/repos/$repository/releases/latest")
231
+ version=$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | sed -n '1p')
232
+ [ -n "$version" ] || die "could not resolve the latest Etherscan CLI version"
233
+ fi
234
+
235
+ case "$version" in
236
+ v*) tag=$version; release_version=${version#v} ;;
237
+ *) tag="v$version"; release_version=$version ;;
238
+ esac
239
+
240
+ printf '%s\n' "$tag" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?$' || die "invalid release version: $tag"
241
+
242
+ system_name=${ETHERSCAN_INSTALL_TEST_OS:-$(uname -s)}
243
+ case "$system_name" in
244
+ Linux|linux) os=linux ;;
245
+ Darwin|darwin) os=darwin ;;
246
+ *) die "unsupported operating system: $system_name" ;;
247
+ esac
248
+
249
+ machine_arch=${ETHERSCAN_INSTALL_TEST_ARCH:-$(uname -m)}
250
+ case "$machine_arch" in
251
+ x86_64|amd64) arch=amd64 ;;
252
+ arm64|aarch64) arch=arm64 ;;
253
+ *) die "unsupported architecture: $machine_arch. Etherscan CLI supports amd64 and arm64." ;;
254
+ esac
255
+
256
+ archive_name="etherscan_${release_version}_${os}_${arch}.tar.gz"
257
+ if [ -z "$download_base" ]; then
258
+ download_base="https://github.com/$repository/releases/download/$tag"
259
+ fi
260
+
261
+ temp_dir=$(mktemp -d 2>/dev/null || mktemp -d -t etherscan-install)
262
+ trap 'rm -rf "$temp_dir"' EXIT HUP INT TERM
263
+ archive_path="$temp_dir/$archive_name"
264
+ checksum_path="$temp_dir/checksums.txt"
265
+ source_executable="$temp_dir/etherscan"
266
+
267
+ printf 'Downloading Etherscan CLI %s for %s/%s...\n' "$release_version" "$os" "$arch"
268
+ fetch_file "$download_base" "$archive_name" "$archive_path"
269
+ fetch_file "$download_base" checksums.txt "$checksum_path"
270
+
271
+ expected_hash=$(awk -v name="$archive_name" '$2 == name || $2 == ("*" name) { print tolower($1); exit }' "$checksum_path")
272
+ [ -n "$expected_hash" ] || die "no checksum was published for $archive_name"
273
+ printf '%s\n' "$expected_hash" | grep -Eq '^[0-9a-f]{64}$' || die "invalid checksum published for $archive_name"
274
+
275
+ if command -v sha256sum >/dev/null 2>&1; then
276
+ actual_hash=$(sha256sum "$archive_path" | awk '{ print tolower($1) }')
277
+ elif command -v shasum >/dev/null 2>&1; then
278
+ actual_hash=$(shasum -a 256 "$archive_path" | awk '{ print tolower($1) }')
279
+ else
280
+ die "sha256sum or shasum is required to verify the download"
281
+ fi
282
+
283
+ [ "$actual_hash" = "$expected_hash" ] || die "checksum verification failed for $archive_name"
284
+
285
+ entry_count=$(tar -tzf "$archive_path" | awk '$0 == "etherscan" { count++ } END { print count + 0 }')
286
+ [ "$entry_count" -eq 1 ] || die "$archive_name must contain exactly one root-level etherscan"
287
+ tar -xOzf "$archive_path" etherscan >"$source_executable"
288
+ [ -s "$source_executable" ] || die "$archive_name contains an empty etherscan executable"
289
+
290
+ mkdir -p "$install_dir"
291
+ staged_executable="$install_dir/.etherscan.new.$$"
292
+ cp "$source_executable" "$staged_executable"
293
+ chmod 0755 "$staged_executable"
294
+ mv -f "$staged_executable" "$install_dir/etherscan"
295
+
296
+ path_updated=0
297
+ if [ "$update_path" -eq 1 ]; then
298
+ case ":$PATH:" in
299
+ *:"$install_dir":*) ;;
300
+ *)
301
+ shell_name=${SHELL:-sh}
302
+ shell_name=${shell_name##*/}
303
+ escaped_install_dir=$(printf '%s' "$install_dir" | sed 's/[\\"$`]/\\&/g')
304
+ if [ "$shell_name" = fish ]; then
305
+ profile="$HOME/.config/fish/config.fish"
306
+ mkdir -p "$(dirname "$profile")"
307
+ path_line="fish_add_path \"$escaped_install_dir\""
308
+ else
309
+ case "$shell_name" in
310
+ zsh) profile="$HOME/.zshrc" ;;
311
+ bash) profile="$HOME/.bashrc" ;;
312
+ *) profile="$HOME/.profile" ;;
313
+ esac
314
+ path_line="export PATH=\"$escaped_install_dir:\$PATH\""
315
+ fi
316
+
317
+ # Match the exact line we would write (whole-line, fixed-string) so an
318
+ # unrelated profile line that merely contains the path does not suppress
319
+ # the update, and a genuine duplicate is not appended.
320
+ if ! [ -f "$profile" ] || ! grep -Fx -e "$path_line" "$profile" >/dev/null 2>&1; then
321
+ {
322
+ printf '\n# Etherscan CLI\n'
323
+ printf '%s\n' "$path_line"
324
+ } >>"$profile"
325
+ path_updated=1
326
+ printf '%s' "$marker_content" >"$install_dir/$marker_name"
327
+ fi
328
+ ;;
329
+ esac
330
+ fi
331
+
332
+ printf '\nEtherscan CLI %s installed successfully.\n' "$release_version"
333
+ printf 'Installed to: %s\n' "$install_dir/etherscan"
334
+ if [ "$update_path" -eq 0 ]; then
335
+ printf 'Add %s to PATH to run etherscan from any directory.\n' "$install_dir"
336
+ elif [ "$path_updated" -eq 1 ]; then
337
+ printf 'Open a new terminal, then run: etherscan version\n'
338
+ else
339
+ printf 'Run: etherscan version\n'
340
+ fi