Files
qinglong/cli/src/internal/execution/shellSession.ts
T
whyour 801a71d740 feat(cli): cover OpenAPI with a remote npm CLI and internal panel tools (#3074)
* feat(cli): add unified Commander CLI for QingLong 2.x

* fix(cli): publish via npm and address security review feedback

* ci(cli): package npm artifacts and remove evaluation collateral

* test(cli): use a fixed shell fixture for log retention

* refactor(cli): separate remote npm client from panel tools

* feat(cli): cover active panel OpenAPI resources

* docs(cli): unify authentication and skill guidance

* refactor(cli): isolate internal commands and generate Commander help

* refactor(cli): organize remote and internal modules by responsibility

* ci(cli): publish verified npm archives from master

* fix(cli): publish under the whyour npm scope

* ci: use npm trusted publishing for both packages

* docs: introduce the published CLI on the project homepage

* fix(cli): preserve server log truncation and correct login hints

* fix(cli): accept dashboard record request bodies

* fix(cli): preserve stdin for local task execution

* fix(cli): resolve task executables after changing directory

* fix(cli): preserve shell function tasks and sanitize test failures

* fix(cli): preserve shell hook state and resolve workdir after hooks

* fix(cli): preserve cleanup across shared shell task timeouts

* fix(cli): isolate shell control descriptors and reap timed-out descendants
2026-09-25 23:24:41 +08:00

99 lines
3.3 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[@]}"
else
. "\${__ql_args[0]}" "\${__ql_script_args[@]}"
fi
_task_exit_code=$?
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,
];
}