# ncli/commands/dev.nix — dev init / track / untrack lib: { dev_case = '' # Only flake files need assume-unchanged (Nix requires them in the index). # .envrc and .direnv are handled via .git/info/exclude instead. DEV_FILES=(flake.nix flake.lock) DIRENV_LOCAL_FILES=(.envrc .direnv) _dev_resolve_dir() { read -rp " Enter target directory [./]: " TARGET_DIR TARGET_DIR="''${TARGET_DIR:-./}" if [[ ! -d "$TARGET_DIR" ]]; then print_error "Directory '$TARGET_DIR' does not exist." exit 1 fi TARGET_ABS="$(cd "$TARGET_DIR" && pwd)" if ! git -C "$TARGET_ABS" rev-parse --git-dir > /dev/null 2>&1; then print_error "No Git repository found at '$TARGET_ABS'." exit 1 fi } _add_local_excludes() { local repo_abs="$1" local git_dir exclude_file git_dir="$(git -C "$repo_abs" rev-parse --git-dir)" exclude_file="$git_dir/info/exclude" mkdir -p "$(dirname "$exclude_file")" touch "$exclude_file" for entry in "''${DIRENV_LOCAL_FILES[@]}"; do if ! grep -qxF "$entry" "$exclude_file" 2>/dev/null; then echo "$entry" >> "$exclude_file" print_success "$entry → ignored via .git/info/exclude (local only)" else print_info "$entry already in .git/info/exclude, skipping." fi done } _remove_local_excludes() { local repo_abs="$1" local git_dir exclude_file tmp_file git_dir="$(git -C "$repo_abs" rev-parse --git-dir)" exclude_file="$git_dir/info/exclude" [[ -f "$exclude_file" ]] || return 0 tmp_file="$(mktemp)" cp "$exclude_file" "$tmp_file" for entry in "''${DIRENV_LOCAL_FILES[@]}"; do if grep -qxF "$entry" "$tmp_file" 2>/dev/null; then grep -vxF "$entry" "$tmp_file" > "''${tmp_file}.new" || true mv "''${tmp_file}.new" "$tmp_file" print_success "$entry → removed from .git/info/exclude" else print_info "$entry not present in .git/info/exclude, skipping." fi done mv "$tmp_file" "$exclude_file" } case "''${2:-init}" in untrack) echo "" echo -e "''${BLUE}--- Dev: Track Files (assume-unchanged) ---''${NOCOLOR}" echo "" _dev_resolve_dir acted=false for f in "''${DEV_FILES[@]}"; do full="$TARGET_ABS/$f" if [[ ! -f "$full" ]]; then print_info "$f not found in $TARGET_ABS — skipping." continue fi if ! git -C "$TARGET_ABS" ls-files --error-unmatch "$f" > /dev/null 2>&1; then git -C "$TARGET_ABS" add --intent-to-add "$f" fi git -C "$TARGET_ABS" update-index --assume-unchanged "$f" print_success "$f → untracked (assume-unchanged)" acted=true done _add_local_excludes "$TARGET_ABS" if ! $acted; then print_info "No dev files found in '$TARGET_ABS' to track." fi ;; track) echo "" echo -e "''${BLUE}--- Dev: Untrack Files (remove assume-unchanged) ---''${NOCOLOR}" echo "" _dev_resolve_dir acted=false for f in "''${DEV_FILES[@]}"; do if git -C "$TARGET_ABS" ls-files -v "$f" 2>/dev/null | grep -q "^h "; then git -C "$TARGET_ABS" update-index --no-assume-unchanged "$f" print_success "$f → tracked (assume-unchanged removed)" acted=true else print_info "$f is not marked assume-unchanged — skipping." fi done _remove_local_excludes "$TARGET_ABS" if ! $acted; then print_info "No files in '$TARGET_ABS' had the assume-unchanged bit set." fi ;; init|*) SOURCE_FLAKE="$HOME/${lib.project}/other/dev-template.nix" if [[ ! -f "$SOURCE_FLAKE" ]]; then print_error "Source flake template not found: $SOURCE_FLAKE" echo "Please ensure dev-template.nix exists in $HOME/${lib.project}/other/" exit 1 fi print_header # ---- Step 1: Target Directory ---- echo -e "''${YELLOW}Step 1/5: Target Directory''${NOCOLOR}" echo "" read -p " Enter target directory [./]: " TARGET_DIR if [[ -z "$TARGET_DIR" ]]; then TARGET_DIR="./" fi if [[ ! -d "$TARGET_DIR" ]]; then print_error "Directory '$TARGET_DIR' does not exist. Exiting." exit 1 fi print_success "Target directory: $TARGET_DIR" echo "" # ---- Check for existing flake ---- if [[ -f "$TARGET_DIR/flake.nix" ]]; then echo "" print_error "A flake.nix already exists in '$TARGET_DIR'!" echo "" read -p " Would you like to just place the .envrc instead? [y/N]: " ENVRC_ONLY ENVRC_ONLY="''${ENVRC_ONLY:-N}" if [[ "$ENVRC_ONLY" =~ ^[Yy]$ ]]; then TARGET_ABS="$(cd "$TARGET_DIR" && pwd)" echo "" print_info "Placing .envrc only..." if ! grep -qxF "use flake" "$TARGET_ABS/.envrc" 2>/dev/null; then echo "use flake" >> "$TARGET_ABS/.envrc" print_success ".envrc created at $TARGET_ABS/" else print_info ".envrc already contains 'use flake', nothing to do." fi echo "" print_info "Next steps:" echo " cd $TARGET_DIR" echo " direnv allow" echo "" exit 0 else print_info "Aborting. No files were changed." exit 1 fi fi # ---- Step 2: Project Name ---- echo -e "''${YELLOW}Step 2/5: Project Name''${NOCOLOR}" echo "" read -p " Enter development environment name [devShell]: " PROJECT_NAME if [[ -z "$PROJECT_NAME" ]]; then PROJECT_NAME="devShell" fi print_success "Project name: $PROJECT_NAME" echo "" # ---- Step 3: Select template ---- echo -e "''${YELLOW}Step 3/5: Select Template''${NOCOLOR}" echo "" echo " 1) Empty - Basic shell, add packages yourself" echo " 2) Python - Python 3, pip, venv setup" echo " 3) Node.js - Node.js, npm" echo " 4) Rust - Rustc, cargo, rust-analyzer" echo " 5) Go - Go, gopls, golangci-lint" echo " 6) C/C++ - GCC, CMake, GDB, pkg-config" echo " 7) Java - JDK 21, Maven" echo "" read -p " Select template [1-7] (default: 1): " TEMPLATE_CHOICE case "$TEMPLATE_CHOICE" in 2) TEMPLATE="python3" PACKAGES="python3 python3Packages.pip python3Packages.virtualenv" BUILD_INPUTS="" SHELL_HOOK_EXTRA=" if [ ! -d .venv ]; then echo 'Creating Python virtual environment...' python3 -m venv .venv fi source .venv/bin/activate echo 'Python venv activated.' " ;; 3) TEMPLATE="node" PACKAGES="nodejs_22" BUILD_INPUTS="" SHELL_HOOK_EXTRA='echo "Node.js $(node --version) ready."' ;; 4) TEMPLATE="rust" PACKAGES="rustc cargo rust-analyzer" BUILD_INPUTS="openssl pkg-config" SHELL_HOOK_EXTRA='echo "Rust $(rustc --version) ready."' ;; 5) TEMPLATE="go" PACKAGES="go gopls golangci-lint" BUILD_INPUTS="" SHELL_HOOK_EXTRA=' export GOPATH="$PWD/.gopath" export PATH="$GOPATH/bin:$PATH" mkdir -p "$GOPATH" echo "Go $(go version) ready. GOPATH: $GOPATH" ' ;; 6) TEMPLATE="cpp" PACKAGES="gcc gdb cmake gnumake" BUILD_INPUTS="pkg-config" SHELL_HOOK_EXTRA='echo "GCC $(gcc --version | head -1) ready."' ;; 7) TEMPLATE="java" PACKAGES="jdk21 maven" BUILD_INPUTS="" SHELL_HOOK_EXTRA='echo "Java $(java --version | head -1) ready."' ;; *) TEMPLATE="empty" PACKAGES="" BUILD_INPUTS="" SHELL_HOOK_EXTRA="" ;; esac print_success "Selected template: $TEMPLATE" echo "" # ---- Step 4: Copy and modify flake ---- echo -e "''${YELLOW}Step 4/5: Generating flake.nix''${NOCOLOR}" echo "" cp "$SOURCE_FLAKE" "$TARGET_DIR/flake.nix" print_success "Copied flake.nix to $TARGET_DIR/" sed -i "s|name = \"replaceNameHere\";|name = \"''${PROJECT_NAME}\";|" "$TARGET_DIR/flake.nix" print_success "Set project name: $PROJECT_NAME" if [[ -n "$PACKAGES" ]]; then sed -i "s|# replacePackagesHere|$PACKAGES|" "$TARGET_DIR/flake.nix" print_success "Added packages: $PACKAGES" fi if [[ -n "$BUILD_INPUTS" ]]; then BUILD_INPUTS_LINE="buildInputs = with pkgs; [ $BUILD_INPUTS ];" sed -i "s|# replaceBuildInputsHere|$BUILD_INPUTS_LINE|" "$TARGET_DIR/flake.nix" print_success "Added build inputs: $BUILD_INPUTS" fi if [[ -n "$SHELL_HOOK_EXTRA" ]]; then awk -v hook="$SHELL_HOOK_EXTRA" '/# replaceShellHookHere/ { print hook; next } { print }' \ "$TARGET_DIR/flake.nix" > "$TARGET_DIR/flake.nix.tmp" \ && mv "$TARGET_DIR/flake.nix.tmp" "$TARGET_DIR/flake.nix" print_success "Added template-specific shell hook" fi # ---- Step 5: Git Integration & .envrc ---- echo -e "''${YELLOW}Step 5/5: Git Integration''${NOCOLOR}" echo "" TARGET_ABS="$(cd "$TARGET_DIR" && pwd)" echo "Creating .envrc for direnv to automatically work!" if ! grep -qxF "use flake" "$TARGET_ABS/.envrc" 2>/dev/null; then echo "use flake" >> "$TARGET_ABS/.envrc" print_success ".envrc created at $TARGET_ABS/" else print_info ".envrc already contains 'use flake', skipping." fi if git -C "$TARGET_ABS" rev-parse --git-dir > /dev/null 2>&1; then echo " A Git repository was detected at:" echo " $(git -C "$TARGET_ABS" rev-parse --show-toplevel)" echo "" _add_local_excludes "$TARGET_ABS" echo "" echo " Nix flakes require flake.nix (and flake.lock) to be Git-tracked." echo " This script can stage them as 'assume-unchanged' so Nix sees them," echo " but they will NEVER be committed or show up in git status." echo "" read -p " Set up flake files as tracked-but-invisible to Git? [y/N]: " GIT_CHOICE GIT_CHOICE="''${GIT_CHOICE:-N}" if [[ "$GIT_CHOICE" =~ ^[Yy]$ ]]; then cat > "$TARGET_ABS/flake.lock" << 'FLAKE_LOCK_EOF' { "nodes": { "root": {} }, "root": "root", "version": 7 } FLAKE_LOCK_EOF print_success "flake.lock ready (Nix will populate it on first run if empty)" git -C "$TARGET_ABS" add --intent-to-add flake.nix flake.lock git -C "$TARGET_ABS" update-index --assume-unchanged flake.nix flake.lock print_success "flake.nix + flake.lock → tracked (assume-unchanged)" else print_info "Skipped. You can do this manually later:" echo "" echo " git -C \"$TARGET_ABS\" add --intent-to-add flake.nix flake.lock" echo " git -C \"$TARGET_ABS\" update-index --assume-unchanged flake.nix flake.lock" echo "" fi else print_info "No Git repository detected — skipping Git integration." echo "" fi # ---- Done ---- echo -e "''${GREEN}==============================================''${NOCOLOR}" echo -e "''${GREEN} Done! Your flake is ready at:''${NOCOLOR}" echo -e "''${GREEN} $TARGET_DIR/flake.nix''${NOCOLOR}" echo -e "''${GREEN}==============================================''${NOCOLOR}" echo "" print_info "Next steps:" echo " cd $TARGET_DIR" echo " direnv allow # Trust the .envrc so direnv auto-activates" echo " nix develop # Or just: cd out and back in" echo "" ;; esac ;; ''; }