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/.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 new file mode 100755 index 0000000..d3afc72 --- /dev/null +++ b/bin/y-bin-default @@ -0,0 +1,136 @@ +#!/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, 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 +$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 npx" # no_npx: manages the npx default symlink + +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 [[ "$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 + 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 + + # 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 + else + echo "OK $bin -> $resolved" + fi +done + +[ "$failed" -ne 0 ] && exit 2 +exit 0 diff --git a/bin/y-bin.runner.yaml b/bin/y-bin.runner.yaml index 5470d8b..43aa7a5 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.1 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: 29538a2cb2a68ec9b5da75a35e85665057efa5479c518a6e7d57c4f97569b3d3 + darwin_arm64: 8ebb026be9f0bec5114691b82c67fae936f856df0598b4ab1642f1cf729936c0 + linux_amd64: 16252fe8ac0b3500bd697bc47213cc438209e2d5f8a812def075a0cdec891301 + linux_arm64: 05ca3451a6f78a68b08c13b4b0b582cb22220b90ccd00160bffff80224d8c50d + +npx: + version: 0.2.1 + templates: + download: https://github.com/Yolean/y1/releases/download/v${version}/y1-${os}-${arch} + sha256: + darwin_amd64: 29538a2cb2a68ec9b5da75a35e85665057efa5479c518a6e7d57c4f97569b3d3 + darwin_arm64: 8ebb026be9f0bec5114691b82c67fae936f856df0598b4ab1642f1cf729936c0 + linux_amd64: 16252fe8ac0b3500bd697bc47213cc438209e2d5f8a812def075a0cdec891301 + linux_arm64: 05ca3451a6f78a68b08c13b4b0b582cb22220b90ccd00160bffff80224d8c50d 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 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 } diff --git a/runner.Dockerfile b/runner.Dockerfile index b617075..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 @@ -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 2>/dev/null + COPY bin/y-kubectl /usr/local/src/ystack/bin/ RUN y-kubectl version --client=true --output=json