Files
qinglong/cli/src/internal/execution/shellSession.ts
T

115 lines
4.2 KiB
TypeScript

import type { LocalContext } from '../runtime/context';
// Bash is the interpreter for user-owned files. The CLI supplies paths/argv as
// separate arguments; no user argument is interpolated into this fixed bridge.
export function shellSessionArguments(
context: LocalContext,
argv: string[],
scriptArgs: string[],
command = false,
timeoutMarker = '',
): string[] {
const scriptIndex = command
? argv.findIndex((arg) => /\.(?:js|mjs|py|pyc|sh|ts)$/.test(arg))
: 0;
const bridge = `
__ql_env=$1; __ql_before=$2; __ql_after=$3; __ql_command=$4; __ql_index=$5; __ql_count=$6; __ql_timeout=$7; shift 7
__ql_hook_args=( "\${@:1:__ql_count}" ); shift "$__ql_count"
__ql_script_args=( "$@" )
# Reserve an internal descriptor before sourcing user code. In particular, fd 3
# and fd 9 remain available for user redirections and locks. Bash 3 also supports
# this numeric descriptor, unlike Bash 4's dynamic descriptor syntax.
if [ -n "$__ql_timeout" ]; then exec 19>&3 3>&-; fi
__ql_after_started=false
__ql_run_after() {
__ql_after_started=true
if [ -n "$__ql_timeout" ]; then printf A >&19; fi
if [ "$__ql_nounset" = true ]; then set -u; fi
export NODE_PATH="\${PREV_NODE_PATH:-}"
unset QL_NODE_GLOBAL_PATH
if [ -f "$__ql_after" ]; then . "$__ql_after" "\${__ql_hook_args[@]}"; fi
if [ -n "\${task_after:-}" ]; then eval "\${task_after%;}"; fi
}
__ql_timeout_exit() {
if [ -f "$__ql_timeout" ] && [ "$__ql_after_started" = false ]; then
_task_exit_code=124
__ql_run_after
exit 124
fi
}
if [ -f "$__ql_env" ]; then . "$__ql_env"; fi
if [ -f "$__ql_before" ]; then . "$__ql_before" "\${__ql_hook_args[@]}"; fi
if [ -n "\${task_before:-}" ]; then eval "\${task_before%;}"; fi
__ql_nounset=false
case $- in *u*) __ql_nounset=true; set +u;; esac
if [ -n "\${__ql_selected_name:-}" ]; then
printf -v "$__ql_selected_name" '%s' "$__ql_selected_value"
export "$__ql_selected_name"
fi
unset __ql_selected_name __ql_selected_value
__ql_args=( "\${__ql_hook_args[@]}" )
if [ "$__ql_index" -lt 0 ]; then __ql_index=0; fi
__ql_file=\${__ql_args[__ql_index]}
__ql_resolve=false
cd "$dir_scripts" || exit 1
if [ -n "\${work_dir:-}" ] && [ -d "$work_dir" ]; then
cd "$work_dir" || exit 1
__ql_resolve=true
elif [[ "$__ql_file" == */* ]] && [ -d "\${__ql_file%/*}" ]; then
cd "\${__ql_file%/*}" || exit 1
__ql_resolve=true
fi
if [ "$__ql_resolve" = true ] && [[ "$__ql_file" == */* ]]; then
__ql_args[__ql_index]="./\${__ql_file##*/}"
fi
if [ -n "$__ql_timeout" ]; then
# Preserve a user-owned EXIT trap. If it prevents our cleanup, the parent
# performs the fallback after the process group has stopped.
if [ -z "$(trap -p EXIT)" ]; then trap '__ql_timeout_exit' EXIT; fi
printf T >&19
fi
if [ "$__ql_command" = true ]; then
"\${__ql_args[@]}" "\${__ql_script_args[@]}"
_task_exit_code=$?
else
# Match task.sh: keep script exit/exec/options inside a child shell while
# retaining before-hook variables and functions for the script.
# Keep the supervisor alive until the child's signal/EXIT traps finish, but
# do not turn user cancellation into an ordinary completion with after hooks.
__ql_saved_signals=$(trap -p INT TERM HUP QUIT ALRM TSTP)
__ql_cancel_code=''
trap '__ql_cancel_code=$((128 + $(kill -l INT)))' INT
trap '__ql_cancel_code=$((128 + $(kill -l TERM)))' TERM
trap '__ql_cancel_code=$((128 + $(kill -l HUP)))' HUP
trap '__ql_cancel_code=$((128 + $(kill -l QUIT)))' QUIT
trap '__ql_cancel_code=$((128 + $(kill -l ALRM)))' ALRM
trap '__ql_cancel_code=$((128 + $(kill -l TSTP)))' TSTP
( . "\${__ql_args[0]}" "\${__ql_script_args[@]}" )
_task_exit_code=$?
trap - INT TERM HUP QUIT ALRM TSTP
eval "$__ql_saved_signals"
if [ -n "$__ql_cancel_code" ] && ! [ -f "$__ql_timeout" ]; then exit "$__ql_cancel_code"; fi
fi
if [ -n "$__ql_timeout" ] && [ -f "$__ql_timeout" ]; then _task_exit_code=124; fi
__ql_run_after
exit "$_task_exit_code"
`;
return [
'--noprofile',
'--norc',
'-c',
bridge,
'ql-shell-session',
context.paths.file_env!,
context.paths.file_task_before!,
context.paths.file_task_after!,
String(command),
String(scriptIndex),
String(argv.length),
timeoutMarker,
...argv,
...(command ? [] : argv.slice(1)),
...scriptArgs,
];
}