advent of code 2025 in ts and nix
1{ 2 description = "Advent of Code 2025 Solutions"; 3 4 inputs = { 5 nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 flake-utils.url = "github:numtide/flake-utils"; 7 }; 8 9 outputs = { self, nixpkgs, flake-utils }: 10 flake-utils.lib.eachDefaultSystem (system: 11 let 12 pkgs = nixpkgs.legacyPackages.${system}; 13 gum = pkgs.gum; 14 15 # Get list of available days 16 getDays = '' 17 days=() 18 for dir in ts/* nix/*; do 19 if [ -d "$dir" ]; then 20 day=$(basename "$dir") 21 if [[ ! " ''${days[@]} " =~ " ''${day} " ]]; then 22 days+=("$day") 23 fi 24 fi 25 done 26 echo "''${days[@]}" | tr ' ' '\n' | sort 27 ''; 28 29 # Interactive runner with gum 30 runner = pkgs.writeShellScriptBin "aoc" '' 31 set -e 32 33 # Parse flags 34 day_flag="" 35 lang_flag="" 36 action_flag="" 37 38 while [[ $# -gt 0 ]]; do 39 case $1 in 40 --day|-d) 41 day_flag="$2" 42 action_flag="run" 43 shift 2 44 ;; 45 --lang|-l) 46 lang_flag="$2" 47 shift 2 48 ;; 49 --all|-a) 50 action_flag="all" 51 shift 52 ;; 53 --init|-i) 54 action_flag="init" 55 shift 56 ;; 57 *) 58 echo "Unknown option: $1" 59 echo "Usage: aoc [--day DAY] [--lang ts|nix] [--all] [--init]" 60 exit 1 61 ;; 62 esac 63 done 64 65 # If running with flags, skip interactive mode 66 if [ -n "$day_flag" ]; then 67 # Format day with leading zero 68 day=$(printf "%02d" $((10#$day_flag))) 69 70 # Determine which languages to run 71 run_ts=false 72 run_nix=false 73 74 if [ -z "$lang_flag" ] || [ "$lang_flag" = "both" ]; then 75 [ -f "ts/$day/index.ts" ] && run_ts=true 76 [ -f "nix/$day/solution.nix" ] && run_nix=true 77 elif [ "$lang_flag" = "ts" ] || [ "$lang_flag" = "typescript" ]; then 78 [ -f "ts/$day/index.ts" ] && run_ts=true 79 elif [ "$lang_flag" = "nix" ]; then 80 [ -f "nix/$day/solution.nix" ] && run_nix=true 81 else 82 echo "Unknown language: $lang_flag (use ts or nix)" 83 exit 1 84 fi 85 86 if [ "$run_ts" = false ] && [ "$run_nix" = false ]; then 87 echo "No solutions found for day $day" 88 exit 1 89 fi 90 91 ${gum}/bin/gum style --border rounded --border-foreground 212 --padding "0 1" "Day $day" 92 echo "" 93 94 if [ "$run_ts" = true ]; then 95 ${gum}/bin/gum style --foreground 212 "TypeScript:" 96 (cd ts/$day && ${pkgs.bun}/bin/bun run index.ts) 97 echo "" 98 fi 99 100 if [ "$run_nix" = true ]; then 101 ${gum}/bin/gum style --foreground 212 "Nix:" 102 result=$(${pkgs.nix}/bin/nix-instantiate --eval --strict --json nix/$day/solution.nix) 103 echo "$result" | ${pkgs.jq}/bin/jq -r 'to_entries | .[] | "\(.key): \(.value)"' 104 fi 105 106 exit 0 107 fi 108 109 # If --all flag, run all solutions 110 if [ "$action_flag" = "all" ]; then 111 all_days=() 112 for dir in ts/* nix/*; do 113 if [ -d "$dir" ]; then 114 day=$(basename "$dir") 115 if [[ ! " ''${all_days[@]} " =~ " ''${day} " ]]; then 116 all_days+=("$day") 117 fi 118 fi 119 done 120 121 for daynum in $(printf '%s\n' "''${all_days[@]}" | sort); do 122 ${gum}/bin/gum style --border rounded --border-foreground 212 --padding "0 1" "Day $daynum" 123 echo "" 124 125 if [ -f "ts/$daynum/index.ts" ]; then 126 ${gum}/bin/gum style --foreground 212 "TypeScript:" 127 (cd ts/$daynum && ${pkgs.bun}/bin/bun run index.ts) 128 echo "" 129 fi 130 131 if [ -f "nix/$daynum/solution.nix" ]; then 132 ${gum}/bin/gum style --foreground 212 "Nix:" 133 result=$(${pkgs.nix}/bin/nix-instantiate --eval --strict --json nix/$daynum/solution.nix) 134 echo "$result" | ${pkgs.jq}/bin/jq -r 'to_entries | .[] | "\(.key): \(.value)"' 135 echo "" 136 fi 137 done 138 139 exit 0 140 fi 141 142 # Interactive mode 143 ${gum}/bin/gum style \ 144 --border double \ 145 --border-foreground 212 \ 146 --padding "1 2" \ 147 --margin "1 0" \ 148 "$(${gum}/bin/gum style --foreground 212 '🎄 Advent of Code 2025 🎄')" 149 150 # Choose action 151 if [ "$action_flag" = "init" ]; then 152 action="Init new day" 153 else 154 action=$(${gum}/bin/gum choose "Run specific day" "Run all solutions" "Init new day" "Exit") 155 fi 156 157 case "$action" in 158 "Run specific day") 159 # Get available days 160 days=$(${getDays}) 161 162 if [ -z "$days" ]; then 163 ${gum}/bin/gum style --foreground 196 "No solutions found!" 164 exit 1 165 fi 166 167 day=$(echo "$days" | ${gum}/bin/gum choose) 168 169 # Check what languages are available for this day 170 langs=() 171 [ -f "ts/$day/index.ts" ] && langs+=("TypeScript") 172 [ -f "nix/$day/solution.nix" ] && langs+=("Nix") 173 langs+=("Both") 174 175 lang=$(printf '%s\n' "''${langs[@]}" | ${gum}/bin/gum choose) 176 177 echo "" 178 case "$lang" in 179 "TypeScript") 180 if [ -f "ts/$day/index.ts" ]; then 181 ${gum}/bin/gum style --border rounded --border-foreground 212 --padding "0 1" "Day $day" 182 echo "" 183 ${gum}/bin/gum style --foreground 212 "TypeScript:" 184 (cd ts/$day && ${pkgs.bun}/bin/bun run index.ts) 185 fi 186 ;; 187 "Nix") 188 if [ -f "nix/$day/solution.nix" ]; then 189 ${gum}/bin/gum style --border rounded --border-foreground 212 --padding "0 1" "Day $day" 190 echo "" 191 ${gum}/bin/gum style --foreground 212 "Nix:" 192 result=$(${pkgs.nix}/bin/nix-instantiate --eval --strict --json nix/$day/solution.nix) 193 echo "$result" | ${pkgs.jq}/bin/jq -r 'to_entries | .[] | "\(.key): \(.value)"' 194 fi 195 ;; 196 "Both") 197 ${gum}/bin/gum style --border rounded --border-foreground 212 --padding "0 1" "Day $day" 198 echo "" 199 if [ -f "ts/$day/index.ts" ]; then 200 ${gum}/bin/gum style --foreground 212 "TypeScript:" 201 (cd ts/$day && ${pkgs.bun}/bin/bun run index.ts) 202 echo "" 203 fi 204 if [ -f "nix/$day/solution.nix" ]; then 205 ${gum}/bin/gum style --foreground 212 "Nix:" 206 result=$(${pkgs.nix}/bin/nix-instantiate --eval --strict --json nix/$day/solution.nix) 207 echo "$result" | ${pkgs.jq}/bin/jq -r 'to_entries | .[] | "\(.key): \(.value)"' 208 fi 209 ;; 210 esac 211 ;; 212 213 "Run all solutions") 214 # Collect all unique days 215 all_days=() 216 for dir in ts/* nix/*; do 217 if [ -d "$dir" ]; then 218 day=$(basename "$dir") 219 if [[ ! " ''${all_days[@]} " =~ " ''${day} " ]]; then 220 all_days+=("$day") 221 fi 222 fi 223 done 224 225 # Sort and run each day 226 for daynum in $(printf '%s\n' "''${all_days[@]}" | sort); do 227 ${gum}/bin/gum style --border rounded --border-foreground 212 --padding "0 1" "Day $daynum" 228 echo "" 229 230 if [ -f "ts/$daynum/index.ts" ]; then 231 ${gum}/bin/gum style --foreground 212 "TypeScript:" 232 (cd ts/$daynum && ${pkgs.bun}/bin/bun run index.ts) 233 echo "" 234 fi 235 236 if [ -f "nix/$daynum/solution.nix" ]; then 237 ${gum}/bin/gum style --foreground 212 "Nix:" 238 result=$(${pkgs.nix}/bin/nix-instantiate --eval --strict --json nix/$daynum/solution.nix) 239 echo "$result" | ${pkgs.jq}/bin/jq -r 'to_entries | .[] | "\(.key): \(.value)"' 240 echo "" 241 fi 242 done 243 ;; 244 245 "Init new day") 246 # Get all existing days 247 all_days=() 248 for dir in ts/* nix/* shared/*; do 249 if [ -d "$dir" ]; then 250 day=$(basename "$dir") 251 if [[ "$day" =~ ^[0-9]+$ ]]; then 252 all_days+=("$day") 253 fi 254 fi 255 done 256 257 # Find next day number 258 next_day=01 259 if [ ''${#all_days[@]} -gt 0 ]; then 260 max_day=$(printf '%s\n' "''${all_days[@]}" | sort -n | tail -1) 261 next_day=$(printf "%02d" $((10#$max_day + 1))) 262 fi 263 264 # Show form with prefilled day 265 ${gum}/bin/gum style --foreground 212 "Initializing new day" 266 echo "" 267 268 day=$(${gum}/bin/gum input --placeholder "Day number" --value "$next_day" --prompt "Day: ") 269 270 # Validate day is a number 271 if ! [[ "$day" =~ ^[0-9]+$ ]]; then 272 ${gum}/bin/gum style --foreground 196 "Invalid day number!" 273 exit 1 274 fi 275 276 # Format with leading zero 277 day=$(printf "%02d" $((10#$day))) 278 279 # Check if day already exists 280 if [ -d "ts/$day" ] || [ -d "nix/$day" ] || [ -d "shared/$day" ]; then 281 ${gum}/bin/gum style --foreground 196 "Day $day already exists!" 282 exit 1 283 fi 284 285 # Create directories 286 mkdir -p "ts/$day" 287 mkdir -p "nix/$day" 288 mkdir -p "shared/$day" 289 290 # Fetch input from adventofcode.com 291 ${gum}/bin/gum style --foreground 212 "Fetching input from adventofcode.com..." 292 293 # Check for session cookie 294 session_cookie="" 295 if [ -f ".aoc-session" ]; then 296 session_cookie=$(cat .aoc-session) 297 else 298 ${gum}/bin/gum style --foreground 196 "No .aoc-session file found!" 299 ${gum}/bin/gum style "Create .aoc-session with your session cookie from adventofcode.com" 300 exit 1 301 fi 302 303 # Fetch input 304 day_num=$((10#$day)) 305 ${pkgs.curl}/bin/curl -s -b "session=$session_cookie" \ 306 "https://adventofcode.com/2025/day/$day_num/input" \ 307 -o "shared/$day/input.txt" 308 309 if [ $? -ne 0 ] || [ ! -s "shared/$day/input.txt" ]; then 310 ${gum}/bin/gum style --foreground 196 "Failed to fetch input!" 311 rm -rf "ts/$day" "nix/$day" "shared/$day" 312 exit 1 313 fi 314 315 # Create TypeScript template 316 cat > "ts/$day/index.ts" << EOF 317const file = await Bun.file("../../shared/$day/input.txt").text(); 318 319(() => { 320 // Part 1 321 console.log("part 1:", 0); 322})(); 323 324(() => { 325 // Part 2 326 console.log("part 2:", 0); 327})(); 328EOF 329 330 # Create Nix template 331 cat > "nix/$day/solution.nix" << EOF 332let 333 input = builtins.readFile ../../shared/$day/input.txt; 334 lines = builtins.filter (s: builtins.isString s && s != "") (builtins.split "\n" input); 335 336 part1 = 0; 337 part2 = 0; 338 339in { 340 inherit part1 part2; 341} 342EOF 343 344 ${gum}/bin/gum style --foreground 212 " Day $day initialized!" 345 ${gum}/bin/gum style " ts/$day/index.ts" 346 ${gum}/bin/gum style " nix/$day/solution.nix" 347 ${gum}/bin/gum style " shared/$day/input.txt" 348 ;; 349 350 "Exit") 351 ${gum}/bin/gum style --foreground 212 "Happy coding! 🎅" 352 exit 0 353 ;; 354 esac 355 ''; 356 357 in { 358 # Development shell with all tools 359 devShells.default = pkgs.mkShell { 360 packages = with pkgs; [ 361 bun 362 jq 363 gum 364 runner 365 ]; 366 367 shellHook = '' 368 ${gum}/bin/gum style --foreground 212 "🎄 Advent of Code 2025 🎄" 369 echo "" 370 ${gum}/bin/gum style "Run 'aoc' to start the interactive runner" 371 ''; 372 }; 373 374 # Packages 375 packages = { 376 default = runner; 377 }; 378 379 # Apps 380 apps = { 381 default = { 382 type = "app"; 383 program = "${runner}/bin/aoc"; 384 }; 385 }; 386 } 387 ); 388}