#!/usr/bin/env bash # # @license Apache-2.0 # # Copyright (c) 2021 The Stdlib Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Build script to run installation tests on [GitHub][1]. # # [1]: https://help.github.com/en/articles/about-github-actions # shellcheck disable=SC2181,SC2129 # Ensure that the exit status of pipelines is non-zero in the event that at least one of the commands in a pipeline fails: set -o pipefail # VARIABLES # # Get the name of the desired package manager as the first argument to the build script: pm="$1" # Get the path to the local installation directory as the second argument to the build script: install_dir="$2" # Get the path to a log file as the third argument to the build script: log_file="$3" # Define the package to install: pkg_name='@stdlib/stdlib' # Define the package repository: pkg_github_url='git+https://git@github.com/stdlib-js/stdlib.git' # Define the repository tag/commit/branch to install: pkg_github_tag='develop' # Define the package version to install: pkg_version='latest' # Define a heartbeat interval to periodically print messages in order to prevent CI from prematurely ending a build due to long running commands: heartbeat_interval='30s' # Declare a variable for storing the heartbeat process id: heartbeat_pid="" # Get the current working directory: working_dir=$(pwd) # Get the Node.js major version: node_major_version=$(node -e 'var proc = ( typeof process !== "undefined" ) ? process : require( "process" ); console.log( proc.version.substring( 1 ).split( "." )[ 0 ] );' | tr -d '\n') # FUNCTIONS # # Error handler. # # $1 - error status on_error() { echo 'ERROR: An error was encountered during execution.' >&2 cleanup exit "$1" } # Runs clean-up tasks. cleanup() { stop_heartbeat } # Starts a heartbeat. # # $1 - heartbeat interval start_heartbeat() { echo 'Starting heartbeat...' >&2 # Create a heartbeat and send to background: heartbeat "$1" & # Capture the heartbeat pid: heartbeat_pid=$! echo "Heartbeat pid: ${heartbeat_pid}" >&2 } # Runs an infinite print loop. # # $1 - heartbeat interval heartbeat() { while true; do echo "$(date) - heartbeat..." >&2; sleep "$1"; done } # Stops the heartbeat print loop. stop_heartbeat() { echo 'Stopping heartbeat...' >&2 kill "${heartbeat_pid}" } # Prints a success message. print_success() { echo 'Success!' >&2 } # Performs local installation initialization tasks. install_init() { echo "Creating installation directory: ${install_dir}..." >> "${log_file}" 2>&1 mkdir -p "${install_dir}" || return 1 echo 'Navigating to installation directory...' >> "${log_file}" 2>&1 cd "${install_dir}" || return 1 echo 'Creating package.json...' >> "${log_file}" 2>&1 echo '{"name":"test-install","version":"0.0.0","private":true}' > "${install_dir}/package.json" echo 'package.json:' >> "${log_file}" 2>&1 cat "${install_dir}/package.json" >> "${log_file}" 2>&1 return 0 } # Tests that a local installation works as expected. install_test() { echo 'Testing installation...' >> "${log_file}" 2>&1 echo 'Creating test script (require global namespace)...' >> "${log_file}" 2>&1 echo 'var stdlib = require( "@stdlib/stdlib" ); console.log( stdlib.math.base.special.sin( 3.14 ) );' > "${install_dir}/script.js" echo 'script:' >> "${log_file}" 2>&1 cat "${install_dir}/script.js" >> "${log_file}" 2>&1 echo 'Running test script...' >> "${log_file}" 2>&1 node "${install_dir}/script.js" >> "${log_file}" 2>&1 if [[ "$?" -ne 0 ]]; then echo "Encountered an error when running test script." >> "${log_file}" 2>&1 return 1 fi echo '' >> "${log_file}" 2>&1 echo 'Successfully ran test script.' >> "${log_file}" 2>&1 echo 'Creating test script (require individual package)...' >> "${log_file}" 2>&1 echo 'var sin = require( "@stdlib/math/base/special/sin" ); console.log( sin( 3.14 ) );' > "${install_dir}/script.js" echo 'script:' >> "${log_file}" 2>&1 cat "${install_dir}/script.js" >> "${log_file}" 2>&1 echo 'Running test script...' >> "${log_file}" 2>&1 node "${install_dir}/script.js" >> "${log_file}" 2>&1 if [[ "$?" -ne 0 ]]; then echo "Encountered an error when running test script." >> "${log_file}" 2>&1 return 1 fi echo '' >> "${log_file}" 2>&1 echo 'Successfully ran test script.' >> "${log_file}" 2>&1 # Test `import` syntax on more recent Node.js versions which support ES modules without having to set flags to enable experimental support... if [[ "${node_major_version}" -gt 13 ]]; then echo 'Creating test script (import global namespace)...' >> "${log_file}" 2>&1 echo 'import stdlib from "@stdlib/stdlib"; console.log( stdlib.math.base.special.sin( 3.14 ) );' > "${install_dir}/script.mjs" echo 'script:' >> "${log_file}" 2>&1 cat "${install_dir}/script.mjs" >> "${log_file}" 2>&1 echo 'Running test script...' >> "${log_file}" 2>&1 node "${install_dir}/script.mjs" >> "${log_file}" 2>&1 if [[ "$?" -ne 0 ]]; then echo "Encountered an error when running test script." >> "${log_file}" 2>&1 return 1 fi echo '' >> "${log_file}" 2>&1 echo 'Successfully ran test script.' >> "${log_file}" 2>&1 # FIMXE: uncomment once we've figured out our package.json `exports` strategy... # echo 'Creating test script (import individual package)...' >> "${log_file}" 2>&1 # echo 'import sin from "@stdlib/math/base/special/sin"; console.log( sin( 3.14 ) );' > "${install_dir}/script.mjs" # echo 'script:' >> "${log_file}" 2>&1 # cat "${install_dir}/script.mjs" >> "${log_file}" 2>&1 # echo 'Running test script...' >> "${log_file}" 2>&1 # node "${install_dir}/script.mjs" >> "${log_file}" 2>&1 # if [[ "$?" -ne 0 ]]; then # echo "Encountered an error when running test script." >> "${log_file}" 2>&1 # return 1 # fi # echo '' >> "${log_file}" 2>&1 # echo 'Successfully ran test script.' >> "${log_file}" 2>&1 fi return 0 } # Performs local installation cleanup tasks. install_clean() { echo "Returning to original directory: ${working_dir}..." >> "${log_file}" 2>&1 cd "${working_dir}" || return 1 echo "Removing installation directory: ${install_dir}..." >> "${log_file}" 2>&1 rm -rf "${install_dir}" || return 1 return 0 } # Installs a package via `npm`. npm_install() { install_init if [[ "$?" -ne 0 ]]; then echo "Encountered an error when performing pre-install initialization tasks." >> "${log_file}" 2>&1 return 1 fi echo "Installing ${pkg_name}@${pkg_version}..." >> "${log_file}" 2>&1 npm install "${pkg_name}@${pkg_version}" >> "${log_file}" 2>&1 if [[ "$?" -ne 0 ]]; then echo "Encountered an error when installing ${pkg_name}@${pkg_version} via npm." >> "${log_file}" 2>&1 cat "${working_dir}/npm-debug.log" >> "${log_file}" 2>&1 return 1 fi echo "Successfully installed ${pkg_name}@${pkg_version}." >> "${log_file}" 2>&1 install_test if [[ "$?" -ne 0 ]]; then echo "Encountered an error when testing installation." >> "${log_file}" 2>&1 return 1 fi install_clean if [[ "$?" -ne 0 ]]; then echo "Encountered an error when performing post-install cleanup tasks." >> "${log_file}" 2>&1 return 1 fi return 0 } # Installs a package via `npm` from GitHub. npm_install_from_github() { install_init if [[ "$?" -ne 0 ]]; then echo "Encountered an error when performing pre-install initialization tasks." >> "${log_file}" 2>&1 return 1 fi echo "Installing ${pkg_github_url}#${pkg_github_tag}..." >> "${log_file}" 2>&1 npm install "${pkg_github_url}#${pkg_github_tag}" >> "${log_file}" 2>&1 if [[ "$?" -ne 0 ]]; then echo "Encountered an error when installing ${pkg_github_url}#${pkg_github_tag} via npm." >> "${log_file}" 2>&1 cat "${working_dir}/npm-debug.log" >> "${log_file}" 2>&1 return 1 fi echo "Successfully installed ${pkg_github_url}#${pkg_github_tag}." >> "${log_file}" 2>&1 install_test if [[ "$?" -ne 0 ]]; then echo "Encountered an error when testing installation." >> "${log_file}" 2>&1 return 1 fi install_clean if [[ "$?" -ne 0 ]]; then echo "Encountered an error when performing post-install cleanup tasks." >> "${log_file}" 2>&1 return 1 fi return 0 } # Installs a package globally via `npm`. npm_install_global() { echo "Installing ${pkg_name}@${pkg_version} as a global package..." >> "${log_file}" 2>&1 npm install -g "${pkg_name}@${pkg_version}" >> "${log_file}" 2>&1 if [[ "$?" -ne 0 ]]; then echo "Encountered an error when installing ${pkg_name}@${pkg_version} as a global package via npm." >> "${log_file}" 2>&1 cat "${working_dir}/npm-debug.log" >> "${log_file}" 2>&1 return 1 fi npm list -g --depth=0 >> "${log_file}" 2>&1 if [[ "$?" -ne 0 ]]; then echo "Encountered an error when attempting to list globally installed packages via npm." >> "${log_file}" 2>&1 return 1 fi echo "Successfully installed ${pkg_name}@${pkg_version}." >> "${log_file}" 2>&1 echo 'Testing global installation...' >> "${log_file}" 2>&1 stdlib --help >> "${log_file}" 2>&1 if [[ "$?" -ne 0 ]]; then echo "Encountered an error when testing installation." >> "${log_file}" 2>&1 return 1 fi echo 'Successfully tested global installation...' >> "${log_file}" 2>&1 echo 'Removing global installation...' >> "${log_file}" 2>&1 npm uninstall -g "${pkg_name}" >> "${log_file}" 2>&1 if [[ "$?" -ne 0 ]]; then echo "Encountered an error when performing post-install cleanup tasks." >> "${log_file}" 2>&1 return 1 fi npm list -g --depth=0 >> "${log_file}" 2>&1 if [[ "$?" -ne 0 ]]; then echo "Encountered an error when attempting to list globally installed packages via npm." >> "${log_file}" 2>&1 return 1 fi echo 'Successfully removed global installation...' >> "${log_file}" 2>&1 return 0 } # Installs a package globally via `npm` from GitHub. npm_install_global_github() { echo "Installing ${pkg_github_url}#${pkg_github_tag} as a global package..." >> "${log_file}" 2>&1 npm install -g "${pkg_github_url}#${pkg_github_tag}" >> "${log_file}" 2>&1 if [[ "$?" -ne 0 ]]; then echo "Encountered an error when installing ${pkg_github_url}#${pkg_github_tag} as a global package via npm." >> "${log_file}" 2>&1 cat "${working_dir}/npm-debug.log" >> "${log_file}" 2>&1 return 1 fi npm list -g --depth=0 >> "${log_file}" 2>&1 if [[ "$?" -ne 0 ]]; then echo "Encountered an error when attempting to list globally installed packages via npm." >> "${log_file}" 2>&1 return 1 fi echo "Successfully installed ${pkg_github_url}#${pkg_github_tag}." >> "${log_file}" 2>&1 echo 'Testing global installation...' >> "${log_file}" 2>&1 stdlib --help >> "${log_file}" 2>&1 if [[ "$?" -ne 0 ]]; then echo "Encountered an error when testing installation." >> "${log_file}" 2>&1 return 1 fi echo 'Successfully tested global installation...' >> "${log_file}" 2>&1 echo 'Removing global installation...' >> "${log_file}" 2>&1 npm uninstall -g "${pkg_name}" >> "${log_file}" 2>&1 if [[ "$?" -ne 0 ]]; then echo "Encountered an error when performing post-install cleanup tasks." >> "${log_file}" 2>&1 return 1 fi npm list -g --depth=0 >> "${log_file}" 2>&1 if [[ "$?" -ne 0 ]]; then echo "Encountered an error when attempting to list globally installed packages via npm." >> "${log_file}" 2>&1 return 1 fi echo 'Successfully removed global installation...' >> "${log_file}" 2>&1 return 0 } # Tests whether the project successfully installs via `npm`. test_npm_install() { echo 'Testing npm install...' >&2 npm_install if [[ "$?" -ne 0 ]]; then echo 'Installation failed.' >&2 echo 'Retry 1 of 1...' >&2 sleep 15s echo 'Testing npm install...' >&2 npm_install if [[ "$?" -ne 0 ]]; then echo 'Installation failed.' >&2 return 1 fi fi echo 'Successfully installed.' >&2 echo 'Testing npm install (via GitHub)...' >&2 npm_install_from_github if [[ "$?" -ne 0 ]]; then echo 'Installation (via GitHub) failed.' >&2 echo 'Retry 1 of 1...' >&2 sleep 15s echo 'Testing npm install (via GitHub)...' >&2 npm_install_from_github if [[ "$?" -ne 0 ]]; then echo 'Installation (via GitHub) failed.' >&2 return 1 fi fi echo 'Successfully installed (via GitHub).' >&2 echo 'Testing npm install (global)...' >&2 npm_install_global if [[ "$?" -ne 0 ]]; then echo 'Installation (global) failed.' >&2 echo 'Retry 1 of 1...' >&2 sleep 15s echo 'Testing npm install (global)...' >&2 npm_install_global if [[ "$?" -ne 0 ]]; then echo 'Installation (global) failed.' >&2 return 1 fi fi echo 'Successfully installed (global).' >&2 echo 'Testing npm install (global via GitHub)...' >&2 npm_install_global_github if [[ "$?" -ne 0 ]]; then echo 'Installation (global via GitHub) failed.' >&2 echo 'Retry 1 of 1...' >&2 sleep 15s echo 'Testing npm install (global via GitHub)...' >&2 npm_install_global_github if [[ "$?" -ne 0 ]]; then echo 'Installation (global via GitHub) failed.' >&2 return 1 fi fi echo 'Successfully installed (global via GitHub).' >&2 return 0 } # Main execution sequence. main() { start_heartbeat "${heartbeat_interval}" echo "Package manager: ${pm}." >&2 # Note: please keep the list of package managers in alphabetical order... if [[ "${pm}" = "npm" ]]; then test_npm_install if [[ "$?" -ne 0 ]]; then on_error 1 fi else echo "ERROR: unknown package manager: ${pm}." >&2 on_error 1 fi cleanup print_success exit 0 } # Set an error handler to print captured output and perform any clean-up tasks: trap 'on_error' ERR # Run main: main