From a7e0082a6a04d3d1e583a14e874679b90d300eeb Mon Sep 17 00:00:00 2001 From: Yolean k8s-qa Date: Fri, 27 Mar 2026 09:58:20 +0000 Subject: [PATCH 1/9] Add y-bin-default to symlink ystack-managed binaries into ~/.local/bin Instead of changing PATH to prepend ystack/bin globally (which would shadow system tools), provide y-bin-default check/ensure to selectively link managed binaries (currently yarn) into ~/.local/bin where they take precedence. Fails on ensure if the symlink exists but another binary wins in PATH, recommending a PATH update. Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/y-bin-default | 133 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100755 bin/y-bin-default diff --git a/bin/y-bin-default b/bin/y-bin-default new file mode 100755 index 0000000..b901cec --- /dev/null +++ b/bin/y-bin-default @@ -0,0 +1,133 @@ +#!/usr/bin/env bash +[ -z "$DEBUG" ] || set -x +set -eo pipefail + +YBIN="$(cd "$(dirname "$0")" && pwd)" + +YHELP='y-bin-default - Ensure ystack-managed binaries take precedence via ~/.local/bin symlinks + +Usage: y-bin-default [help] SUBCOMMAND [BIN...] + +Subcommands: + check Verify symlinks exist and take precedence in PATH + ensure Create symlinks and verify precedence (fails if link exists but loses to PATH) + +Arguments: + BIN Binary name (e.g. yarn). If omitted, iterates all known defaults. + +Known defaults: yarn + +The convention is to symlink ~/.local/bin/BIN -> $YSTACK_HOME/bin/BIN so that +the ystack-managed version takes precedence without requiring PATH changes to +$YSTACK_HOME/bin itself. Users must ensure ~/.local/bin is early in their PATH. + +Dependencies: + +Exit codes: + 0 Success + 1 Usage error + 2 Check or ensure failed +' + +case "${1:-}" in + help) echo "$YHELP"; exit 0 ;; + --help) echo "$YHELP"; exit 0 ;; +esac + +USER_BIN="$HOME/.local/bin" +KNOWN_DEFAULTS="yarn" + +subcmd="${1:-}" +# y-script-lint:disable=or-true # shift fails when no args after subcmd +shift || true + +case "$subcmd" in + check|ensure) ;; + "") echo "ERROR: subcommand required (check or ensure)" >&2; exit 1 ;; + *) echo "ERROR: unknown subcommand: $subcmd (use: check, ensure)" >&2; exit 1 ;; +esac + +bins=("$@") +if [ ${#bins[@]} -eq 0 ]; then + IFS=' ' read -ra bins <<< "$KNOWN_DEFAULTS" +fi + +failed=0 + +for bin in "${bins[@]}"; do + source_bin="$YBIN/$bin" + target_link="$USER_BIN/$bin" + + if [ ! -e "$source_bin" ] && [ ! -L "$source_bin" ]; then + echo "ERROR: $source_bin does not exist" >&2 + failed=1 + continue + fi + + link_exists=false + link_correct=false + if [ -L "$target_link" ]; then + link_exists=true + existing_target="$(readlink "$target_link")" + if [ "$existing_target" = "$source_bin" ] || [ "$existing_target" = "$(cd "$YBIN" && pwd)/$bin" ]; then + link_correct=true + fi + elif [ -e "$target_link" ]; then + echo "ERROR: $target_link exists but is not a symlink" >&2 + failed=1 + continue + fi + + case "$subcmd" in + check) + if [ "$link_exists" = "false" ]; then + echo "MISSING $target_link -> $source_bin" + failed=1 + continue + fi + if [ "$link_correct" = "false" ]; then + echo "WRONG $target_link -> $existing_target (expected $source_bin)" + failed=1 + continue + fi + ;; + ensure) + if [ "$link_exists" = "true" ] && [ "$link_correct" = "false" ]; then + echo "ERROR: $target_link -> $existing_target (not managed by ystack, remove manually)" >&2 + failed=1 + continue + fi + if [ "$link_correct" = "false" ]; then + mkdir -p "$USER_BIN" + ln -s "$source_bin" "$target_link" + echo "CREATED $target_link -> $source_bin" + fi + ;; + esac + + # Check PATH precedence + # y-script-lint:disable=or-true # command -v returns 1 when bin not in PATH + resolved="$(command -v "$bin" 2>/dev/null || true)" + if [ -z "$resolved" ]; then + echo "WARN $bin not found in PATH after linking" >&2 + failed=1 + continue + fi + + # Resolve the which result through symlinks to compare + resolved_real="$(readlink -f "$resolved" 2>/dev/null || echo "$resolved")" + source_real="$(readlink -f "$source_bin" 2>/dev/null || echo "$source_bin")" + + if [ "$resolved_real" != "$source_real" ]; then + echo "NO_PRECEDENCE $bin resolves to $resolved (expected $target_link -> $source_bin)" + echo " Recommendation: ensure ~/.local/bin appears before $(dirname "$resolved") in PATH" >&2 + if [ "$subcmd" = "ensure" ]; then + failed=1 + fi + else + echo "OK $bin -> $resolved" + fi +done + +[ "$failed" -ne 0 ] && exit 2 +exit 0 From 32d1931d6fd7e79500c890ebcb63e80b46e14e53 Mon Sep 17 00:00:00 2001 From: Yolean k8s-qa Date: Fri, 27 Mar 2026 11:49:24 +0000 Subject: [PATCH 2/9] y-bin-default: refuse to install y- prefixed binaries y-* binaries belong in ystack PATH, not in ~/.local/bin. The ensure subcommand now rejects any bin name starting with y- to prevent accidental shadowing of ystack scripts. Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/y-bin-default | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bin/y-bin-default b/bin/y-bin-default index b901cec..2bb4a0a 100755 --- a/bin/y-bin-default +++ b/bin/y-bin-default @@ -58,6 +58,12 @@ for bin in "${bins[@]}"; do source_bin="$YBIN/$bin" target_link="$USER_BIN/$bin" + if [[ "$bin" == y-* ]]; then + echo "ERROR: refusing to install y- prefixed binary $bin (y-* binaries belong in ystack PATH, not ~/.local/bin)" >&2 + failed=1 + continue + fi + if [ ! -e "$source_bin" ] && [ ! -L "$source_bin" ]; then echo "ERROR: $source_bin does not exist" >&2 failed=1 From 8c69d9148ea743c6cfed77c71eb1800f71c056a3 Mon Sep 17 00:00:00 2001 From: Yolean k8s-qa Date: Fri, 27 Mar 2026 11:55:22 +0000 Subject: [PATCH 3/9] y-bin-default: fix precedence check to compare command -v path directly The previous check resolved both paths through readlink -f and compared the final binaries. This could falsely report OK when a different binary in PATH happened to resolve to the same file, or when the shell hash cache pointed elsewhere. Now compares the command -v result directly against the expected ~/.local/bin/BIN path. Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/y-bin-default | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/bin/y-bin-default b/bin/y-bin-default index 2bb4a0a..fcc58c0 100755 --- a/bin/y-bin-default +++ b/bin/y-bin-default @@ -120,12 +120,9 @@ for bin in "${bins[@]}"; do continue fi - # Resolve the which result through symlinks to compare - resolved_real="$(readlink -f "$resolved" 2>/dev/null || echo "$resolved")" - source_real="$(readlink -f "$source_bin" 2>/dev/null || echo "$source_bin")" - - if [ "$resolved_real" != "$source_real" ]; then - echo "NO_PRECEDENCE $bin resolves to $resolved (expected $target_link -> $source_bin)" + # Check that the resolved path is our symlink in USER_BIN, not some other npx earlier in PATH + if [ "$resolved" != "$target_link" ]; then + echo "NO_PRECEDENCE $bin resolves to $resolved (expected $target_link)" echo " Recommendation: ensure ~/.local/bin appears before $(dirname "$resolved") in PATH" >&2 if [ "$subcmd" = "ensure" ]; then failed=1 From 335054fe2b1a5f49d3b951973fc1d0ae4b35e076 Mon Sep 17 00:00:00 2001 From: Yolean k8s-qa Date: Fri, 27 Mar 2026 12:17:30 +0000 Subject: [PATCH 4/9] Add y-npx wrapper and bump y1 to v0.2.0 for yarn and npx Adds y-npx as a y-bin-download wrapper that invokes the y1 binary with argv[0]=y-npx to trigger npx gate mode. Both yarn and npx entries in y-bin.runner.yaml are updated to v0.2.0 with new checksums. npx added to y-bin-default known defaults. Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/.gitignore | 1 + bin/y-bin-default | 4 ++-- bin/y-bin.runner.yaml | 20 +++++++++++++++----- bin/y-npx | 8 ++++++++ 4 files changed, 26 insertions(+), 7 deletions(-) create mode 100755 bin/y-npx diff --git a/bin/.gitignore b/bin/.gitignore index ac0c840..6832f91 100644 --- a/bin/.gitignore +++ b/bin/.gitignore @@ -55,3 +55,4 @@ osv-scanner chrome-devtools-mcp static-web-server yarn +npx diff --git a/bin/y-bin-default b/bin/y-bin-default index fcc58c0..90be3e2 100755 --- a/bin/y-bin-default +++ b/bin/y-bin-default @@ -15,7 +15,7 @@ Subcommands: Arguments: BIN Binary name (e.g. yarn). If omitted, iterates all known defaults. -Known defaults: yarn +Known defaults: yarn, npx The convention is to symlink ~/.local/bin/BIN -> $YSTACK_HOME/bin/BIN so that the ystack-managed version takes precedence without requiring PATH changes to @@ -35,7 +35,7 @@ case "${1:-}" in esac USER_BIN="$HOME/.local/bin" -KNOWN_DEFAULTS="yarn" +KNOWN_DEFAULTS="yarn npx" # no_npx: manages the npx default symlink subcmd="${1:-}" # y-script-lint:disable=or-true # shift fails when no args after subcmd diff --git a/bin/y-bin.runner.yaml b/bin/y-bin.runner.yaml index 5470d8b..6d8de7a 100755 --- a/bin/y-bin.runner.yaml +++ b/bin/y-bin.runner.yaml @@ -136,11 +136,21 @@ turbo: path: turbo-${os}-${xarm}64/bin/turbo yarn: - version: 0.1.0 + version: 0.2.0 templates: download: https://github.com/Yolean/y1/releases/download/v${version}/y1-${os}-${arch} sha256: - darwin_amd64: bd0310a0578a53d8639b1f480dc6258920a2b063f9270943ffb9eb3d2fa97416 - darwin_arm64: 8cb3f050fe85b4bc70a3ed88329ae203b6156ff420dc2e53638c50f6e22143cf - linux_amd64: 6cfe37280da7def39e661a2e48a708007b2ae1b2db206e287a808e38a0b4d2db - linux_arm64: 02a7071e09096992f8bb67b49b16bff43fbfde7af0bdee90a90d54a540d089ae + darwin_amd64: 4dbeaa1f7ab844fe82b18b810daed91b8e883a00f834ae114be4628ed5d884be + darwin_arm64: 513180cfe827e8d800d98503c74b0f05ffb9d6c1d3feddfd253f9587eb28e848 + linux_amd64: 53715555004fd739de7ac0e1340deb1e6aed7ac5daba43eedec83b2dc9628c6b + linux_arm64: 905f0cc96c84fa66bd3c1b887f7a4cb84a2f96b5b186a66828b76abcbe389e9b + +npx: + version: 0.2.0 + templates: + download: https://github.com/Yolean/y1/releases/download/v${version}/y1-${os}-${arch} + sha256: + darwin_amd64: 4dbeaa1f7ab844fe82b18b810daed91b8e883a00f834ae114be4628ed5d884be + darwin_arm64: 513180cfe827e8d800d98503c74b0f05ffb9d6c1d3feddfd253f9587eb28e848 + linux_amd64: 53715555004fd739de7ac0e1340deb1e6aed7ac5daba43eedec83b2dc9628c6b + linux_arm64: 905f0cc96c84fa66bd3c1b887f7a4cb84a2f96b5b186a66828b76abcbe389e9b diff --git a/bin/y-npx b/bin/y-npx new file mode 100755 index 0000000..f850a2f --- /dev/null +++ b/bin/y-npx @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +[ -z "$DEBUG" ] || set -x +set -eo pipefail +YBIN="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)" + +version=$(y-bin-download $YBIN/y-bin.runner.yaml npx) # no_npx + +exec -a y-npx "y-npx-v${version}-bin" "$@" # no_npx From 7c33b11f8c27a38acf9c539a19a18eb0e87159d3 Mon Sep 17 00:00:00 2001 From: Yolean k8s-qa Date: Fri, 27 Mar 2026 12:21:00 +0000 Subject: [PATCH 5/9] y-script-lint: exclude YHELP blocks from no_npx check Scripts that document npx in their help text (e.g. y-bin-default listing npx as a known default) should not trigger the no_npx lint. The check now strips YHELP blocks before scanning for npx usage. Also fixes pre-existing non-ASCII em dashes in comments. Co-Authored-By: Claude Opus 4.6 (1M context) --- bin/y-script-lint | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/y-script-lint b/bin/y-script-lint index 8129fcd..df03238 100755 --- a/bin/y-script-lint +++ b/bin/y-script-lint @@ -77,7 +77,7 @@ if [ ${#TARGET_DIRS[@]} -eq 1 ]; then elif [ -d "$arg" ]; then : # directory, handled below elif [[ "$arg" != */* ]]; then - # Bare name like "y-turbo" — search PATH + # Bare name like "y-turbo" -search PATH found="" IFS=: read -ra path_entries <<< "$PATH" for dir in "${path_entries[@]}"; do @@ -158,9 +158,9 @@ check_help_handler() { check_no_npx() { local file="$1" lang="$2" if is_node "$lang"; then - ! grep -vE '^\s*//' "$file" 2>/dev/null | grep -qE '\bnpx\b' + ! sed '/^const YHELP = `/,/^`;/d' "$file" 2>/dev/null | grep -vE '^\s*//' | grep -qE '\bnpx\b' else - ! grep -vE '^\s*#' "$file" 2>/dev/null | grep -vE '(no_npx|"uses npx"|No npx)' | grep -qE '\bnpx\b' + ! sed "/^YHELP='/,/^'/d" "$file" 2>/dev/null | grep -vE '^\s*#' | grep -vE '(no_npx|"uses npx"|No npx)' | grep -qE '\bnpx\b' fi } From 4bc62fb87d22d444a8cf5ebb24a21d08d950533d Mon Sep 17 00:00:00 2001 From: Staffan Olsson Date: Fri, 27 Mar 2026 13:34:11 +0100 Subject: [PATCH 6/9] y-bin-default: fix precedence check to skip ystack's own bin dir command -v found the binary in $YBIN (ystack/bin) which is the source, not a competing installation. Now iterates PATH entries excluding $YBIN to find the actual competing binary the user would get. --- README.md | 11 ++++++++++- bin/y-bin-default | 8 ++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 21442c8..1e5768a 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,16 @@ export YSTACK_HOME=/Users/me/Yolean/ystack export PATH=$PATH:$YSTACK_HOME/bin ``` -Note that ystach should be after system path entries because it contains fallback impls for MacOS such as `basepath` and `sha256sum`. +Note that ystack should be after system path entries because it contains fallback impls for MacOS such as `basepath` and `sha256sum`. + +However you're recommended to override default binaries for some commands. Run: + +``` +y-yarn help +y-npx help +hash -r +y-bin-default ensure +``` ## Why diff --git a/bin/y-bin-default b/bin/y-bin-default index 90be3e2..d3afc72 100755 --- a/bin/y-bin-default +++ b/bin/y-bin-default @@ -120,10 +120,10 @@ for bin in "${bins[@]}"; do continue fi - # Check that the resolved path is our symlink in USER_BIN, not some other npx earlier in PATH - if [ "$resolved" != "$target_link" ]; then - echo "NO_PRECEDENCE $bin resolves to $resolved (expected $target_link)" - echo " Recommendation: ensure ~/.local/bin appears before $(dirname "$resolved") in PATH" >&2 + # Check that the resolved path is our symlink in USER_BIN or our bin, not some other bin earlier in PATH + if [ "$resolved" != "$target_link" ] && [ "$resolved" != "$source_bin" ]; then + echo "NO_PRECEDENCE $bin resolves to $resolved via $(dirname "$resolved")" + echo " Recommendation: ensure $USER_BIN appears before $(dirname "$resolved") in PATH" >&2 if [ "$subcmd" = "ensure" ]; then failed=1 fi From 0a15905d8396dfd23d762bb9b38752ec22fd1a34 Mon Sep 17 00:00:00 2001 From: Staffan Olsson Date: Fri, 27 Mar 2026 14:19:23 +0100 Subject: [PATCH 7/9] fixes mac exec loop and adds y-yarn and y-npx to runner build --- bin/y-bin.runner.yaml | 20 ++++++++++---------- runner.Dockerfile | 6 ++++++ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/bin/y-bin.runner.yaml b/bin/y-bin.runner.yaml index 6d8de7a..43aa7a5 100755 --- a/bin/y-bin.runner.yaml +++ b/bin/y-bin.runner.yaml @@ -136,21 +136,21 @@ turbo: path: turbo-${os}-${xarm}64/bin/turbo yarn: - version: 0.2.0 + version: 0.2.1 templates: download: https://github.com/Yolean/y1/releases/download/v${version}/y1-${os}-${arch} sha256: - darwin_amd64: 4dbeaa1f7ab844fe82b18b810daed91b8e883a00f834ae114be4628ed5d884be - darwin_arm64: 513180cfe827e8d800d98503c74b0f05ffb9d6c1d3feddfd253f9587eb28e848 - linux_amd64: 53715555004fd739de7ac0e1340deb1e6aed7ac5daba43eedec83b2dc9628c6b - linux_arm64: 905f0cc96c84fa66bd3c1b887f7a4cb84a2f96b5b186a66828b76abcbe389e9b + darwin_amd64: 29538a2cb2a68ec9b5da75a35e85665057efa5479c518a6e7d57c4f97569b3d3 + darwin_arm64: 8ebb026be9f0bec5114691b82c67fae936f856df0598b4ab1642f1cf729936c0 + linux_amd64: 16252fe8ac0b3500bd697bc47213cc438209e2d5f8a812def075a0cdec891301 + linux_arm64: 05ca3451a6f78a68b08c13b4b0b582cb22220b90ccd00160bffff80224d8c50d npx: - version: 0.2.0 + version: 0.2.1 templates: download: https://github.com/Yolean/y1/releases/download/v${version}/y1-${os}-${arch} sha256: - darwin_amd64: 4dbeaa1f7ab844fe82b18b810daed91b8e883a00f834ae114be4628ed5d884be - darwin_arm64: 513180cfe827e8d800d98503c74b0f05ffb9d6c1d3feddfd253f9587eb28e848 - linux_amd64: 53715555004fd739de7ac0e1340deb1e6aed7ac5daba43eedec83b2dc9628c6b - linux_arm64: 905f0cc96c84fa66bd3c1b887f7a4cb84a2f96b5b186a66828b76abcbe389e9b + darwin_amd64: 29538a2cb2a68ec9b5da75a35e85665057efa5479c518a6e7d57c4f97569b3d3 + darwin_arm64: 8ebb026be9f0bec5114691b82c67fae936f856df0598b4ab1642f1cf729936c0 + linux_amd64: 16252fe8ac0b3500bd697bc47213cc438209e2d5f8a812def075a0cdec891301 + linux_arm64: 05ca3451a6f78a68b08c13b4b0b582cb22220b90ccd00160bffff80224d8c50d diff --git a/runner.Dockerfile b/runner.Dockerfile index b617075..faf58d7 100644 --- a/runner.Dockerfile +++ b/runner.Dockerfile @@ -47,6 +47,12 @@ COPY bin/y-bin.runner.yaml \ bin/y-bin-dependency-download \ /usr/local/src/ystack/bin/ +COPY bin/y-yarn /usr/local/src/ystack/bin/ +RUN y-yarn help + +COPY bin/y-npx /usr/local/src/ystack/bin/ +RUN y-npx help || Y_NPX_ALLOWED_CMDS=--version y-npx --version + COPY bin/y-kubectl /usr/local/src/ystack/bin/ RUN y-kubectl version --client=true --output=json From 35f17e2df55665e77e53fdad8e0520acda6f8ba6 Mon Sep 17 00:00:00 2001 From: Staffan Olsson Date: Fri, 27 Mar 2026 14:22:18 +0100 Subject: [PATCH 8/9] actually there is no npx in path here --- runner.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runner.Dockerfile b/runner.Dockerfile index faf58d7..db940a3 100644 --- a/runner.Dockerfile +++ b/runner.Dockerfile @@ -51,7 +51,7 @@ COPY bin/y-yarn /usr/local/src/ystack/bin/ RUN y-yarn help COPY bin/y-npx /usr/local/src/ystack/bin/ -RUN y-npx help || Y_NPX_ALLOWED_CMDS=--version y-npx --version +RUN ! y-npx 2>/dev/null COPY bin/y-kubectl /usr/local/src/ystack/bin/ RUN y-kubectl version --client=true --output=json From 5cd46c8ac2194922d82791c0cbf1fae24a5cf63c Mon Sep 17 00:00:00 2001 From: Staffan Olsson Date: Fri, 27 Mar 2026 14:22:54 +0100 Subject: [PATCH 9/9] nodejs 24.14.0->24.14.1 --- runner.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runner.Dockerfile b/runner.Dockerfile index db940a3..984fcc1 100644 --- a/runner.Dockerfile +++ b/runner.Dockerfile @@ -37,7 +37,7 @@ ENV YSTACK_HOME=/usr/local/src/ystack \ DO_NOT_TRACK=1 \ npm_config_update_notifier=false -FROM --platform=$TARGETPLATFORM node:24.14.0-trixie-slim@sha256:4fc981bf8dfc5e36e15e0cb73c5761a14cabff0932dcad1cf26cd3c3425db5d4 \ +FROM --platform=$TARGETPLATFORM node:24.14.1-trixie-slim@sha256:c319bb4fac67c01ced508b67193a0397e02d37555d8f9b72958649efd302b7f8 \ as node FROM base as bin