Skip to content

Commit d571f47

Browse files
committed
chore: wip
1 parent a6941d2 commit d571f47

File tree

4 files changed

+175
-67
lines changed

4 files changed

+175
-67
lines changed

packages/launchpad/bin/cli.ts

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1992,21 +1992,14 @@ cli
19921992
})
19931993

19941994
cli
1995-
.command('dev:find-project-root [dir]', 'Find project root directory using fast file detection')
1996-
.option('--fallback-shell', 'Use shell fallback if fast detection fails')
1997-
.action(async (dir?: string, options?: { fallbackShell?: boolean }) => {
1995+
.command('dev:find-project-root [dir]', 'Find project root directory (fast detection with shell fallback)')
1996+
.option('--fallback-shell', 'Deprecated: hybrid fallback is now the default')
1997+
.action(async (dir?: string) => {
19981998
try {
1999-
const { findProjectRoot, findProjectRootFast } = await import('../src/dev/benchmark')
1999+
const { findProjectRoot } = await import('../src/dev/benchmark')
20002000
const startDir = dir ? path.resolve(dir) : process.cwd()
20012001

2002-
let result: string | null = null
2003-
2004-
if (options?.fallbackShell) {
2005-
result = findProjectRoot(startDir) // Uses hybrid approach
2006-
}
2007-
else {
2008-
result = findProjectRootFast(startDir) // Fast-only approach
2009-
}
2002+
const result = findProjectRoot(startDir)
20102003

20112004
if (result) {
20122005
console.log(result)
@@ -2017,14 +2010,7 @@ cli
20172010
}
20182011
}
20192012
catch {
2020-
if (options?.fallbackShell) {
2021-
// If fast detection fails and fallback is requested, exit with error
2022-
process.exit(1)
2023-
}
2024-
else {
2025-
// For fast-only mode, just exit with error code
2026-
process.exit(1)
2027-
}
2013+
process.exit(1)
20282014
}
20292015
})
20302016

packages/launchpad/src/dev/benchmark.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,13 @@ function findProjectRootShell(startDir: string): string | null {
115115
* Optimized implementation: Always use fast approach since we're in Node.js/Bun environment
116116
*/
117117
export function findProjectRoot(startDir: string): string | null {
118-
return findProjectRootFast(startDir)
118+
// Try fast detection first
119+
const fast = findProjectRootFast(startDir)
120+
if (fast)
121+
return fast
122+
123+
// Fallback to shell-based detection for edge cases
124+
return findProjectRootShell(startDir)
119125
}
120126

121127
/**

packages/launchpad/src/dev/integrate.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ export default async function (op: 'install' | 'uninstall', { dryrun }: { dryrun
8888
}
8989

9090
function getShellFiles(): [string, string][] {
91-
// Simple shell integration - the shellcode handles all the safety checks
92-
const eval_ln = 'eval "$(launchpad dev:shellcode)"'
91+
// Robust integration: only eval if launchpad is available on PATH
92+
const eval_ln = 'command -v launchpad >/dev/null 2>&1 && eval "$(launchpad dev:shellcode)"'
9393

9494
const home = homedir()
9595
const zdotdir = process.env.ZDOTDIR || home

packages/launchpad/src/dev/shellcode.ts

Lines changed: 160 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,29 @@ function getLaunchpadBinary(): string {
99
return 'launchpad'
1010
}
1111

12-
export function shellcode(testMode: boolean = false): string {
12+
export function shellcode(_testMode: boolean = false): string {
1313
// Use the same launchpad binary that's currently running
1414
const launchpadBinary = getLaunchpadBinary()
15-
const testModeCheck = testMode ? '' : ' || "$NODE_ENV" == "test"'
1615

17-
// Use default shell message configuration
18-
const showMessages = (typeof process !== 'undefined' && process.env?.LAUNCHPAD_SHOW_ENV_MESSAGES !== 'false') ? 'true' : 'false'
19-
const activationMessage = ((typeof process !== 'undefined' && process.env?.LAUNCHPAD_SHELL_ACTIVATION_MESSAGE) || '✅ Environment activated for \\033[3m$(basename "$project_dir")\\033[0m').replace('{path}', '$(basename "$project_dir")')
20-
const deactivationMessage = (typeof process !== 'undefined' && process.env?.LAUNCHPAD_SHELL_DEACTIVATION_MESSAGE) || 'Environment deactivated'
16+
// Use config-backed shell message configuration with {path} substitution
17+
const showMessages = config.showShellMessages ? 'true' : 'false'
18+
// Replace {path} with shell-evaluated basename
19+
const activationMessage = (config.shellActivationMessage || '✅ Environment activated for {path}')
20+
.replace('{path}', '$(basename "$project_dir")')
21+
const deactivationMessage = config.shellDeactivationMessage || 'Environment deactivated'
2122

22-
const verboseDefault = !!config.verbose
23+
// Verbosity: default to verbose for shell integration unless explicitly disabled
24+
// Priority: LAUNCHPAD_VERBOSE (runtime) > LAUNCHPAD_SHELL_VERBOSE (env) > config.verbose
25+
const verboseDefault = (typeof process !== 'undefined' && process.env?.LAUNCHPAD_SHELL_VERBOSE !== 'false')
26+
? true
27+
: !!config.verbose
2328

2429
return `
2530
# MINIMAL LAUNCHPAD SHELL INTEGRATION - DEBUGGING VERSION
2631
# This is a minimal version to isolate the hanging issue
2732
28-
# Exit early if shell integration is disabled or in test mode
29-
if [[ "$LAUNCHPAD_DISABLE_SHELL_INTEGRATION" == "1"${testModeCheck} ]]; then
33+
# Exit early if shell integration is disabled or explicit test mode
34+
if [[ "$LAUNCHPAD_DISABLE_SHELL_INTEGRATION" == "1" || "$LAUNCHPAD_TEST_MODE" == "1" ]]; then
3035
return 0 2>/dev/null || exit 0
3136
fi
3237
@@ -36,9 +41,64 @@ if [[ "$LAUNCHPAD_SKIP_INITIAL_INTEGRATION" == "1" ]]; then
3641
return 0 2>/dev/null || exit 0
3742
fi
3843
44+
# PATH helper: prepend a directory if not already present
45+
__lp_prepend_path() {
46+
local dir="$1"
47+
if [[ -n "$dir" && -d "$dir" ]]; then
48+
case ":$PATH:" in
49+
*":$dir:"*) : ;;
50+
*) PATH="$dir:$PATH"; export PATH ;;
51+
esac
52+
fi
53+
}
54+
55+
# Ensure Launchpad global bin is on PATH early (for globally installed tools)
56+
__lp_prepend_path "$HOME/.local/share/launchpad/global/bin"
57+
58+
# Portable timeout helper: uses timeout, gtimeout (macOS), or no-timeout fallback
59+
lp_timeout() {
60+
local duration="$1"; shift
61+
if command -v timeout >/dev/null 2>&1; then
62+
timeout "$duration" "$@"
63+
elif command -v gtimeout >/dev/null 2>&1; then
64+
gtimeout "$duration" "$@"
65+
else
66+
"$@"
67+
fi
68+
}
69+
70+
# Portable current time in milliseconds
71+
lp_now_ms() {
72+
if [[ -n "$ZSH_VERSION" && -n "$EPOCHREALTIME" ]]; then
73+
# EPOCHREALTIME is like: seconds.microseconds
74+
local sec="\${EPOCHREALTIME%.*}"
75+
local usec="\${EPOCHREALTIME#*.}"
76+
# Zero-pad/truncate to 3 digits for milliseconds
77+
local msec=$(( 10#\${usec:0:3} ))
78+
printf '%d%03d\n' "$sec" "$msec"
79+
elif command -v python3 >/dev/null 2>&1; then
80+
python3 - <<'PY'
81+
import time
82+
print(int(time.time() * 1000))
83+
PY
84+
else
85+
# Fallback: seconds * 1000 (approx)
86+
local s=$(date +%s 2>/dev/null || echo 0)
87+
printf '%d\n' $(( s * 1000 ))
88+
fi
89+
}
90+
3991
# Set up directory change hooks for zsh and bash (do this first, before any processing guards)
4092
if [[ -n "$ZSH_VERSION" ]]; then
4193
# zsh hook
94+
# Ensure hook arrays exist
95+
if ! typeset -p chpwd_functions >/dev/null 2>&1; then
96+
typeset -ga chpwd_functions
97+
fi
98+
if ! typeset -p precmd_functions >/dev/null 2>&1; then
99+
typeset -ga precmd_functions
100+
fi
101+
42102
__launchpad_chpwd() {
43103
# Prevent infinite recursion during hook execution
44104
if [[ "$__LAUNCHPAD_IN_HOOK" == "1" ]]; then
@@ -60,24 +120,26 @@ fi
60120
chpwd_functions+=(__launchpad_chpwd)
61121
fi
62122
63-
# zsh precmd to refresh on each prompt
64-
__launchpad_precmd() {
65-
# Prevent infinite recursion during hook execution
66-
if [[ "$__LAUNCHPAD_IN_HOOK" == "1" ]]; then
67-
return 0
68-
fi
69-
export __LAUNCHPAD_IN_HOOK=1
123+
# Optionally enable a precmd-based refresh if explicitly requested
124+
if [[ "$LAUNCHPAD_USE_PRECMD" == "1" ]]; then
125+
__launchpad_precmd() {
126+
# Prevent infinite recursion during hook execution
127+
if [[ "$__LAUNCHPAD_IN_HOOK" == "1" ]]; then
128+
return 0
129+
fi
130+
export __LAUNCHPAD_IN_HOOK=1
70131
71-
# Reuse the same environment switching/refresh logic
72-
__launchpad_switch_environment
132+
# Reuse the same environment switching/refresh logic
133+
__launchpad_switch_environment
73134
74-
# Clean up hook flag explicitly
75-
unset __LAUNCHPAD_IN_HOOK 2>/dev/null || true
76-
}
135+
# Clean up hook flag explicitly
136+
unset __LAUNCHPAD_IN_HOOK 2>/dev/null || true
137+
}
77138
78-
# Add the precmd hook if not already added
79-
if [[ ! " \${precmd_functions[*]} " =~ " __launchpad_precmd " ]]; then
80-
precmd_functions+=(__launchpad_precmd)
139+
# Add the precmd hook if not already added
140+
if [[ ! " \${precmd_functions[*]} " =~ " __launchpad_precmd " ]]; then
141+
precmd_functions+=(__launchpad_precmd)
142+
fi
81143
fi
82144
elif [[ -n "$BASH_VERSION" ]]; then
83145
# bash hook using PROMPT_COMMAND
@@ -105,23 +167,40 @@ fi
105167
106168
# Environment switching function (called by hooks)
107169
__launchpad_switch_environment() {
108-
# Start timer for performance tracking
109-
local start_time=$(date +%s%3N 2>/dev/null || echo "0")
170+
# Start timer for performance tracking (portable)
171+
local start_time=$(lp_now_ms)
110172
111173
# Check if verbose mode is enabled
112174
local verbose_mode="${verboseDefault}"
113175
if [[ -n "$LAUNCHPAD_VERBOSE" ]]; then
114176
verbose_mode="$LAUNCHPAD_VERBOSE"
115177
fi
116178
117-
if [[ "$verbose_mode" == "true" ]]; then
179+
# Dedupe key for verbose printing (avoid duplicate start/completion logs per PWD)
180+
local __lp_verbose_key="$PWD"
181+
local __lp_should_verbose_print="1"
182+
if [[ "$__LAUNCHPAD_LAST_VERBOSE_KEY" == "$__lp_verbose_key" ]]; then
183+
__lp_should_verbose_print="0"
184+
fi
185+
export __LAUNCHPAD_LAST_VERBOSE_KEY="$__lp_verbose_key"
186+
187+
if [[ "$verbose_mode" == "true" && "$__lp_should_verbose_print" == "1" ]]; then
118188
printf "⏱️ [0ms] Shell integration started for PWD=%s\\n" "$PWD" >&2
119189
fi
120190
121-
# Step 1: Find project directory using our fast binary (with timeout)
191+
# Step 1: Find project directory using our fast binary (with portable timeout)
122192
local project_dir=""
123-
if timeout 0.5s ${launchpadBinary} dev:find-project-root "$PWD" >/dev/null 2>&1; then
124-
project_dir=$(LAUNCHPAD_DISABLE_SHELL_INTEGRATION=1 timeout 0.5s ${launchpadBinary} dev:find-project-root "$PWD" 2>/dev/null || echo "")
193+
if lp_timeout 1s ${launchpadBinary} dev:find-project-root "$PWD" >/dev/null 2>&1; then
194+
project_dir=$(LAUNCHPAD_DISABLE_SHELL_INTEGRATION=1 lp_timeout 1s ${launchpadBinary} dev:find-project-root "$PWD" 2>/dev/null || echo "")
195+
fi
196+
197+
# Verbose: show project detection result
198+
if [[ "$verbose_mode" == "true" && "$__lp_should_verbose_print" == "1" ]]; then
199+
if [[ -n "$project_dir" ]]; then
200+
printf "📁 Project detected: %s\n" "$project_dir" >&2
201+
else
202+
printf "📁 No project detected (global mode)\n" >&2
203+
fi
125204
fi
126205
127206
# Step 2: Always ensure global paths are available (even in projects)
@@ -153,9 +232,21 @@ __launchpad_switch_environment() {
153232
rehash 2>/dev/null || true
154233
fi
155234
235+
# Initialize starship when it just became available (idempotent)
236+
if command -v starship >/dev/null 2>&1 && [[ -z "$STARSHIP_SHELL" ]]; then
237+
if [[ -n "$ZSH_VERSION" ]]; then
238+
eval "$(starship init zsh)" >/dev/null 2>&1 || true
239+
elif [[ -n "$BASH_VERSION" ]]; then
240+
eval "$(starship init bash)" >/dev/null 2>&1 || true
241+
fi
242+
if [[ "$verbose_mode" == "true" ]]; then
243+
printf "🌟 Initialized Starship prompt after install\n" >&2
244+
fi
245+
fi
246+
156247
# Show refresh message if verbose
157248
if [[ "$verbose_mode" == "true" ]]; then
158-
printf "🔄 Shell environment refreshed for newly installed tools\\n" >&2
249+
printf "🔄 Shell environment refreshed for newly installed tools\n" >&2
159250
fi
160251
fi
161252
@@ -166,13 +257,19 @@ __launchpad_switch_environment() {
166257
# Remove project-specific paths from PATH
167258
export PATH=$(echo "$PATH" | sed "s|$LAUNCHPAD_ENV_BIN_PATH:||g" | sed "s|:$LAUNCHPAD_ENV_BIN_PATH||g" | sed "s|^$LAUNCHPAD_ENV_BIN_PATH$||g")
168259
169-
# Show deactivation message if enabled
170-
if [[ "${showMessages}" == "true" ]]; then
260+
# Show deactivation message if enabled (only once per deactivation)
261+
if [[ "${showMessages}" == "true" && -n "$__LAUNCHPAD_LAST_ACTIVATION_KEY" ]]; then
171262
printf "${deactivationMessage}\\n" >&2
172263
fi
173264
265+
# Verbose: deactivated environment
266+
if [[ "$verbose_mode" == "true" && "$__lp_should_verbose_print" == "1" ]]; then
267+
printf "⚪ Deactivated environment\n" >&2
268+
fi
269+
174270
unset LAUNCHPAD_CURRENT_PROJECT
175271
unset LAUNCHPAD_ENV_BIN_PATH
272+
unset __LAUNCHPAD_LAST_ACTIVATION_KEY
176273
fi
177274
return 0
178275
fi
@@ -181,7 +278,7 @@ __launchpad_switch_environment() {
181278
if [[ -n "$project_dir" ]]; then
182279
local project_basename=$(basename "$project_dir")
183280
# Use proper MD5 hash to match existing environments
184-
local md5hash=$(printf "%s" "$project_dir" | LAUNCHPAD_DISABLE_SHELL_INTEGRATION=1 timeout 1s ${launchpadBinary} dev:md5 /dev/stdin 2>/dev/null || echo "00000000")
281+
local md5hash=$(printf "%s" "$project_dir" | LAUNCHPAD_DISABLE_SHELL_INTEGRATION=1 lp_timeout 2s ${launchpadBinary} dev:md5 /dev/stdin 2>/dev/null || echo "00000000")
185282
local project_hash="\${project_basename}_$(echo "$md5hash" | cut -c1-8)"
186283
187284
# Check for dependency file to add dependency hash
@@ -195,7 +292,7 @@ __launchpad_switch_environment() {
195292
196293
local env_dir="$HOME/.local/share/launchpad/envs/$project_hash"
197294
if [[ -n "$dep_file" ]]; then
198-
local dep_short=$(LAUNCHPAD_DISABLE_SHELL_INTEGRATION=1 timeout 1s ${launchpadBinary} dev:md5 "$dep_file" 2>/dev/null | cut -c1-8 || echo "")
295+
local dep_short=$(LAUNCHPAD_DISABLE_SHELL_INTEGRATION=1 lp_timeout 2s ${launchpadBinary} dev:md5 "$dep_file" 2>/dev/null | cut -c1-8 || echo "")
199296
if [[ -n "$dep_short" ]]; then
200297
env_dir="\${env_dir}-d\${dep_short}"
201298
fi
@@ -220,16 +317,27 @@ __launchpad_switch_environment() {
220317
export LAUNCHPAD_ENV_BIN_PATH="$env_dir/bin"
221318
export PATH="$env_dir/bin:$PATH"
222319
223-
# Show activation message if enabled
320+
# Show activation message if enabled (only when env changes)
224321
if [[ "${showMessages}" == "true" ]]; then
225-
printf "${activationMessage}\\n" >&2
322+
if [[ "$__LAUNCHPAD_LAST_ACTIVATION_KEY" != "$env_dir" ]]; then
323+
printf "${activationMessage}\\n" >&2
324+
fi
325+
fi
326+
export __LAUNCHPAD_LAST_ACTIVATION_KEY="$env_dir"
327+
328+
# Verbose: show activated env path
329+
if [[ "$verbose_mode" == "true" && "$__lp_should_verbose_print" == "1" ]]; then
330+
printf "✅ Activated environment: %s\n" "$env_dir" >&2
226331
fi
227332
else
228333
# Install dependencies synchronously but with timeout to avoid hanging
229334
# Use LAUNCHPAD_SHELL_INTEGRATION=1 to enable proper progress display
230-
if LAUNCHPAD_DISABLE_SHELL_INTEGRATION=1 LAUNCHPAD_SHELL_INTEGRATION=1 timeout 30s ${launchpadBinary} install "$project_dir"; then
335+
if LAUNCHPAD_DISABLE_SHELL_INTEGRATION=1 LAUNCHPAD_SHELL_INTEGRATION=1 lp_timeout 30s ${launchpadBinary} install "$project_dir"; then
336+
if [[ "$verbose_mode" == "true" ]]; then
337+
printf "📦 Installed project dependencies (on-demand)\n" >&2
338+
fi
231339
# If install succeeded, try to activate the environment
232-
if [[ -d "$env_dir/bin" ]]; then
340+
if [[ -d "$env_dir/bin" ]]; then
233341
export LAUNCHPAD_CURRENT_PROJECT="$project_dir"
234342
export LAUNCHPAD_ENV_BIN_PATH="$env_dir/bin"
235343
export PATH="$env_dir/bin:$PATH"
@@ -238,17 +346,25 @@ __launchpad_switch_environment() {
238346
if [[ "${showMessages}" == "true" ]]; then
239347
printf "${activationMessage}\\n" >&2
240348
fi
349+
350+
# Verbose: show activated env path after install
351+
if [[ "$verbose_mode" == "true" && "$__lp_should_verbose_print" == "1" ]]; then
352+
printf "✅ Activated environment after install: %s\n" "$env_dir" >&2
353+
fi
241354
fi
242355
fi
243356
fi
244357
fi
245358
246359
# Show completion time if verbose
247-
if [[ "$verbose_mode" == "true" ]]; then
248-
local end_time=$(date +%s%3N 2>/dev/null || echo "0")
249-
local elapsed=$((end_time - start_time))
250-
if [[ "$elapsed" -gt 0 ]]; then
251-
printf "⏱️ [%sms] Shell integration completed\\n" "$elapsed" >&2
360+
if [[ "$verbose_mode" == "true" && "$__lp_should_verbose_print" == "1" ]]; then
361+
local end_time=$(lp_now_ms)
362+
# Only print if both are integers
363+
if [[ "$start_time" =~ ^[0-9]+$ && "$end_time" =~ ^[0-9]+$ ]]; then
364+
local elapsed=$(( end_time - start_time ))
365+
if [[ "$elapsed" -ge 0 ]]; then
366+
printf "⏱️ [%sms] Shell integration completed\n" "$elapsed" >&2
367+
fi
252368
fi
253369
fi
254370
}

0 commit comments

Comments
 (0)