From 56194ccf2a47fe00bbdc64aec613595273a12375 Mon Sep 17 00:00:00 2001 From: Rolf Sormo Date: Fri, 22 May 2026 10:44:55 +0300 Subject: [PATCH 1/2] feat(mise): corepack module and npm globals migration on node upgrade - Add module_corepack.sh: runs `corepack enable` after mise configures node, restoring pnpm/yarn shims that are bundled inside each Node.js install but disabled by default - Add db_confirm() helper to core_files.sh for default-yes prompts - Update module_mise.sh: snapshot global npm packages before a node version upgrade, then prompt the user to reinstall them into the new version (with a warning that version pins and custom config won't carry) - Wire corepack module into build.sh and module registration (runs after mise so node is guaranteed to exist) Fixes the regression where upgrading node via `mise use -g node@lts` silently dropped globally-installed npm packages (e.g. pnpm) from the new install. --- build.sh | 3 + core/core_files.sh | 15 +++++ devboost.sh | 116 ++++++++++++++++++++++++++++++++++--- modules/module_corepack.sh | 31 ++++++++++ modules/module_mise.sh | 70 ++++++++++++++++++---- 5 files changed, 216 insertions(+), 19 deletions(-) create mode 100644 modules/module_corepack.sh diff --git a/build.sh b/build.sh index cae65fa..8475350 100755 --- a/build.sh +++ b/build.sh @@ -39,6 +39,8 @@ OUT="devboost.sh" echo "" cat modules/module_mise.sh echo "" + cat modules/module_corepack.sh + echo "" cat modules/module_direnv.sh echo "" cat modules/module_git.sh @@ -59,6 +61,7 @@ db_load_modules() { db_module_starship_register db_module_tmux_register db_module_mise_register + db_module_corepack_register db_module_direnv_register db_module_git_register db_module_services_register diff --git a/core/core_files.sh b/core/core_files.sh index 1aead3f..d7acef6 100644 --- a/core/core_files.sh +++ b/core/core_files.sh @@ -100,6 +100,21 @@ db_upsert_block() { fi } +db_confirm() { + local prompt="$1" + local default="${2:-y}" + local hint + if [[ "$default" == "y" ]]; then + hint="[Y/n]" + else + hint="[y/N]" + fi + local reply + read -r -p "$(echo -e "${YELLOW}?${NC} ${prompt} ${hint} ")" reply + reply="${reply:-$default}" + [[ "${reply,,}" == "y" ]] +} + db_remove_block() { local file="$1" local start_marker="$2" diff --git a/devboost.sh b/devboost.sh index efa81b4..cee3533 100755 --- a/devboost.sh +++ b/devboost.sh @@ -328,6 +328,21 @@ db_upsert_block() { fi } +db_confirm() { + local prompt="$1" + local default="${2:-y}" + local hint + if [[ "$default" == "y" ]]; then + hint="[Y/n]" + else + hint="[y/N]" + fi + local reply + read -r -p "$(echo -e "${YELLOW}?${NC} ${prompt} ${hint} ")" reply + reply="${reply:-$default}" + [[ "${reply,,}" == "y" ]] +} + db_remove_block() { local file="$1" local start_marker="$2" @@ -1180,39 +1195,121 @@ db_module_mise_plan() { if [[ "$enable" != "true" ]]; then return 0 fi - + db_log_info "Would configure mise toolchains" } +# Returns a space-separated list of globally installed npm packages (excluding npm itself). +_db_mise_npm_globals() { + local node_bin + node_bin=$(command -v node 2>/dev/null) || return 0 + local npm_bin + npm_bin=$(dirname "$node_bin")/npm + [[ -x "$npm_bin" ]] || return 0 + "$npm_bin" list -g --depth=0 --parseable 2>/dev/null \ + | awk -F/ 'NF>1 && $NF!="npm" && $NF!="corepack" {print $NF}' +} + +# Returns the currently active mise-managed node version, or empty string. +_db_mise_current_node_version() { + mise current node 2>/dev/null | tr -d '[:space:]' || true +} + db_module_mise_apply() { local enable=$(db_yaml_get '.toolchains.enable_mise' 'true') if [[ "$enable" != "true" ]]; then return 0 fi - + if ! db_command_exists mise; then db_log_warn "mise not found, skipping toolchain setup" return 0 fi - + db_log_info "Configuring mise toolchains..." - + local node=$(db_yaml_get '.toolchains.globals.node' 'lts') local python=$(db_yaml_get '.toolchains.globals.python' '3.12') local go=$(db_yaml_get '.toolchains.globals.go' '1.23') local rust=$(db_yaml_get '.toolchains.globals.rust' 'stable') local deno=$(db_yaml_get '.toolchains.globals.deno' 'latest') - + if [[ "${DB_DRY_RUN:-false}" == "true" ]]; then db_log_info "Would run: mise use -g node@${node} python@${python} go@${go} rust@${rust} deno@${deno}" db_log_info "Would run: mise install" - else - mise use -g "node@${node}" "python@${python}" "go@${go}" "rust@${rust}" "deno@${deno}" 2>/dev/null || true - mise install 2>/dev/null || db_log_warn "Some toolchains may not be available" - db_log_success "Configured mise toolchains" + return 0 fi + + # Capture current node version and its global npm packages before any upgrade. + local prev_node_version + prev_node_version=$(_db_mise_current_node_version) + local npm_globals=() + if [[ -n "$prev_node_version" ]] && db_command_exists node; then + while IFS= read -r pkg; do + [[ -n "$pkg" ]] && npm_globals+=("$pkg") + done < <(_db_mise_npm_globals) + fi + + mise use -g "node@${node}" "python@${python}" "go@${go}" "rust@${rust}" "deno@${deno}" 2>/dev/null || true + mise install 2>/dev/null || db_log_warn "Some toolchains may not be available" + db_log_success "Configured mise toolchains" + + # After upgrade, offer to migrate npm globals to the new node version. + if [[ ${#npm_globals[@]} -gt 0 ]]; then + local new_node_version + new_node_version=$(_db_mise_current_node_version) + if [[ "$prev_node_version" != "$new_node_version" ]]; then + db_log_warn "Node upgraded: ${prev_node_version} → ${new_node_version}" + db_log_warn "The following global npm packages were present in the old version:" + for pkg in "${npm_globals[@]}"; do + db_log_warn " - $pkg" + done + db_log_warn "Note: any version pins or custom configuration for these packages will NOT be migrated." + if db_confirm "Reinstall these packages into node@${new_node_version}?"; then + local npm_bin + npm_bin=$(dirname "$(command -v node)")/npm + for pkg in "${npm_globals[@]}"; do + db_log_info "Installing $pkg..." + "$npm_bin" install -g "$pkg" 2>/dev/null \ + && db_log_success " ✓ $pkg" \ + || db_log_warn " ✗ $pkg (failed — install manually if needed)" + done + fi + fi + fi +} + +# Corepack module — enables pnpm/yarn shims bundled with Node.js + +db_module_corepack_register() { + db_register_module "corepack" \ + "db_module_corepack_plan" \ + "db_module_corepack_apply" +} + +db_module_corepack_plan() { + local enable=$(db_yaml_get '.toolchains.enable_mise' 'true') + [[ "$enable" == "true" ]] || return 0 + db_log_info "Would run: corepack enable" } +db_module_corepack_apply() { + local enable=$(db_yaml_get '.toolchains.enable_mise' 'true') + [[ "$enable" == "true" ]] || return 0 + + if ! db_command_exists corepack; then + db_log_warn "corepack not found (expected inside Node.js install), skipping" + return 0 + fi + + if [[ "${DB_DRY_RUN:-false}" == "true" ]]; then + db_log_info "Would run: corepack enable" + return 0 + fi + + corepack enable 2>/dev/null && db_log_success "corepack: pnpm/yarn shims enabled" \ + || db_log_warn "corepack enable failed — pnpm/yarn shims may be missing" +} # Direnv module @@ -1504,6 +1601,7 @@ db_load_modules() { db_module_starship_register db_module_tmux_register db_module_mise_register + db_module_corepack_register db_module_direnv_register db_module_git_register db_module_services_register diff --git a/modules/module_corepack.sh b/modules/module_corepack.sh new file mode 100644 index 0000000..ec30485 --- /dev/null +++ b/modules/module_corepack.sh @@ -0,0 +1,31 @@ +# Corepack module — enables pnpm/yarn shims bundled with Node.js + +db_module_corepack_register() { + db_register_module "corepack" \ + "db_module_corepack_plan" \ + "db_module_corepack_apply" +} + +db_module_corepack_plan() { + local enable=$(db_yaml_get '.toolchains.enable_mise' 'true') + [[ "$enable" == "true" ]] || return 0 + db_log_info "Would run: corepack enable" +} + +db_module_corepack_apply() { + local enable=$(db_yaml_get '.toolchains.enable_mise' 'true') + [[ "$enable" == "true" ]] || return 0 + + if ! db_command_exists corepack; then + db_log_warn "corepack not found (expected inside Node.js install), skipping" + return 0 + fi + + if [[ "${DB_DRY_RUN:-false}" == "true" ]]; then + db_log_info "Would run: corepack enable" + return 0 + fi + + corepack enable 2>/dev/null && db_log_success "corepack: pnpm/yarn shims enabled" \ + || db_log_warn "corepack enable failed — pnpm/yarn shims may be missing" +} diff --git a/modules/module_mise.sh b/modules/module_mise.sh index 673ed00..8c01cf7 100644 --- a/modules/module_mise.sh +++ b/modules/module_mise.sh @@ -11,36 +11,86 @@ db_module_mise_plan() { if [[ "$enable" != "true" ]]; then return 0 fi - + db_log_info "Would configure mise toolchains" } +# Returns a space-separated list of globally installed npm packages (excluding npm itself). +_db_mise_npm_globals() { + local node_bin + node_bin=$(command -v node 2>/dev/null) || return 0 + local npm_bin + npm_bin=$(dirname "$node_bin")/npm + [[ -x "$npm_bin" ]] || return 0 + "$npm_bin" list -g --depth=0 --parseable 2>/dev/null \ + | awk -F/ 'NF>1 && $NF!="npm" && $NF!="corepack" {print $NF}' +} + +# Returns the currently active mise-managed node version, or empty string. +_db_mise_current_node_version() { + mise current node 2>/dev/null | tr -d '[:space:]' || true +} + db_module_mise_apply() { local enable=$(db_yaml_get '.toolchains.enable_mise' 'true') if [[ "$enable" != "true" ]]; then return 0 fi - + if ! db_command_exists mise; then db_log_warn "mise not found, skipping toolchain setup" return 0 fi - + db_log_info "Configuring mise toolchains..." - + local node=$(db_yaml_get '.toolchains.globals.node' 'lts') local python=$(db_yaml_get '.toolchains.globals.python' '3.12') local go=$(db_yaml_get '.toolchains.globals.go' '1.23') local rust=$(db_yaml_get '.toolchains.globals.rust' 'stable') local deno=$(db_yaml_get '.toolchains.globals.deno' 'latest') - + if [[ "${DB_DRY_RUN:-false}" == "true" ]]; then db_log_info "Would run: mise use -g node@${node} python@${python} go@${go} rust@${rust} deno@${deno}" db_log_info "Would run: mise install" - else - mise use -g "node@${node}" "python@${python}" "go@${go}" "rust@${rust}" "deno@${deno}" 2>/dev/null || true - mise install 2>/dev/null || db_log_warn "Some toolchains may not be available" - db_log_success "Configured mise toolchains" + return 0 fi -} + # Capture current node version and its global npm packages before any upgrade. + local prev_node_version + prev_node_version=$(_db_mise_current_node_version) + local npm_globals=() + if [[ -n "$prev_node_version" ]] && db_command_exists node; then + while IFS= read -r pkg; do + [[ -n "$pkg" ]] && npm_globals+=("$pkg") + done < <(_db_mise_npm_globals) + fi + + mise use -g "node@${node}" "python@${python}" "go@${go}" "rust@${rust}" "deno@${deno}" 2>/dev/null || true + mise install 2>/dev/null || db_log_warn "Some toolchains may not be available" + db_log_success "Configured mise toolchains" + + # After upgrade, offer to migrate npm globals to the new node version. + if [[ ${#npm_globals[@]} -gt 0 ]]; then + local new_node_version + new_node_version=$(_db_mise_current_node_version) + if [[ "$prev_node_version" != "$new_node_version" ]]; then + db_log_warn "Node upgraded: ${prev_node_version} → ${new_node_version}" + db_log_warn "The following global npm packages were present in the old version:" + for pkg in "${npm_globals[@]}"; do + db_log_warn " - $pkg" + done + db_log_warn "Note: any version pins or custom configuration for these packages will NOT be migrated." + if db_confirm "Reinstall these packages into node@${new_node_version}?"; then + local npm_bin + npm_bin=$(dirname "$(command -v node)")/npm + for pkg in "${npm_globals[@]}"; do + db_log_info "Installing $pkg..." + "$npm_bin" install -g "$pkg" 2>/dev/null \ + && db_log_success " ✓ $pkg" \ + || db_log_warn " ✗ $pkg (failed — install manually if needed)" + done + fi + fi + fi +} From 93edc044ddcbc1104fa34aff6af698d12772a8a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 May 2026 07:51:56 +0000 Subject: [PATCH 2/2] fix(core): replace bash 4+ case conversion with tr for bash 3.x compat The db_confirm helper used ${reply,,} (bash 4+ feature) which broke the bash 3.x compatibility test. Replace with tr '[:upper:]' '[:lower:]' for portable case folding. Also rebuild devboost.sh to include the fix. Agent-Logs-Url: https://github.com/rolfsormo/devboost/sessions/0a9f64de-8440-41db-b9f4-a77f33dc1cb4 Co-authored-by: rolfsormo <1366797+rolfsormo@users.noreply.github.com> --- core/core_files.sh | 3 ++- devboost.sh | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/core_files.sh b/core/core_files.sh index d7acef6..f5e10d7 100644 --- a/core/core_files.sh +++ b/core/core_files.sh @@ -112,7 +112,8 @@ db_confirm() { local reply read -r -p "$(echo -e "${YELLOW}?${NC} ${prompt} ${hint} ")" reply reply="${reply:-$default}" - [[ "${reply,,}" == "y" ]] + reply=$(echo "$reply" | tr '[:upper:]' '[:lower:]') + [[ "$reply" == "y" ]] } db_remove_block() { diff --git a/devboost.sh b/devboost.sh index cee3533..d1eaa53 100755 --- a/devboost.sh +++ b/devboost.sh @@ -340,7 +340,8 @@ db_confirm() { local reply read -r -p "$(echo -e "${YELLOW}?${NC} ${prompt} ${hint} ")" reply reply="${reply:-$default}" - [[ "${reply,,}" == "y" ]] + reply=$(echo "$reply" | tr '[:upper:]' '[:lower:]') + [[ "$reply" == "y" ]] } db_remove_block() {