willow-core 0.1.0

Rust + JS framework for Solana programs without IDL
Documentation
#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

cmd="${1:-help}"
shift || true

case "$cmd" in
  init)
    name=""
    path=""
    while [[ $# -gt 0 ]]; do
      case "$1" in
        --name)
          name="${2:-}"
          shift 2
          ;;
        --path)
          path="${2:-}"
          shift 2
          ;;
        -*)
          shift
          ;;
        *)
          if [[ -z "$name" ]]; then
            name="$1"
          fi
          shift
          ;;
      esac
    done
    if [[ -z "$path" ]]; then
      path="$(pwd)"
    fi

    dest=""
    if [[ -n "$name" ]]; then
      dest="$path/$name"
    else
      if [[ -z "$(ls -A "$path" 2>/dev/null)" ]]; then
        name="$(basename "$path")"
        dest="$path"
      else
        name="program"
        dest="$path/$name"
      fi
    fi

    if [[ -e "$dest" && -n "$(ls -A "$dest" 2>/dev/null)" ]]; then
      echo "Refusing to init in non-empty directory: $dest" >&2
      exit 1
    fi

    template_dir="$ROOT_DIR/templates/program"
    if [[ ! -d "$template_dir" ]]; then
      echo "Template not found: $template_dir" >&2
      exit 1
    fi

    mkdir -p "$dest"
    cp -R "$template_dir/." "$dest"
    perl -pi -e "s/\\{\\{name\\}\\}/$name/g" "$dest/Cargo.toml"

    echo "Initialized willow program in: $dest"
    ;;
  build)
    path=""
    no_raydium="false"
    while [[ $# -gt 0 ]]; do
      case "$1" in
        --path)
          path="${2:-}"
          shift 2
          ;;
        --no-raydium)
          no_raydium="true"
          shift
          ;;
        *)
          shift
          ;;
      esac
    done
    if [[ -z "$path" ]]; then
      path="$(pwd)"
    fi
    get_name() {
      local cargo_toml="$1/Cargo.toml"
      awk -F'=' '
        /^\[package\]/ {in_pkg=1; next}
        /^\[/ {in_pkg=0}
        in_pkg && $1 ~ /^[[:space:]]*name[[:space:]]*$/ {
          gsub(/[[:space:]\"]/, "", $2);
          print $2;
          exit
        }
      ' "$cargo_toml"
    }

    ensure_program_id() {
      local program_path="$1"
      local name="$2"
      local deploy_dir="$program_path/target/deploy"
      local keypair_path="$deploy_dir/${name}-keypair.json"
      local program_id_rs="$program_path/src/program_id.rs"

      mkdir -p "$deploy_dir"
      if [[ ! -f "$keypair_path" ]]; then
        solana-keygen new --no-bip39-passphrase -o "$keypair_path" -s >/dev/null
      fi
      local pubkey
      if [[ "$keypair_path" == *" "* ]]; then
        local tmp_keypair
        tmp_keypair="$(mktemp)"
        cp "$keypair_path" "$tmp_keypair"
        pubkey="$(solana-keygen pubkey "$tmp_keypair")"
        rm -f "$tmp_keypair"
      else
        pubkey="$(solana-keygen pubkey "$keypair_path")"
      fi

      if [[ ! -f "$program_id_rs" ]]; then
        cat > "$program_id_rs" <<EOF
// Auto-generated by willow. Do not edit.
use pinocchio_pubkey::declare_id;

declare_id!("$pubkey");
EOF
      else
        perl -pi -e "s/declare_id!\\(\\\"[^\\\"]*\\\"\\)/declare_id!(\\\"$pubkey\\\")/" "$program_id_rs"
      fi
    }

    name="$(get_name "$path")"
    ensure_program_id "$path" "$name"
    so_path="$path/target/deploy/${name}.so"
    before_size=""
    if [[ -f "$so_path" ]]; then
      before_size="$(stat -f%z "$so_path" 2>/dev/null || stat -c%s "$so_path")"
    fi

    if [[ "$no_raydium" == "true" ]]; then
      cargo build-sbf --manifest-path "$path/Cargo.toml" -- --no-default-features
    else
      cargo build-sbf --manifest-path "$path/Cargo.toml"
    fi

    fmt() {
      local b="$1"
      if (( b >= 1048576 )); then
        printf "%.2f MB" "$(awk "BEGIN {print $b/1048576}")"
      else
        printf "%.2f KB" "$(awk "BEGIN {print $b/1024}")"
      fi
    }

    if [[ -f "$so_path" ]]; then
      after_size="$(stat -f%z "$so_path" 2>/dev/null || stat -c%s "$so_path")"
      if [[ -n "$before_size" ]]; then
        delta=$((after_size - before_size))
        sign=""
        if (( delta > 0 )); then sign="+"; fi
        echo "sbf size: ${before_size} -> ${after_size} bytes (${sign}${delta}) | $(fmt "$before_size") -> $(fmt "$after_size")"
      else
        echo "sbf size: ${after_size} bytes | $(fmt "$after_size")"
      fi
    else
      echo "sbf size: not found at ${so_path}"
    fi
    ;;
  deploy)
    path=""
    keypair=""
    program_id=""
    while [[ $# -gt 0 ]]; do
      case "$1" in
        --path)
          path="${2:-}"
          shift 2
          ;;
        --keypair)
          keypair="${2:-}"
          shift 2
          ;;
        --program-id)
          program_id="${2:-}"
          shift 2
          ;;
        -*)
          shift
          ;;
        *)
          shift
          ;;
      esac
    done
    if [[ -z "$path" ]]; then
      path="$(pwd)"
    fi
    get_name() {
      local cargo_toml="$1/Cargo.toml"
      awk -F'=' '
        /^\[package\]/ {in_pkg=1; next}
        /^\[/ {in_pkg=0}
        in_pkg && $1 ~ /^[[:space:]]*name[[:space:]]*$/ {
          gsub(/[[:space:]\"]/, "", $2);
          print $2;
          exit
        }
      ' "$cargo_toml"
    }
    name="$(get_name "$path")"
    so_path="$path/target/deploy/${name}.so"
    if [[ ! -f "$so_path" ]]; then
      echo "sbf file not found: $so_path" >&2
      exit 1
    fi
    cmd=(solana program deploy "$so_path")
    if [[ -n "$program_id" ]]; then
      cmd+=("--program-id" "$program_id")
    fi
    if [[ -n "$keypair" ]]; then
      cmd+=("--keypair" "$keypair")
    fi
    (cd "$path" && "${cmd[@]}")
    ;;
  gen)
    path=""
    while [[ $# -gt 0 ]]; do
      case "$1" in
        --path)
          path="${2:-}"
          shift 2
          ;;
        *)
          shift
          ;;
      esac
    done
    if [[ -z "$path" ]]; then
      path="$(pwd)"
    fi
    (cd "$path" && cargo build)
    ;;
  help|-h|--help)
    echo "willow"
    echo "Usage:"
    echo "  willow init [--name <program_name>] [--path <dir>]"
    echo "  willow build [--path <program_dir>] [--no-raydium]   # build-sbf"
    echo "  willow deploy [--path <program_dir>] [--keypair <path>] [--program-id <path>]"
    echo "  willow gen [--path <program_dir>]     # host build for codegen"
    echo "  willow help"
    ;;
  *)
    echo "Unknown command: $cmd" >&2
    echo "Run: willow help" >&2
    exit 1
    ;;
esac