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}