commit 245c66ce3cddd829d0a768ad88278217f86e9dd8 Author: Darius Rapalis Date: Sun Sep 14 14:09:15 2025 +0300 this is all wrong and barely working... no parallel running since... it messed things up diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..1c2fda5 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/boot-dev-typescript-web-scraper.iml b/.idea/boot-dev-typescript-web-scraper.iml new file mode 100644 index 0000000..bf4c9d3 --- /dev/null +++ b/.idea/boot-dev-typescript-web-scraper.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml new file mode 100644 index 0000000..39ab04a --- /dev/null +++ b/.idea/dictionaries/project.xml @@ -0,0 +1,7 @@ + + + + rdarius + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..a0c0c0a --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..000a3ea --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..c8397c9 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5a2280e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2189 @@ +{ + "name": "boot-dev-typescript-web-scraper", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "boot-dev-typescript-web-scraper", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "jsdom": "^27.0.0", + "p-limit": "^7.1.1" + }, + "devDependencies": { + "@types/jsdom": "^21.1.7", + "@types/node": "^24.3.3", + "tsx": "^4.20.5", + "typescript": "^5.9.2", + "vitest": "^3.2.4" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.0.4.tgz", + "integrity": "sha512-cKjSKvWGmAziQWbCouOsFwb14mp1betm8Y7Fn+yglDMUUu3r9DCbJ9iJbeFDenLMqFbIMC0pQP8K+B8LAxX3OQ==", + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-color-parser": "^3.0.10", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "lru-cache": "^11.1.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.5.4.tgz", + "integrity": "sha512-RNSNk1dnB8lAn+xdjlRoM4CzdVrHlmXZtSXAWs2jyl4PiBRWqTZr9ML5M710qgd9RPTBsVG6P0SLy7dwy0Foig==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "license": "MIT" + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz", + "integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.1.tgz", + "integrity": "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.1.tgz", + "integrity": "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.1.tgz", + "integrity": "sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.1.tgz", + "integrity": "sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.1.tgz", + "integrity": "sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.1.tgz", + "integrity": "sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.1.tgz", + "integrity": "sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.1.tgz", + "integrity": "sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.1.tgz", + "integrity": "sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.1.tgz", + "integrity": "sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.50.1.tgz", + "integrity": "sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.1.tgz", + "integrity": "sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.1.tgz", + "integrity": "sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.1.tgz", + "integrity": "sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.1.tgz", + "integrity": "sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.1.tgz", + "integrity": "sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.1.tgz", + "integrity": "sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.1.tgz", + "integrity": "sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.1.tgz", + "integrity": "sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.1.tgz", + "integrity": "sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.1.tgz", + "integrity": "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsdom": { + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/node": { + "version": "24.3.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.3.tgz", + "integrity": "sha512-GKBNHjoNw3Kra1Qg5UXttsY5kiWMEfoHq2TmXb+b1rcm6N7B3wTrFYIf/oSZ1xNQ+hVVijgLkiDZh7jRRsh+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.10.0" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssstyle": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.0.tgz", + "integrity": "sha512-RveJPnk3m7aarYQ2bJ6iw+Urh55S6FzUiqtBq+TihnTDP4cI8y/TYDqGOyqgnG1J1a6BxJXZsV9JFSTulm9Z7g==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.0.3", + "@csstools/css-syntax-patches-for-csstree": "^1.0.14", + "css-tree": "^3.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", + "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.0.tgz", + "integrity": "sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/dom-selector": "^6.5.4", + "cssstyle": "^5.3.0", + "data-urls": "^6.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^7.3.0", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.0.0", + "ws": "^8.18.2", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.1.tgz", + "integrity": "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/p-limit": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.1.1.tgz", + "integrity": "sha512-i8PyM2JnsNChVSYWLr2BAjNoLi0BAYC+wecOnZnVV+YSNJkzP7cWmvI34dk0WArWfH9KwBHNoZI3P3MppImlIA==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.2.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/rollup": { + "version": "4.50.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.1.tgz", + "integrity": "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.50.1", + "@rollup/rollup-android-arm64": "4.50.1", + "@rollup/rollup-darwin-arm64": "4.50.1", + "@rollup/rollup-darwin-x64": "4.50.1", + "@rollup/rollup-freebsd-arm64": "4.50.1", + "@rollup/rollup-freebsd-x64": "4.50.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.50.1", + "@rollup/rollup-linux-arm-musleabihf": "4.50.1", + "@rollup/rollup-linux-arm64-gnu": "4.50.1", + "@rollup/rollup-linux-arm64-musl": "4.50.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.50.1", + "@rollup/rollup-linux-ppc64-gnu": "4.50.1", + "@rollup/rollup-linux-riscv64-gnu": "4.50.1", + "@rollup/rollup-linux-riscv64-musl": "4.50.1", + "@rollup/rollup-linux-s390x-gnu": "4.50.1", + "@rollup/rollup-linux-x64-gnu": "4.50.1", + "@rollup/rollup-linux-x64-musl": "4.50.1", + "@rollup/rollup-openharmony-arm64": "4.50.1", + "@rollup/rollup-win32-arm64-msvc": "4.50.1", + "@rollup/rollup-win32-ia32-msvc": "4.50.1", + "@rollup/rollup-win32-x64-msvc": "4.50.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.14.tgz", + "integrity": "sha512-lMNHE4aSI3LlkMUMicTmAG3tkkitjOQGDTFboPJwAg2kJXKP1ryWEyqujktg5qhrFZOkk5YFzgkxg3jErE+i5w==", + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.14" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.14.tgz", + "integrity": "sha512-viZGNK6+NdluOJWwTO9olaugx0bkKhscIdriQQ+lNNhwitIKvb+SvhbYgnCz6j9p7dX3cJntt4agQAKMXLjJ5g==", + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx": { + "version": "4.20.5", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.5.tgz", + "integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", + "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", + "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.0.0.tgz", + "integrity": "sha512-+0q+Pc6oUhtbbeUfuZd4heMNOLDJDdagYxv756mCf9vnLF+NTj4zvv5UyYNkHJpc3CJIesMVoEIOdhi7L9RObA==", + "license": "MIT", + "dependencies": { + "tr46": "^5.1.1", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/yocto-queue": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c1e69f5 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "boot-dev-typescript-web-scraper", + "version": "1.0.0", + "main": "index.js", + "type": "module", + "scripts": { + "start": "tsx ./src/index.ts", + "test": "vitest run" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/rdarius/boot-dev-typescript-web-scraper.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/rdarius/boot-dev-typescript-web-scraper/issues" + }, + "homepage": "https://github.com/rdarius/boot-dev-typescript-web-scraper#readme", + "description": "", + "devDependencies": { + "@types/jsdom": "^21.1.7", + "@types/node": "^24.3.3", + "tsx": "^4.20.5", + "typescript": "^5.9.2", + "vitest": "^3.2.4" + }, + "dependencies": { + "jsdom": "^27.0.0", + "p-limit": "^7.1.1" + } +} diff --git a/report.csv b/report.csv new file mode 100644 index 0000000..ebc41eb --- /dev/null +++ b/report.csv @@ -0,0 +1,29 @@ +page_url,h1,first_paragraph,outgoing_link_urls,image_urls +https://blog.boot.dev/," + Boot.dev Blog + ","The training grounds are LIVE! 21,000 challenges have been generated between the launch and as I write this, and we’re just getting started. Big things to come. +",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev/news/bootdev-beat-2025-09/;https://blog.boot.dev/create-a-course/;https://blog.boot.dev/news/training-grounds-launch/;https://blog.boot.dev/news/bootdev-beat-2025-08/;https://blog.boot.dev/news/hackathon-2025/;https://blog.boot.dev/news/bootdev-beat-2025-07/;https://blog.boot.dev/news/bootdev-beat-2025-06/;https://blog.boot.dev/education/is-boot-dev-free/;https://blog.boot.dev/news/bootdev-beat-2025-05/;https://blog.boot.dev/news/bootdev-beat-2025-04/;https://blog.boot.dev/news/bootdev-beat-2025-03/;https://blog.boot.dev/news/bootdev-beat-2025-02/;https://blog.boot.dev/computer-science/18-months-with-gpt-4/;https://blog.boot.dev/news/bootdev-beat-2025-01/;https://blog.boot.dev/tutorials/python/lists/;https://blog.boot.dev/tutorials/python/loops/;https://blog.boot.dev/tutorials/python/functions/;https://blog.boot.dev/tutorials/python/variables/;https://blog.boot.dev/news/bootdev-beat-2024-12/;https://blog.boot.dev/news/bootdev-beat-2024-11/;https://blog.boot.dev/education/state-of-learning-to-code-2024/;https://blog.boot.dev/news/bootdev-beat-2024-10/;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/;https://blog.boot.dev/news/bootdev-beat-2024-09/;https://blog.boot.dev/page/2/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/bootssalmonhoney.webp.webp;https://blog.boot.dev/img/800/bootssalmonhoney.webp.webp;https://blog.boot.dev/img/800/handwriting.png.webp;https://blog.boot.dev/img/800/handwriting.png.webp;https://blog.boot.dev/img/800/traininggroundsscreenshot.png.webp;https://blog.boot.dev/img/800/traininggroundsscreenshot.png.webp;https://blog.boot.dev/img/800/bootsaugust.png.webp;https://blog.boot.dev/img/800/bootsaugust.png.webp;https://blog.boot.dev/img/800/bootsbackend.png.webp;https://blog.boot.dev/img/800/bootsbackend.png.webp;https://blog.boot.dev/img/800/bootsstandinggold.png.webp;https://blog.boot.dev/img/800/bootsstandinggold.png.webp;https://blog.boot.dev/img/800/412509565-af264412-8653-4409-94b1-1da5a3251503.png.webp;https://blog.boot.dev/img/800/412509565-af264412-8653-4409-94b1-1da5a3251503.png.webp;https://blog.boot.dev/img/800/ruinreborn_fantasy_art_purse_cut_cut_purse_cut_purse_spilling_7863ce44-bff3-474e-abd8-3dd6579172d0_1.png.webp;https://blog.boot.dev/img/800/ruinreborn_fantasy_art_purse_cut_cut_purse_cut_purse_spilling_7863ce44-bff3-474e-abd8-3dd6579172d0_1.png.webp;https://blog.boot.dev/img/800/bootssketches.png.webp;https://blog.boot.dev/img/800/bootssketches.png.webp;https://blog.boot.dev/img/800/bootscrystal.png.webp;https://blog.boot.dev/img/800/bootscrystal.png.webp;https://blog.boot.dev/img/800/bootsmarch.png.webp;https://blog.boot.dev/img/800/bootsmarch.png.webp;https://blog.boot.dev/img/800/bootsreadingabook.png.webp;https://blog.boot.dev/img/800/bootsreadingabook.png.webp;https://blog.boot.dev/img/800/golemnumber2.png.webp;https://blog.boot.dev/img/800/golemnumber2.png.webp;https://blog.boot.dev/img/800/bootsandsky.png.webp;https://blog.boot.dev/img/800/bootsandsky.png.webp;https://blog.boot.dev/img/800/wagslane_snakes_all_slithering_in_a_line__studio_ghibli_anime_92c11046-1c25-44da-a9df-550614ab5404_0.png.webp;https://blog.boot.dev/img/800/wagslane_snakes_all_slithering_in_a_line__studio_ghibli_anime_92c11046-1c25-44da-a9df-550614ab5404_0.png.webp;https://blog.boot.dev/img/800/loopsinthesky.png.webp;https://blog.boot.dev/img/800/loopsinthesky.png.webp;https://blog.boot.dev/img/800/functionsinpythonscreen.png.webp;https://blog.boot.dev/img/800/functionsinpythonscreen.png.webp;https://blog.boot.dev/img/800/symbolsonboard.png.webp;https://blog.boot.dev/img/800/symbolsonboard.png.webp;https://blog.boot.dev/img/800/bootsdecember2024.png.webp;https://blog.boot.dev/img/800/bootsdecember2024.png.webp;https://blog.boot.dev/img/800/bootsthanksgiving.png.webp;https://blog.boot.dev/img/800/bootsthanksgiving.png.webp;https://blog.boot.dev/img/800/bootsatdesk.png.webp;https://blog.boot.dev/img/800/bootsatdesk.png.webp;https://blog.boot.dev/img/800/bootsoctober.png.webp;https://blog.boot.dev/img/800/bootsoctober.png.webp;https://blog.boot.dev/img/800/webservers.png.webp;https://blog.boot.dev/img/800/webservers.png.webp;https://blog.boot.dev/img/800/blog_aggregator_art.png.webp;https://blog.boot.dev/img/800/blog_aggregator_art.png.webp;https://blog.boot.dev/img/800/bootsseptember2024.png.webp;https://blog.boot.dev/img/800/bootsseptember2024.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/news/bootdev-beat-2025-09/,The Boot.dev Beat. September 2025,"The training grounds are LIVE! 21,000 challenges have been generated between the launch and as I write this, and we’re just getting started. Big things to come.",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/news/;https://www.boot.dev/teachers/lane-wagner/;https://blog.boot.dev/news/bootdev-beat-2025-09/#patch-notes;https://blog.boot.dev/news/bootdev-beat-2025-09/#1-training-grounds-is-live;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://www.boot.dev/training;https://en.wikipedia.org/wiki/Spaced_repetition;https://blog.boot.dev/news/bootdev-beat-2025-09/#2-boots-interview-lessons;https://blog.boot.dev/news/bootdev-beat-2025-09/#3-http-protocol-course-walkthrough;https://www.boot.dev/courses/learn-http-protocol-golang;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://blog.boot.dev/news/bootdev-beat-2025-09/#4-ai-agent-course-walkthrough;https://www.boot.dev/courses/build-ai-agent-python;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://blog.boot.dev/news/bootdev-beat-2025-09/#5-big-web-scraper-course-updates;https://www.boot.dev/courses/build-web-scraper-python;https://www.boot.dev/courses/build-web-scraper-typescript;https://www.boot.dev/courses/build-web-scraper-golang;https://blog.boot.dev/news/bootdev-beat-2025-09/#6-miscellaneous-improvements;https://blog.boot.dev/news/bootdev-beat-2025-09/#what-is-yet-to-come;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/news/training-grounds-launch/;https://blog.boot.dev/news/bootdev-beat-2025-08/;https://blog.boot.dev/news/hackathon-2025/;https://blog.boot.dev/news/bootdev-beat-2025-07/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/bootssalmonhoney.webp.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/traininggroundsscreenshot.png.webp;https://blog.boot.dev/img/800/traininggroundsscreenshot.png.webp;https://blog.boot.dev/img/800/bootsaugust.png.webp;https://blog.boot.dev/img/800/bootsaugust.png.webp;https://blog.boot.dev/img/800/bootsbackend.png.webp;https://blog.boot.dev/img/800/bootsbackend.png.webp;https://blog.boot.dev/img/800/bootsstandinggold.png.webp;https://blog.boot.dev/img/800/bootsstandinggold.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/create-a-course/,Create a Course on Boot.dev,"We create most of our courses at Boot.dev in-house, but we also love to collaborate with talented authors! If you’re interested in creating a course for Boot.dev, here’s some preliminary info about how we work:",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://www.boot.dev/teachers/lane-wagner/;https://github.com/bootdotdev/curriculum;https://www.boot.dev/courses;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/handwriting.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/news/training-grounds-launch/,Boot.dev Launches the Training Grounds,We’ve always said Boot.dev is fun… but we’ve never said it’s easy.,https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/news/;https://www.boot.dev/teachers/lane-wagner/;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://www.boot.dev/training;https://en.wikipedia.org/wiki/Spaced_repetition;https://www.boot.dev/training;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/news/bootdev-beat-2025-08/;https://blog.boot.dev/news/hackathon-2025/;https://blog.boot.dev/news/bootdev-beat-2025-07/;https://blog.boot.dev/news/bootdev-beat-2025-06/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/traininggroundsscreenshot.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/userreportsscreenshot.png.webp;https://blog.boot.dev/img/800/bootsaugust.png.webp;https://blog.boot.dev/img/800/bootsaugust.png.webp;https://blog.boot.dev/img/800/bootsbackend.png.webp;https://blog.boot.dev/img/800/bootsbackend.png.webp;https://blog.boot.dev/img/800/bootsstandinggold.png.webp;https://blog.boot.dev/img/800/bootsstandinggold.png.webp;https://blog.boot.dev/img/800/412509565-af264412-8653-4409-94b1-1da5a3251503.png.webp;https://blog.boot.dev/img/800/412509565-af264412-8653-4409-94b1-1da5a3251503.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/news/bootdev-beat-2025-08/,The Boot.dev Beat. August 2025,"We ran the biggest hackathon I’ve ever been a part of (500+ participants!) and saw some incredible projects come out of it. Thanks to everyone that participated, I’m excited for the next one!",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/news/;https://www.boot.dev/teachers/lane-wagner/;https://blog.boot.dev/news/bootdev-beat-2025-08/#patch-notes;https://blog.boot.dev/news/bootdev-beat-2025-08/#1-2025-hackathon-winners;https://blog.boot.dev/news/hackathon-2025;https://github.com/uncomfyhalomacro/bunny;https://github.com/mierdev/codemon/;https://github.com/haanhtuandev/pokemon-code-battle;https://github.com/The-Memory-Managers/cpu-vs-ai;https://github.com/noazlee/code_off;https://github.com/Eqedos/BootDevMulti;https://blog.boot.dev/news/bootdev-beat-2025-08/#2-listen-totalk-to-boots;https://blog.boot.dev/news/bootdev-beat-2025-08/#3-updated-migrations-chapter-in-sql-course;https://www.boot.dev/courses/learn-sql;https://blog.boot.dev/news/bootdev-beat-2025-08/#4-new-chest-icons;https://blog.boot.dev/news/bootdev-beat-2025-08/#5-miscellaneous-improvements;https://blog.boot.dev/news/bootdev-beat-2025-08/#what-is-yet-to-come;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/news/hackathon-2025/;https://blog.boot.dev/news/bootdev-beat-2025-07/;https://blog.boot.dev/news/bootdev-beat-2025-06/;https://blog.boot.dev/news/bootdev-beat-2025-05/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/bootsaugust.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/talktoboots.png.webp;https://blog.boot.dev/img/800/listentoboots.png.webp;https://blog.boot.dev/img/800/chestquests.png.webp;https://blog.boot.dev/img/800/bootsbackend.png.webp;https://blog.boot.dev/img/800/bootsbackend.png.webp;https://blog.boot.dev/img/800/bootsstandinggold.png.webp;https://blog.boot.dev/img/800/bootsstandinggold.png.webp;https://blog.boot.dev/img/800/412509565-af264412-8653-4409-94b1-1da5a3251503.png.webp;https://blog.boot.dev/img/800/412509565-af264412-8653-4409-94b1-1da5a3251503.png.webp;https://blog.boot.dev/img/800/bootssketches.png.webp;https://blog.boot.dev/img/800/bootssketches.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/news/hackathon-2025/,2025 Hackathon,"The 2025 Boot.dev Hackathon has been completed! We had an amazing turnout this year, with over 500 participants across two categories: Amateur and Pro. The projects were incredible, and the competition was fierce.",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/news/;https://www.boot.dev/teachers/lane-wagner/;https://blog.boot.dev/news/hackathon-2025/#2025-winners;https://blog.boot.dev/news/hackathon-2025/#amateur-category;https://github.com/uncomfyhalomacro/bunny;https://github.com/mierdev/codemon/;https://github.com/haanhtuandev/pokemon-code-battle;https://github.com/Skallywaggs806/DMs_REIGN;https://github.com/fadelbantan/quickmind;https://github.com/FT1006/devesis;https://github.com/Vedjw/DonkeyType;https://github.com/AbdurDRahman/AlgoVisualizer;https://github.com/kawilkinson/gocade;https://github.com/riessss/BootBeats;https://blog.boot.dev/news/hackathon-2025/#pro-category;https://github.com/The-Memory-Managers/cpu-vs-ai;https://github.com/noazlee/code_off;https://github.com/Eqedos/BootDevMulti;https://github.com/patrickjoan/LagLens;https://github.com/sixsat/vtuber-js;https://github.com/ulises-gomez-dev/vim-wizards;https://github.com/Alpha2749/FluidSim;https://github.com/Pranay0205/coachlint;https://github.com/nmlakra/sc-inspect;https://github.com/sslaybac/your-way-TD;https://blog.boot.dev/news/hackathon-2025/#12-honorable-mentions-no-prizes-sorry;https://github.com/Irakli00/murder_she_code;https://github.com/Asfolny/geaves;https://github.com/yurikdotdev/covfefescript;https://github.com/rachyrachyrach/Bark-at-the-moon-paranormal-research;https://github.com/FeliciaFredlund/trackinstall;https://github.com/NBGtega/shoelace-cipher;https://github.com/A-thanasios/ScreamingPussies;https://github.com/ManoloEsS/cli-password-manager;https://github.com/Gosewinckel/Stellar_Gravity_Simulation;https://github.com/Cacutss/JamFrog;https://github.com/BlanahrAbarstyrn/EscapeTheCave;https://github.com/maniac-en/req;https://blog.boot.dev/news/hackathon-2025/#thank-you-for-participating;https://www.boot.dev/courses/build-personal-project-1;https://www.boot.dev/courses/build-capstone-project;https://blog.boot.dev/news/hackathon-2025/#archived-instructions-for-the-2025-hackathon;https://blog.boot.dev/news/hackathon-2025/#timeline;https://blog.boot.dev/news/hackathon-2025/#submission-instructions;https://blog.boot.dev/news/hackathon-2025/#voting-instructions;https://blog.boot.dev/news/hackathon-2025/#judging-criteria;https://blog.boot.dev/news/hackathon-2025/#prizes;https://blog.boot.dev/news/hackathon-2025/#misc-rules;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/news/bootdev-beat-2025-07/;https://blog.boot.dev/news/bootdev-beat-2025-06/;https://blog.boot.dev/news/bootdev-beat-2025-05/;https://blog.boot.dev/news/bootdev-beat-2025-04/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/bootsbackend.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/bootsstandinggold.png.webp;https://blog.boot.dev/img/800/bootsstandinggold.png.webp;https://blog.boot.dev/img/800/412509565-af264412-8653-4409-94b1-1da5a3251503.png.webp;https://blog.boot.dev/img/800/412509565-af264412-8653-4409-94b1-1da5a3251503.png.webp;https://blog.boot.dev/img/800/bootssketches.png.webp;https://blog.boot.dev/img/800/bootssketches.png.webp;https://blog.boot.dev/img/800/bootscrystal.png.webp;https://blog.boot.dev/img/800/bootscrystal.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/news/bootdev-beat-2025-07/,The Boot.dev Beat. July 2025,This month we were heads down building a lot of background stuff for big releases that are coming up - but we still did manage to get a few new features out the door!,https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/news/;https://www.boot.dev/teachers/lane-wagner/;https://blog.boot.dev/news/bootdev-beat-2025-07/#patch-notes;https://blog.boot.dev/news/bootdev-beat-2025-07/#1-javascript-course-walkthrough;https://www.boot.dev/courses/learn-javascript;https://www.youtube.com/watch?v=OOOfBC1grl0;https://blog.boot.dev/news/bootdev-beat-2025-07/#2-achivement-modal-rework;https://blog.boot.dev/news/bootdev-beat-2025-07/#3-leagues-and-leaderboard-update;https://www.boot.dev/leaderboard;https://blog.boot.dev/news/bootdev-beat-2025-07/#4-miscellaneous-improvements;https://blog.boot.dev/news/bootdev-beat-2025-07/#what-is-yet-to-come;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/news/bootdev-beat-2025-06/;https://blog.boot.dev/news/bootdev-beat-2025-05/;https://blog.boot.dev/news/bootdev-beat-2025-04/;https://blog.boot.dev/news/bootdev-beat-2025-03/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/bootsstandinggold.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/achivementsimage.webp;https://blog.boot.dev/img/800/412509565-af264412-8653-4409-94b1-1da5a3251503.png.webp;https://blog.boot.dev/img/800/412509565-af264412-8653-4409-94b1-1da5a3251503.png.webp;https://blog.boot.dev/img/800/bootssketches.png.webp;https://blog.boot.dev/img/800/bootssketches.png.webp;https://blog.boot.dev/img/800/bootscrystal.png.webp;https://blog.boot.dev/img/800/bootscrystal.png.webp;https://blog.boot.dev/img/800/bootsmarch.png.webp;https://blog.boot.dev/img/800/bootsmarch.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/news/bootdev-beat-2025-06/,The Boot.dev Beat. June 2025,We’ve capitulated into the vibe coding era? Well… not really. But it’s cool to understand how AI agents like Cursor and Claude Code work under the hood.,https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/news/;https://www.boot.dev/teachers/lane-wagner/;https://blog.boot.dev/news/bootdev-beat-2025-06/#patch-notes;https://www.boot.dev/courses/build-ai-agent-python;https://blog.boot.dev/news/bootdev-beat-2025-06/#1-new-build-an-ai-agent-course-is-live;https://www.boot.dev/courses/build-static-site-generator-python;https://www.boot.dev/courses/build-maze-solver-python;https://blog.boot.dev/news/bootdev-beat-2025-06/#2-massive-speed-improvements;https://blog.boot.dev/news/bootdev-beat-2025-06/#3-boots-improvements;https://blog.boot.dev/news/bootdev-beat-2025-06/#4-docker-course-update;https://www.boot.dev/courses/learn-docker;https://blog.boot.dev/news/bootdev-beat-2025-06/#5-miscellaneous-improvements;https://blog.boot.dev/news/bootdev-beat-2025-06/#what-is-yet-to-come;https://blog.boot.dev/news/bootdev-beat-2025-06/#new-youtube-videos;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://www.youtube.com/shorts/tBns-Uc6D3I;https://www.youtube.com/shorts/drLXYsuurCw;https://www.youtube.com/shorts/NIZP4q8EvwI;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/news/bootdev-beat-2025-05/;https://blog.boot.dev/news/bootdev-beat-2025-04/;https://blog.boot.dev/news/bootdev-beat-2025-03/;https://blog.boot.dev/news/bootdev-beat-2025-02/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/412509565-af264412-8653-4409-94b1-1da5a3251503.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/bootssketches.png.webp;https://blog.boot.dev/img/800/bootssketches.png.webp;https://blog.boot.dev/img/800/bootscrystal.png.webp;https://blog.boot.dev/img/800/bootscrystal.png.webp;https://blog.boot.dev/img/800/bootsmarch.png.webp;https://blog.boot.dev/img/800/bootsmarch.png.webp;https://blog.boot.dev/img/800/bootsreadingabook.png.webp;https://blog.boot.dev/img/800/bootsreadingabook.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/education/is-boot-dev-free/,Are Boot.dev Courses Free?,"Sometimes folks get confused and wonder, are Boot.dev’s programming courses really free?",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/education/;https://www.boot.dev/teachers/lane-wagner/;https://www.boot.dev;https://blog.boot.dev/education/is-boot-dev-free/#so-why-pay;https://www.boot.dev/pricing;https://www.boot.dev/pricing;https://blog.boot.dev/education/is-boot-dev-free/#were-one-of-the-only-learning-platforms-like-this;https://blog.boot.dev/education/is-boot-dev-free/#why-subscriptions;https://blog.boot.dev/education/is-boot-dev-free/#what-if-my-company-wants-to-pay;https://www.boot.dev/teams;https://www.boot.dev/expense;https://blog.boot.dev/education/is-boot-dev-free/#can-i-give-a-membership-as-a-gift;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/education/state-of-learning-to-code-2024/;https://blog.boot.dev/education/is-there-a-course-on/;https://blog.boot.dev/education/maybe-you-do-need-kubernetes/;https://blog.boot.dev/education/learn-to-code-the-slow-way/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/ruinreborn_fantasy_art_purse_cut_cut_purse_cut_purse_spilling_7863ce44-bff3-474e-abd8-3dd6579172d0_1.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/bootsatdesk.png.webp;https://blog.boot.dev/img/800/bootsatdesk.png.webp;https://blog.boot.dev/img/800/pileofbooks.png.webp;https://blog.boot.dev/img/800/pileofbooks.png.webp;https://blog.boot.dev/img/800/buildingarobot.png.webp;https://blog.boot.dev/img/800/buildingarobot.png.webp;https://blog.boot.dev/img/800/shortcutwizard.png.webp;https://blog.boot.dev/img/800/shortcutwizard.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/news/bootdev-beat-2025-05/,The Boot.dev Beat. May 2025,"Our biggest content release ever just dropped: the complete backend learning path, but now in Python and TypeScript! This is a massive update that we’ve been working on for a year, I hope you enjoy it!",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/news/;https://www.boot.dev/teachers/lane-wagner/;https://blog.boot.dev/news/bootdev-beat-2025-05/#patch-notes;https://www.boot.dev/tracks/backend-python-typescript;https://blog.boot.dev/news/bootdev-beat-2025-05/#1-new-pythontypescript-backend-path-is-live;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/courses/learn-http-clients-typescript;https://www.boot.dev/courses/learn-http-servers-typescript;https://www.boot.dev/courses/learn-file-servers-s3-cloudfront-typescript;https://www.boot.dev/courses/learn-javascript;https://www.boot.dev/courses/learn-typescript;https://blog.boot.dev/news/bootdev-beat-2025-05/#2-new-dashboard-view;https://blog.boot.dev/news/bootdev-beat-2025-05/#3-leagues;https://www.boot.dev/leaderboard;https://blog.boot.dev/news/bootdev-beat-2025-05/#4-spellbook-ui-update;https://blog.boot.dev/news/bootdev-beat-2025-05/#5-new-course-catalogue-page;https://www.boot.dev/courses;https://blog.boot.dev/news/bootdev-beat-2025-05/#6-miscellaneous-improvements;https://blog.boot.dev/news/bootdev-beat-2025-05/#what-is-yet-to-come;https://blog.boot.dev/news/bootdev-beat-2025-05/#new-youtube-videos;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://www.youtube.com/watch?v=DMeD8trbj6A;https://www.youtube.com/watch?v=WNrHrwm1wkU;https://www.youtube.com/watch?v=GMV7ycKZ-mM;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/news/bootdev-beat-2025-04/;https://blog.boot.dev/news/bootdev-beat-2025-03/;https://blog.boot.dev/news/bootdev-beat-2025-02/;https://blog.boot.dev/news/bootdev-beat-2025-01/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/bootssketches.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/typescriptlearningpathlive.jpg.webp;https://blog.boot.dev/img/800/dashboardscreenshot.png.webp;https://blog.boot.dev/img/800/spellbookuiupdate.png.webp;https://blog.boot.dev/img/800/bootscrystal.png.webp;https://blog.boot.dev/img/800/bootscrystal.png.webp;https://blog.boot.dev/img/800/bootsmarch.png.webp;https://blog.boot.dev/img/800/bootsmarch.png.webp;https://blog.boot.dev/img/800/bootsreadingabook.png.webp;https://blog.boot.dev/img/800/bootsreadingabook.png.webp;https://blog.boot.dev/img/800/bootsandsky.png.webp;https://blog.boot.dev/img/800/bootsandsky.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/news/bootdev-beat-2025-04/,The Boot.dev Beat. April 2025,"The Lord of the Mire has been the hardest boss yet (by far), weighing it with 150,000,000 HP. The community rallied during the event, dealing a whopping 132,282,452 damage in total, but not quite enough to bring him down.",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/news/;https://www.boot.dev/teachers/lane-wagner/;https://www.boot.dev/lore/the-lord-of-the-mire;https://blog.boot.dev/news/bootdev-beat-2025-04/#patch-notes;https://www.boot.dev/courses/learn-http-protocol-golang;https://blog.boot.dev/news/bootdev-beat-2025-04/#1-new-http-protocol-course-build-your-own-is-live;https://www.boot.dev/teachers/the-primeagen;https://www.boot.dev/community;https://blog.boot.dev/news/bootdev-beat-2025-04/#2-new-got-a-job-forum-in-the-discord;https://blog.boot.dev/news/bootdev-beat-2025-04/#3-new-boss-end-screen-ui;https://blog.boot.dev/news/bootdev-beat-2025-04/#4-miscellaneous-improvements;https://blog.boot.dev/news/bootdev-beat-2025-04/#what-is-yet-to-come;https://blog.boot.dev/news/bootdev-beat-2025-04/#new-youtube-videos;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://www.backendbanter.fm;https://www.youtube.com/watch?v=cqxjAJ9Fzeg;https://www.youtube.com/watch?v=586_BAMMOQ8;https://www.youtube.com/watch?v=jNqpav8TqAk;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/news/bootdev-beat-2025-03/;https://blog.boot.dev/news/bootdev-beat-2025-02/;https://blog.boot.dev/news/bootdev-beat-2025-01/;https://blog.boot.dev/news/bootdev-beat-2024-12/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/bootscrystal.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/primehttpcourse.jpeg.webp;https://blog.boot.dev/img/800/bootsmarch.png.webp;https://blog.boot.dev/img/800/bootsmarch.png.webp;https://blog.boot.dev/img/800/bootsreadingabook.png.webp;https://blog.boot.dev/img/800/bootsreadingabook.png.webp;https://blog.boot.dev/img/800/bootsandsky.png.webp;https://blog.boot.dev/img/800/bootsandsky.png.webp;https://blog.boot.dev/img/800/bootsdecember2024.png.webp;https://blog.boot.dev/img/800/bootsdecember2024.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/news/bootdev-beat-2025-03/,The Boot.dev Beat. March 2025,"Our new JavaScript course is live! ThePrimeagen’s course on building your own HTTP server from scratch is just around the corner, and the TypeScript track couldn’t be closer.",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/news/;https://www.boot.dev/teachers/lane-wagner/;https://blog.boot.dev/news/bootdev-beat-2025-03/#patch-notes;https://www.boot.dev/courses/learn-javascript;https://blog.boot.dev/news/bootdev-beat-2025-03/#1-new-javascript-course;https://blog.boot.dev/news/bootdev-beat-2025-03/#2-mobile-experience-that-doesnt-suck;https://blog.boot.dev/news/bootdev-beat-2025-03/#3-boots-is-smarter;https://www.anthropic.com/news/claude-3-7-sonnet;https://blog.boot.dev/news/bootdev-beat-2025-03/#4-we-acquired-icodethis-and-are-making-it-free;https://icodethis.com;https://blog.boot.dev/news/bootdev-beat-2025-03/#5-miscellaneous-improvements;https://www.boot.dev/courses/learn-sql;https://blog.boot.dev/news/bootdev-beat-2025-03/#what-is-yet-to-come;https://blog.boot.dev/news/bootdev-beat-2025-03/#new-youtube-videos;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://www.backendbanter.fm;https://www.youtube.com/watch?v=5EpxZBtXT74;https://www.youtube.com/watch?v=kKW7a1oPIFY;https://www.youtube.com/watch?v=1-w9HDOVIzQ;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/news/bootdev-beat-2025-02/;https://blog.boot.dev/news/bootdev-beat-2025-01/;https://blog.boot.dev/news/bootdev-beat-2024-12/;https://blog.boot.dev/news/bootdev-beat-2024-11/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/bootsmarch.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/jscourse.jpeg.webp;https://blog.boot.dev/img/800/bootsreadingabook.png.webp;https://blog.boot.dev/img/800/bootsreadingabook.png.webp;https://blog.boot.dev/img/800/bootsandsky.png.webp;https://blog.boot.dev/img/800/bootsandsky.png.webp;https://blog.boot.dev/img/800/bootsdecember2024.png.webp;https://blog.boot.dev/img/800/bootsdecember2024.png.webp;https://blog.boot.dev/img/800/bootsthanksgiving.png.webp;https://blog.boot.dev/img/800/bootsthanksgiving.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/news/bootdev-beat-2025-02/,The Boot.dev Beat. February 2025,"It’s official: TJ’s C course walkthrough was released before GTA6! This is a great month for Boot.dev, and for humanity as a whole.",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/news/;https://www.boot.dev/teachers/lane-wagner/;https://blog.boot.dev/news/bootdev-beat-2025-02/#patch-notes;https://blog.boot.dev/news/bootdev-beat-2025-02/#1-new-c-course-walkthrough;https://www.boot.dev/courses/learn-memory-management-c;https://www.youtube.com/watch?v=rJrd2QMVbGM;https://blog.boot.dev/news/bootdev-beat-2025-02/#2-difficulty-score-balance;https://blog.boot.dev/news/bootdev-beat-2025-02/#3-algos-and-data-structures-courses-merged;https://www.boot.dev/courses/learn-data-structures-and-algorithms-python;https://blog.boot.dev/news/bootdev-beat-2025-02/#4-miscellaneous-improvements;https://blog.boot.dev/news/bootdev-beat-2025-02/#what-is-yet-to-come;https://blog.boot.dev/news/bootdev-beat-2025-02/#new-youtube-videos;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://www.backendbanter.fm;https://www.youtube.com/watch?v=9ynX4znKFDA;https://www.youtube.com/watch?v=Cc93qz4wPw4;https://www.youtube.com/watch?v=UeJPyuVxL-o;https://www.youtube.com/watch?v=yk9x3nX5MQo;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/news/bootdev-beat-2025-01/;https://blog.boot.dev/news/bootdev-beat-2024-12/;https://blog.boot.dev/news/bootdev-beat-2024-11/;https://blog.boot.dev/news/bootdev-beat-2024-10/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/bootsreadingabook.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/tjhoriztonalccourse.jpg.webp;https://blog.boot.dev/img/800/dsacourseimage.jpeg.webp;https://blog.boot.dev/img/800/bootsandsky.png.webp;https://blog.boot.dev/img/800/bootsandsky.png.webp;https://blog.boot.dev/img/800/bootsdecember2024.png.webp;https://blog.boot.dev/img/800/bootsdecember2024.png.webp;https://blog.boot.dev/img/800/bootsthanksgiving.png.webp;https://blog.boot.dev/img/800/bootsthanksgiving.png.webp;https://blog.boot.dev/img/800/bootsoctober.png.webp;https://blog.boot.dev/img/800/bootsoctober.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/computer-science/18-months-with-gpt-4/,18 Months with GPT-4: Now Can I Fire my Developers?,"As the founder of a company where my largest static expense is engineering salaries, I’m over here just chomping at the bit, eagerly awaiting the moment I can fire everyone and line my pockets with all those juicy savings. See, about one year ago, I wrote an article titled, “9 Months with GPT-4: Can I Fire my Developers Yet?” and I thought I’d give you a little update on Allan’s employment status.",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/computer-science/;https://www.boot.dev/teachers/lane-wagner/;https://blog.boot.dev/computer-science/ai-taking-programming-jobs;https://www.boot.dev/pricing;https://blog.boot.dev/computer-science/18-months-with-gpt-4/#whats-changed-the-most;https://zed.dev;https://blog.boot.dev/computer-science/18-months-with-gpt-4/#reformatting;https://blog.boot.dev/computer-science/18-months-with-gpt-4/#what-about-code-generation;https://blog.boot.dev/computer-science/18-months-with-gpt-4/#anything-else-llms-have-been-good-for;https://blog.boot.dev/computer-science/18-months-with-gpt-4/#1-generating-summaries;https://blog.boot.dev/computer-science/18-months-with-gpt-4/#2-customer-support;https://blog.boot.dev/computer-science/18-months-with-gpt-4/#3-translating-programming-languages;https://blog.boot.dev/computer-science/18-months-with-gpt-4/#wait-what-about-new-reasoning-models;https://api-docs.deepseek.com/quick_start/pricing;https://beta.openai.com/pricing/;https://blog.boot.dev/computer-science/18-months-with-gpt-4/#predictions-for-the-next-year;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/computer-science/ai-taking-programming-jobs/;https://blog.boot.dev/python/queue-data-structure-python/;https://blog.boot.dev/python/stack-data-structure-python/;https://blog.boot.dev/computer-science/is-computer-science-hard/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/golemnumber2.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://media.tenor.com/kSmVJHqqPtgAAAAM/ron-swanson-no.gif;https://blog.boot.dev/img/800/golemai.png.webp;https://blog.boot.dev/img/800/golemai.png.webp;https://blog.boot.dev/img/800/queue_of_swords.png.webp;https://blog.boot.dev/img/800/queue_of_swords.png.webp;https://blog.boot.dev/img/800/stackofrocksfantasygame.png.webp;https://blog.boot.dev/img/800/stackofrocksfantasygame.png.webp;https://blog.boot.dev/img/800/android-studying-cs.webp;https://blog.boot.dev/img/800/android-studying-cs.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/news/bootdev-beat-2025-01/,The Boot.dev Beat. January 2025,"Fastest boss defeat in the history of Boot.dev, a new AWS course, embers are live, and my children just won’t stop being sick for more than 4 days at a time. Win some, lose some.",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/news/;https://www.boot.dev/teachers/lane-wagner/;https://blog.boot.dev/news/bootdev-beat-2025-01/#patch-notes;https://blog.boot.dev/news/bootdev-beat-2025-01/#1-new-aws-file-storage-course;https://www.boot.dev/courses/learn-file-servers-s3-cloudfront-golang;https://www.boot.dev/courses/learn-http-servers-golang;https://blog.boot.dev/news/bootdev-beat-2025-01/#2-streak-embers-are-live;https://blog.boot.dev/news/bootdev-beat-2025-01/#3-chapter-5-lore-released;https://www.boot.dev/lore/the-guardian;https://blog.boot.dev/news/bootdev-beat-2025-01/#4-more-ui-updates;https://blog.boot.dev/news/bootdev-beat-2025-01/#5-miscellaneous-improvements;https://blog.boot.dev/news/bootdev-beat-2025-01/#what-is-yet-to-come;https://blog.boot.dev/news/bootdev-beat-2025-01/#new-youtube-videos;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://www.youtube.com/watch?v=aybSXT9ZJ8w;https://www.youtube.com/watch?v=ygJJ5Gg7txA;https://www.youtube.com/watch?v=WEVWAF50Mc4;https://www.youtube.com/watch?v=ImSmxjF-4Ms;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/news/bootdev-beat-2024-12/;https://blog.boot.dev/news/bootdev-beat-2024-11/;https://blog.boot.dev/news/bootdev-beat-2024-10/;https://blog.boot.dev/news/bootdev-beat-2024-09/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/bootsandsky.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/bossfightstarts.png.webp;https://blog.boot.dev/img/800/s3courseimage.jpeg.webp;https://blog.boot.dev/img/800/emberslit.png.webp;https://blog.boot.dev/img/800/guardianimage.png.CRYSsxuu.webp.webp;https://blog.boot.dev/img/800/bootsdecember2024.png.webp;https://blog.boot.dev/img/800/bootsdecember2024.png.webp;https://blog.boot.dev/img/800/bootsthanksgiving.png.webp;https://blog.boot.dev/img/800/bootsthanksgiving.png.webp;https://blog.boot.dev/img/800/bootsoctober.png.webp;https://blog.boot.dev/img/800/bootsoctober.png.webp;https://blog.boot.dev/img/800/bootsseptember2024.png.webp;https://blog.boot.dev/img/800/bootsseptember2024.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/tutorials/python/lists/,Lists in Python,"A natural way to organize and store data is in a List. Some languages call them “arrays”, but in Python, we just call them lists. Think of all the apps you use and how many of the items in the app are organized into lists.",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/tutorials/;https://www.boot.dev/teachers/lane-wagner/;https://blog.boot.dev/tutorials/python/lists/#lists;https://www.boot.dev/courses/learn-code-python;https://blog.boot.dev/tutorials/python/lists/#counting-in-programming;https://blog.boot.dev/tutorials/python/lists/#indexes;https://blog.boot.dev/tutorials/python/lists/#indexing-into-lists;https://blog.boot.dev/tutorials/python/lists/#list-length;https://blog.boot.dev/tutorials/python/lists/#list-updates;https://blog.boot.dev/tutorials/python/lists/#appending-in-python;https://blog.boot.dev/tutorials/python/lists/#pop-values;https://blog.boot.dev/tutorials/python/lists/#counting-the-items-in-a-list;https://blog.boot.dev/tutorials/python/loops;https://blog.boot.dev/tutorials/python/lists/#no-index-syntax;https://blog.boot.dev/tutorials/python/lists/#find-an-item-in-a-list;https://blog.boot.dev/tutorials/python/lists/#slicing-lists;https://blog.boot.dev/tutorials/python/lists/#omitting-sections;https://blog.boot.dev/tutorials/python/lists/#using-only-the-step-section;https://blog.boot.dev/tutorials/python/lists/#negative-indices;https://blog.boot.dev/tutorials/python/lists/#list-operations---concatenate;https://blog.boot.dev/tutorials/python/lists/#list-operations---contains;https://blog.boot.dev/tutorials/python/lists/#list-deletion;https://docs.python.org/3/tutorial/datastructures.html#the-del-statement;https://blog.boot.dev/tutorials/python/lists/#first-element;https://blog.boot.dev/tutorials/python/lists/#example-reverse-a-list;https://blog.boot.dev/tutorials/python/lists/#xample-filter-messages;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/tutorials/python/loops/;https://blog.boot.dev/tutorials/python/functions/;https://blog.boot.dev/tutorials/python/variables/;https://blog.boot.dev/python/queue-data-structure-python/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/wagslane_snakes_all_slithering_in_a_line__studio_ghibli_anime_92c11046-1c25-44da-a9df-550614ab5404_0.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/loopsinthesky.png.webp;https://blog.boot.dev/img/800/loopsinthesky.png.webp;https://blog.boot.dev/img/800/functionsinpythonscreen.png.webp;https://blog.boot.dev/img/800/functionsinpythonscreen.png.webp;https://blog.boot.dev/img/800/symbolsonboard.png.webp;https://blog.boot.dev/img/800/symbolsonboard.png.webp;https://blog.boot.dev/img/800/queue_of_swords.png.webp;https://blog.boot.dev/img/800/queue_of_swords.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/tutorials/python/loops/,Loops in Python,"If you’re new to Python, or perhaps coding in general (welcome!), loops are what allow us to do the same thing over and over and over again, without having to re-type the same code each time. For example, let’s pretend I want to print the numbers 0-9.",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/tutorials/;https://www.boot.dev/teachers/lane-wagner/;https://www.boot.dev/courses/learn-code-python;https://blog.boot.dev/tutorials/python/loops/#range-continued;https://blog.boot.dev/tutorials/python/functions;https://blog.boot.dev/tutorials/python/loops/#iterating-over-lists;https://blog.boot.dev/tutorials/python/lists;https://blog.boot.dev/tutorials/python/variables;https://blog.boot.dev/tutorials/python/loops/#while;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/tutorials/python/functions/;https://blog.boot.dev/tutorials/python/variables/;https://blog.boot.dev/python/queue-data-structure-python/;https://blog.boot.dev/python/stack-data-structure-python/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/loopsinthesky.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/functionsinpythonscreen.png.webp;https://blog.boot.dev/img/800/functionsinpythonscreen.png.webp;https://blog.boot.dev/img/800/symbolsonboard.png.webp;https://blog.boot.dev/img/800/symbolsonboard.png.webp;https://blog.boot.dev/img/800/queue_of_swords.png.webp;https://blog.boot.dev/img/800/queue_of_swords.png.webp;https://blog.boot.dev/img/800/stackofrocksfantasygame.png.webp;https://blog.boot.dev/img/800/stackofrocksfantasygame.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/tutorials/python/functions/,Functions in Python,"Functions in Python allow us to reuse and organize code. For example, say we have some code that calculates the area of a circle:",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/tutorials/;https://www.boot.dev/teachers/lane-wagner/;https://www.boot.dev/courses/learn-code-python;https://en.wikibooks.org/wiki/Python_Programming/Functions#Function_Calls;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://blog.boot.dev/tutorials/python/functions/#multiple-parameters;https://blog.boot.dev/tutorials/python/functions/#printing-vs-returning;https://blog.boot.dev/tutorials/python/functions/#printing-to-debug-your-code;https://blog.boot.dev/tutorials/python/variables;https://blog.boot.dev/tutorials/python/functions/#where-to-declare-functions;https://blog.boot.dev/tutorials/python/functions/#order-of-functions;https://blog.boot.dev/tutorials/python/functions/#none-return;https://blog.boot.dev/tutorials/python/functions/#multiple-return-values;https://blog.boot.dev/tutorials/python/functions/#receiving-multiple-values;https://blog.boot.dev/tutorials/python/functions/#what-happened-to-the-variables;https://blog.boot.dev/tutorials/python/functions/#parameters-vs-arguments;https://blog.boot.dev/tutorials/python/functions/#default-values;https://docs.python.org/3/glossary.html#term-parameter;https://blog.boot.dev/cdn-cgi/l/email-protection;https://blog.boot.dev/cdn-cgi/l/email-protection;https://blog.boot.dev/cdn-cgi/l/email-protection;https://blog.boot.dev/cdn-cgi/l/email-protection;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/tutorials/python/variables/;https://blog.boot.dev/python/queue-data-structure-python/;https://blog.boot.dev/python/stack-data-structure-python/;https://blog.boot.dev/python/python-for-web-development/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/functionsinpythonscreen.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/symbolsonboard.png.webp;https://blog.boot.dev/img/800/symbolsonboard.png.webp;https://blog.boot.dev/img/800/queue_of_swords.png.webp;https://blog.boot.dev/img/800/queue_of_swords.png.webp;https://blog.boot.dev/img/800/stackofrocksfantasygame.png.webp;https://blog.boot.dev/img/800/stackofrocksfantasygame.png.webp;https://blog.boot.dev/img/800/wagslane_A_majestic_castle_made_of_intertwined_Python_code_stan_24fe25ad-a8fa-40f5-823f-d840c0cd788a.png.webp;https://blog.boot.dev/img/800/wagslane_A_majestic_castle_made_of_intertwined_Python_code_stan_24fe25ad-a8fa-40f5-823f-d840c0cd788a.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/tutorials/python/variables/,Variables and Types in Python,Variables are how we store data as our program runs. You’re probably already familiar with printing data by passing it straight into print():,https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/tutorials/;https://www.boot.dev/teachers/lane-wagner/;https://www.boot.dev/courses/learn-code-python;https://blog.boot.dev/tutorials/python/variables/#creating-variables;https://blog.boot.dev/tutorials/python/variables/#using-variables;https://blog.boot.dev/tutorials/python/variables/#variables-vary;https://blog.boot.dev/tutorials/python/variables/#storing-results;https://www.mathsisfun.com/operation-order-pemdas.html;https://blog.boot.dev/tutorials/python/variables/#negative-numbers;https://blog.boot.dev/tutorials/python/variables/#comments;https://blog.boot.dev/tutorials/python/variables/#single-line-comment;https://blog.boot.dev/tutorials/python/variables/#multi-line-comments-aka-docstrings;https://blog.boot.dev/tutorials/python/variables/#variable-names;https://blog.boot.dev/tutorials/python/variables/#basic-variable-types;https://blog.boot.dev/tutorials/python/variables/#strings;https://blog.boot.dev/tutorials/python/variables/#numbers;https://blog.boot.dev/tutorials/python/variables/#booleans;https://blog.boot.dev/tutorials/python/variables/#f-strings-in-python;https://blog.boot.dev/tutorials/python/variables/#nonetype-variables;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://blog.boot.dev/tutorials/python/variables/#none-is-not-a-string;https://blog.boot.dev/tutorials/python/variables/#dynamic-typing;https://blog.boot.dev/tutorials/python/variables/#but-like-maybe-dont;https://blog.boot.dev/tutorials/python/variables/#what-is-non-dynamic-typing;https://blog.boot.dev/tutorials/python/variables/#math-with-strings;https://blog.boot.dev/tutorials/python/variables/#multi-variable-declaration;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/python/queue-data-structure-python/;https://blog.boot.dev/python/stack-data-structure-python/;https://blog.boot.dev/python/python-for-web-development/;https://blog.boot.dev/python/best-places-to-learn-python-online/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/symbolsonboard.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/queue_of_swords.png.webp;https://blog.boot.dev/img/800/queue_of_swords.png.webp;https://blog.boot.dev/img/800/stackofrocksfantasygame.png.webp;https://blog.boot.dev/img/800/stackofrocksfantasygame.png.webp;https://blog.boot.dev/img/800/wagslane_A_majestic_castle_made_of_intertwined_Python_code_stan_24fe25ad-a8fa-40f5-823f-d840c0cd788a.png.webp;https://blog.boot.dev/img/800/wagslane_A_majestic_castle_made_of_intertwined_Python_code_stan_24fe25ad-a8fa-40f5-823f-d840c0cd788a.png.webp;https://blog.boot.dev/img/800/pythonincastele.png.webp;https://blog.boot.dev/img/800/pythonincastele.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/news/bootdev-beat-2024-12/,The Boot.dev Beat. December 2024,"Potions got a rework, solutions were added to the Git course, and I’ve been sick for like 10 days straight… hope your Holidays are staying healthier than mine!",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/news/;https://www.boot.dev/teachers/lane-wagner/;https://blog.boot.dev/news/bootdev-beat-2024-12/#patch-notes;https://blog.boot.dev/news/bootdev-beat-2024-12/#1-3-month-gift-codes;https://www.boot.dev/gifts;https://blog.boot.dev/news/bootdev-beat-2024-12/#2-git-courses-walkthrough;https://www.boot.dev/teachers/the-primeagen;https://www.boot.dev/courses/learn-git;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://blog.boot.dev/news/bootdev-beat-2024-12/#3-potion-updates;https://blog.boot.dev/news/bootdev-beat-2024-12/#4-miscellaneous-improvements;https://blog.boot.dev/news/bootdev-beat-2024-12/#what-is-yet-to-come;https://blog.boot.dev/news/bootdev-beat-2024-12/#new-youtube-explainer-videos;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://www.youtube.com/watch?v=ygJJ5Gg7txA;https://www.youtube.com/watch?v=IpazwWu__Io;https://www.youtube.com/watch?v=BJc39EExAuU;https://www.youtube.com/watch?v=rQ2h0ljTdNU;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/news/bootdev-beat-2024-11/;https://blog.boot.dev/news/bootdev-beat-2024-10/;https://blog.boot.dev/news/bootdev-beat-2024-09/;https://blog.boot.dev/news/bootdev-beat-2024-08/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/bootsdecember2024.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/bootsthanksgiving.png.webp;https://blog.boot.dev/img/800/bootsthanksgiving.png.webp;https://blog.boot.dev/img/800/bootsoctober.png.webp;https://blog.boot.dev/img/800/bootsoctober.png.webp;https://blog.boot.dev/img/800/bootsseptember2024.png.webp;https://blog.boot.dev/img/800/bootsseptember2024.png.webp;https://blog.boot.dev/img/800/bootsaugust.png.webp;https://blog.boot.dev/img/800/bootsaugust.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/news/bootdev-beat-2024-11/,The Boot.dev Beat. November 2024,Mortrunk has fallen. The community rallied in our Halloween boss fight spectacular. Well done. The Boots’ aura XP boost even hit 2x for a while there.,https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/news/;https://www.boot.dev/teachers/lane-wagner/;https://blog.boot.dev/news/bootdev-beat-2024-11/#patch-notes;https://blog.boot.dev/news/bootdev-beat-2024-11/#1-spellbooks-released;https://blog.boot.dev/news/bootdev-beat-2024-11/#2-new-ui;https://www.twitch.tv/thecoppinger;https://blog.boot.dev/news/bootdev-beat-2024-11/#3-weekly-streaks-are-now-daily;https://blog.boot.dev/news/bootdev-beat-2024-11/#4-miscellaneous-improvements;https://www.boot.dev/community;https://blog.boot.dev/news/bootdev-beat-2024-11/#what-is-yet-to-come;https://blog.boot.dev/news/bootdev-beat-2024-11/#new-youtube-explainer-videos;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://www.youtube.com/watch?v=CULVBpF5UEQ;https://www.youtube.com/watch?v=_02z3tvMr4I;https://www.youtube.com/watch?v=iIaM7j3tMuk;https://www.youtube.com/watch?v=pEew75MjiG8;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/news/bootdev-beat-2024-10/;https://blog.boot.dev/news/bootdev-beat-2024-09/;https://blog.boot.dev/news/bootdev-beat-2024-08/;https://blog.boot.dev/news/bootdev-beat-2024-07/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/bootsthanksgiving.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/profileimage.png.webp;https://blog.boot.dev/img/800/trackimage.png.webp;https://blog.boot.dev/img/800/bootsoctober.png.webp;https://blog.boot.dev/img/800/bootsoctober.png.webp;https://blog.boot.dev/img/800/bootsseptember2024.png.webp;https://blog.boot.dev/img/800/bootsseptember2024.png.webp;https://blog.boot.dev/img/800/bootsaugust.png.webp;https://blog.boot.dev/img/800/bootsaugust.png.webp;https://blog.boot.dev/img/800/bootscamping.png.webp;https://blog.boot.dev/img/800/bootscamping.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/education/state-of-learning-to-code-2024/,The State of Learning to Code - 2024 Report,"I’ve been building a learning curriculum for backend developers for the last 3 years, but I’ve mostly been relying on qualitative feedback and my own intuitions.",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/education/;https://www.boot.dev/teachers/lane-wagner/;https://www.boot.dev;https://blog.boot.dev/education/state-of-learning-to-code-2024/#what-is-this-report;https://blog.boot.dev/education/state-of-learning-to-code-2024/#high-level-numbers;https://blog.boot.dev/education/state-of-learning-to-code-2024/#user-data;https://blog.boot.dev/education/state-of-learning-to-code-2024/#course-data;https://blog.boot.dev/education/state-of-learning-to-code-2024/#usage-data;https://blog.boot.dev/education/state-of-learning-to-code-2024/#the-hardest-concepts-when-learning-to-code;https://www.boot.dev/courses/learn-code-python;https://blog.boot.dev/education/state-of-learning-to-code-2024/#how-does-go-compare-to-python;https://www.boot.dev/courses/learn-golang;https://blog.boot.dev/education/state-of-learning-to-code-2024/#giving-up-vs-struggling;https://en.wikipedia.org/wiki/Normal_distribution;https://en.wikipedia.org/wiki/Standard_deviation;https://blog.boot.dev/education/state-of-learning-to-code-2024/#to-simplify-lets-aggregate-all-this-data-by-course;https://www.boot.dev/teachers/the-primeagen;https://www.boot.dev/teachers/tj-devries;https://blog.boot.dev/education/state-of-learning-to-code-2024/#ai-tutors;https://blog.boot.dev/education/state-of-learning-to-code-2024/#breaking-it-down-by-language;https://blog.boot.dev/education/state-of-learning-to-code-2024/#breaking-it-down-by-model;https://blog.boot.dev/education/state-of-learning-to-code-2024/#ai-content-help;https://blog.boot.dev/education/state-of-learning-to-code-2024/#use-case-1-diagnosing-student-errors;https://blog.boot.dev/education/state-of-learning-to-code-2024/#use-case-2-intentionally-making-content-worse;https://blog.boot.dev/education/state-of-learning-to-code-2024/#resetting-lessons;https://blog.boot.dev/education/state-of-learning-to-code-2024/#people-like-to-rtfm;https://blog.boot.dev/education/state-of-learning-to-code-2024/#vim-mode;https://blog.boot.dev/education/state-of-learning-to-code-2024/#thanks-for-making-it-this-far;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/education/is-there-a-course-on/;https://blog.boot.dev/education/maybe-you-do-need-kubernetes/;https://blog.boot.dev/education/learn-to-code-the-slow-way/;https://blog.boot.dev/education/best-web-dev-books/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/bootsatdesk.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://storage.googleapis.com/qvault-webapp-dynamic-assets/course_assets/juMFNBi.png;https://storage.googleapis.com/qvault-webapp-dynamic-assets/course_assets/rRTiims.png;https://storage.googleapis.com/qvault-webapp-dynamic-assets/course_assets/Y8A6Drd.png;https://storage.googleapis.com/qvault-webapp-dynamic-assets/course_assets/WpupzA0.png;https://storage.googleapis.com/qvault-webapp-dynamic-assets/course_assets/GZ0XzYY.png;https://storage.googleapis.com/qvault-webapp-dynamic-assets/course_assets/b7BKKEa.png;https://storage.googleapis.com/qvault-webapp-dynamic-assets/course_assets/DJAwHkv.png;https://storage.googleapis.com/qvault-webapp-dynamic-assets/course_assets/Om023Gs.png;https://blog.boot.dev/img/800/pileofbooks.png.webp;https://blog.boot.dev/img/800/pileofbooks.png.webp;https://blog.boot.dev/img/800/buildingarobot.png.webp;https://blog.boot.dev/img/800/buildingarobot.png.webp;https://blog.boot.dev/img/800/shortcutwizard.png.webp;https://blog.boot.dev/img/800/shortcutwizard.png.webp;https://blog.boot.dev/img/800/pileofbooks.png.webp;https://blog.boot.dev/img/800/pileofbooks.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/news/bootdev-beat-2024-10/,The Boot.dev Beat. October 2024,"The “learn computer science basics” section of the backend developer learning path is now complete! We have no big plans to make serious modifications (tbh I always say this though, so idk). To be clear, that’s the first half of the track complete - the first 13 courses up to the personal project.",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/news/;https://www.boot.dev/teachers/lane-wagner/;https://blog.boot.dev/news/bootdev-beat-2024-10/#patch-notes;https://www.boot.dev/courses/learn-memory-management-c;https://blog.boot.dev/news/bootdev-beat-2024-10/#1-new-course-learn-memory-management;https://www.boot.dev/teachers/tj-devries;https://www.youtube.com/watch?v=5QZYGU0C2OA;https://blog.boot.dev/news/bootdev-beat-2024-10/#2-functional-programming-course-walkthrough;https://www.boot.dev/community;https://www.boot.dev/courses/learn-functional-programming-python;https://blog.boot.dev/news/bootdev-beat-2024-10/#3-http-servers-and-blog-aggregator-updates;https://www.boot.dev/courses/learn-http-servers-golang;https://www.boot.dev/courses/build-blog-aggregator;https://blog.boot.dev/news/bootdev-beat-2024-10/#4-as-of-today-10185-issue-tickets-closed;https://blog.boot.dev/news/bootdev-beat-2024-10/#5-miscellaneous-improvements;https://blog.boot.dev/news/bootdev-beat-2024-10/#what-is-yet-to-come;https://blog.boot.dev/news/bootdev-beat-2024-10/#new-youtube-explainer-videos;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://www.youtube.com/watch?v=7daqauCVJZg;https://www.youtube.com/watch?v=l1hZaTfFxQ4;https://www.youtube.com/watch?v=pFCxMr5T2mY;https://www.youtube.com/watch?v=fs8jgUMsaL8;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/news/bootdev-beat-2024-09/;https://blog.boot.dev/news/bootdev-beat-2024-08/;https://blog.boot.dev/news/bootdev-beat-2024-07/;https://blog.boot.dev/news/bootdev-beat-2024-06/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/bootsoctober.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/learn-memory-management.png.webp;https://blog.boot.dev/img/800/travvywalkthrough.jpg.webp;https://blog.boot.dev/img/800/bootsseptember2024.png.webp;https://blog.boot.dev/img/800/bootsseptember2024.png.webp;https://blog.boot.dev/img/800/bootsaugust.png.webp;https://blog.boot.dev/img/800/bootsaugust.png.webp;https://blog.boot.dev/img/800/bootscamping.png.webp;https://blog.boot.dev/img/800/bootscamping.png.webp;https://blog.boot.dev/img/800/boots_beach.png.webp;https://blog.boot.dev/img/800/boots_beach.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/,Build a Web Server in Go [Deprecated],"Let’s build a fully-fledged HTTP server from scratch in Go. This course assumes you already have a solid understanding of Go. If you don’t, take a step back and take our Go course.",https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/tutorials/;https://www.boot.dev/teachers/lane-wagner/;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#servers;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#routing;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#architecture;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#json;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#storage;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#authentication-with-passwords;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#authorization;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#webhooks;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#documentation;https://www.boot.dev/courses/learn-golang;https://www.boot.dev/courses/learn-http-servers-golang;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#servers;https://en.wikipedia.org/wiki/Server_%28computing%29;https://go.dev/tour/concurrency;https://nodejs.org/en/;https://expressjs.com;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Event_loop;https://www.djangoproject.com;https://flask.palletsprojects.com/en/2.2.x/;https://wsgi.readthedocs.io/en/latest/what.html;https://uwsgi-docs.readthedocs.io/en/latest/;https://www.youtube.com/watch?v=Z-fBpdwy5aA;https://www.hostinger.com/tutorials/what-is-localhost;https://code.visualstudio.com;https://docs.microsoft.com/en-us/windows/wsl/install;https://golang.org/doc/install;https://github.com/bootdotdev/bootdev;https://pkg.go.dev/net/http#NewServeMux;https://pkg.go.dev/net/http#Server;https://pkg.go.dev/net/http#Server.ListenAndServe;https://github.com/bootdotdev/bootdev;https://pkg.go.dev/net/http#NewServeMux;https://pkg.go.dev/net/http#ServeMux.Handle;https://pkg.go.dev/net/http#FileServer;https://pkg.go.dev/net/http#Dir;https://pkg.go.dev/net/http#FileServer;https://pkg.go.dev/net/http#FileServer;https://pkg.go.dev/net/http#Handler;https://pkg.go.dev/net/http#HandlerFunc;https://pkg.go.dev/net/http#ServeMux.HandleFunc;https://pkg.go.dev/net/http#HandlerFunc;https://pkg.go.dev/net/http#ResponseWriter.WriteHeader;https://pkg.go.dev/net/http#ResponseWriter.Write;https://pkg.go.dev/net/http#ServeMux.Handle;https://pkg.go.dev/net/http#StripPrefix;https://pkg.go.dev/net/http#Handler;https://go.dev/ref/spec#Type_definitions;https://go.dev/tour/methods/9;https://pkg.go.dev/net/http#ServeMux;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#routing;https://en.wikipedia.org/wiki/Middleware;https://en.wikipedia.org/wiki/Wrapper_function;https://github.com/gorilla/mux;https://github.com/go-chi/chi;https://pkg.go.dev/net/http#ServeMux;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#architecture;https://www.youtube.com/watch?v=k0XuJjZ_HP0;https://xkcd.com/908/;https://www.cloudflare.com/learning/cdn/what-is-a-cdn/;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#json;https://marketplace.visualstudio.com/items?itemName=rangav.vscode-thunder-client;https://marketplace.visualstudio.com/items?itemName=humao.rest-client;https://marketplace.visualstudio.com/items?itemName=Postman.postman-for-vscode;https://curl.se;https://www.postman.com;https://www.boot.dev/courses/learn-http-clients-golang;https://blog.boot.dev/golang/json-golang/;https://marketplace.visualstudio.com/items?itemName=rangav.vscode-thunder-client;https://pkg.go.dev/encoding/json#Marshal;https://marketplace.visualstudio.com/items?itemName=rangav.vscode-thunder-client;https://blog.boot.dev/clean-code/dry-code/;https://pkg.go.dev/strings#ToLower;https://pkg.go.dev/strings#Split;https://pkg.go.dev/strings#Join;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#storage;https://www.postgresql.org;https://www.mysql.com;https://blog.boot.dev/golang/golang-mutex/;https://pkg.go.dev/os#ReadFile;https://pkg.go.dev/os#ErrNotExist;https://pkg.go.dev/os#WriteFile;https://pkg.go.dev/sort#Slice;https://pkg.go.dev/database/sql;https://www.postgresql.org;https://www.mysql.com;https://www.mongodb.com;https://firebase.google.com;https://db-engines.com/en/ranking;https://pkg.go.dev/net/http#Request.PathValue;https://pkg.go.dev/strconv#Atoi;https://restfulapi.net;https://www.youtube.com/watch?v=YahE7cWw4p0;https://blog.boot.dev/cdn-cgi/l/email-protection;https://blog.boot.dev/cdn-cgi/l/email-protection;https://pkg.go.dev/flag#Bool;https://pkg.go.dev/flag#Parse;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#authentication-with-passwords;https://blog.boot.dev/cdn-cgi/l/email-protection;https://pkg.go.dev/golang.org/x/crypto/bcrypt#GenerateFromPassword;https://blog.boot.dev/cryptography/bcrypt-step-by-step/;https://blog.boot.dev/cdn-cgi/l/email-protection;https://blog.boot.dev/cdn-cgi/l/email-protection;https://pkg.go.dev/golang.org/x/crypto/bcrypt#CompareHashAndPassword;https://blog.boot.dev/cdn-cgi/l/email-protection;https://blog.boot.dev/cryptography/what-is-entropy-in-cryptography/;https://xkcd.com/936/;https://blog.boot.dev/cryptography/bcrypt-step-by-step/;https://blog.boot.dev/cryptography/how-sha-2-works-step-by-step-sha-256/;https://en.wikipedia.org/wiki/MD5;https://www.youtube.com/watch?v=L1tcPu95fEU;https://blog.boot.dev/cryptography/hmac-and-macs-in-jwts/;https://www.boot.dev/courses/learn-cryptography-golang;https://blog.boot.dev/cdn-cgi/l/email-protection;https://en.wikipedia.org/wiki/Environment_variable;https://github.com/joho/godotenv;https://github.com/golang-jwt/jwt;https://pkg.go.dev/github.com/golang-jwt/jwt/v5#NewWithClaims;https://pkg.go.dev/github.com/golang-jwt/jwt/v5#SigningMethodHS256;https://pkg.go.dev/github.com/golang-jwt/jwt/v5#RegisteredClaims;https://pkg.go.dev/github.com/golang-jwt/jwt/v5#Token.SignedString;https://golang-jwt.github.io/jwt/usage/signing_methods/#signing-methods-and-key-types;https://blog.boot.dev/cdn-cgi/l/email-protection;https://pkg.go.dev/net/http#Header.Get;https://pkg.go.dev/github.com/golang-jwt/jwt/v5#ParseWithClaims;https://pkg.go.dev/github.com/golang-jwt/jwt/v5#Token;https://pkg.go.dev/github.com/golang-jwt/jwt/v5#Claims;https://blog.boot.dev/cdn-cgi/l/email-protection;https://blog.boot.dev/cdn-cgi/l/email-protection;https://pkg.go.dev/crypto/rand#Read;https://pkg.go.dev/encoding/hex#EncodeToString;https://www.rfc-editor.org/rfc/rfc9110.html#name-204-no-content;https://en.wikipedia.org/wiki/HTTP_cookie;https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#authorization;https://blog.boot.dev/cdn-cgi/l/email-protection;https://blog.boot.dev/cdn-cgi/l/email-protection;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#webhooks;https://en.wikipedia.org/wiki/Idempotence;https://en.wikipedia.org/wiki/Idempotence;https://www.youtube.com/watch?v=DsFqTWAQakU;https://blog.boot.dev/tutorials/learn-http-servers-golang-deprecated/#documentation;https://developer.squareup.com/docs/build-basics/common-api-patterns/pagination;https://pkg.go.dev/net/http#Request;https://www.markdownguide.org;https://swagger.io;https://graphql.org;https://go.dev/blog/godoc;https://learning.postman.com/docs/publishing-your-api/documenting-your-api/;https://www.youtube.com/watch?v=oqyCM8_r79w;https://blog.boot.dev/clean-code/what-are-uuids-and-should-you-use-them/;https://github.com/wagslane/go-rabbitmq;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/;https://blog.boot.dev/golang/pointers-faster-than-values/;https://blog.boot.dev/clean-code/give-up-sooner/;https://blog.boot.dev/webdev/put-that-framework-down/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/,https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/webservers.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://storage.googleapis.com/qvault-webapp-dynamic-assets/course_assets/nUzP2kg.png;https://storage.googleapis.com/qvault-webapp-dynamic-assets/course_assets/bmsOkiQ.png;https://storage.googleapis.com/qvault-webapp-dynamic-assets/course_assets/2CofkLc.png;https://storage.googleapis.com/qvault-webapp-dynamic-assets/course_assets/ek42mUQ.png;https://storage.googleapis.com/qvault-webapp-dynamic-assets/course_assets/VDJk0zU.png;https://imgs.xkcd.com/comics/the_cloud.png;https://storage.googleapis.com/qvault-webapp-dynamic-assets/course_assets/tHmqS6O.png;https://storage.googleapis.com/qvault-webapp-dynamic-assets/course_assets/Sf2FcyZ.png;https://imgs.xkcd.com/comics/password_strength.png;https://storage.googleapis.com/qvault-webapp-dynamic-assets/course_assets/hFgop3U.png;https://blog.boot.dev/img/800/blog_aggregator_art.png.webp;https://blog.boot.dev/img/800/blog_aggregator_art.png.webp;https://blog.boot.dev/img/800/arrowpointing.png.webp;https://blog.boot.dev/img/800/arrowpointing.png.webp;https://blog.boot.dev/img/800/goldencitysad.png.webp;https://blog.boot.dev/img/800/goldencitysad.png.webp;https://blog.boot.dev/img/800/frameworkhouse.png.webp;https://blog.boot.dev/img/800/frameworkhouse.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png +https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/,Build a Blog Aggregator in Go [Deprecated],"We’re going to build an RSS feed aggregator in Go! It’s a web server that allows clients to:","https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://www.boot.dev;https://www.boot.dev/tracks/backend;https://www.boot.dev/tracks/backend-python-golang;https://www.boot.dev/tracks/backend-python-typescript;https://www.boot.dev/training;https://www.boot.dev/pricing;https://www.boot.dev/gifts;https://www.boot.dev/redeem;https://www.boot.dev/teams;https://www.boot.dev/community;https://www.youtube.com/@bootdotdev;https://www.backendbanter.fm;https://www.boot.dev/lore;https://blog.boot.dev;https://www.boot.dev/leaderboard;https://blog.boot.dev;https://blog.boot.dev/tutorials/;https://www.boot.dev/teachers/lane-wagner/;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#boilerplate;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#postgresql;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#create-users;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#api-key;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#create-a-feed;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#get-all-feeds;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#feed-follows;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#scraper;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#posts;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#submit-your-git-repo;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#solution;https://en.wikipedia.org/wiki/RSS;https://www.boot.dev/courses/build-blog-aggregator;https://code.visualstudio.com;https://docs.microsoft.com/en-us/windows/wsl/install;https://golang.org/doc/install;https://marketplace.visualstudio.com/items?itemName=golang.Go;https://www.thunderclient.com;https://www.youtube.com/@bootdotdev?sub_confirmation=1;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#boilerplate;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#assignment;https://github.com;https://github.com/joho/godotenv;https://www.freecodecamp.org/news/gitignore-what-is-it-and-how-to-add-to-repo/;https://pkg.go.dev/github.com/joho/godotenv#Load;https://pkg.go.dev/os#Getenv;https://pkg.go.dev/net/http#ServeMux;https://pkg.go.dev/net/http#NewServeMux;https://pkg.go.dev/net/http#Server;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#postgresql;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#how-does-postgresql-work;https://www.postgresql.org/docs/current/app-psql.html#:~:text=psql%20is%20a%20terminal%2Dbased,or%20from%20command%20line%20arguments.;https://www.pgadmin.org;https://brew.sh;https://learn.microsoft.com/en-us/windows/wsl/tutorials/wsl-database#install-postgresql;https://www.pgadmin.org;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#create-users;https://pkg.go.dev/database/sql;https://sqlc.dev;https://www.freecodecamp.org/news/what-is-an-orm-the-meaning-of-object-relational-mapping-database-tools/;https://github.com/pressly/goose;https://docs.sqlc.dev/en/latest/overview/install.html;https://github.com/pressly/goose#install;https://docs.sqlc.dev/en/latest/tutorials/getting-started-postgresql.html;https://docs.sqlc.dev/en/latest/tutorials/getting-started-postgresql.html;https://github.com/lib/pq;https://pkg.go.dev/database/sql#Open;https://pkg.go.dev/github.com/google/uuid;https://blog.boot.dev/clean-code/what-are-uuids-and-should-you-use-them/;https://blog.boot.dev/clean-code/what-are-uuids-and-should-you-use-them/;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#api-key;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#create-a-feed;https://stackoverflow.com/a/14141354;https://pkg.go.dev/context;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#a-custom-type-for-handlers-that-require-authentication;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#middleware-that-authenticates-a-request-gets-the-user-and-calls-the-next-authed-handler;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#using-the-middleware;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#get-all-feeds;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#feed-follows;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#what-is-a-feed-follow;https://en.wikipedia.org/wiki/Many-to-many_(data_model);https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#scraper;https://www.cloudflare.com/learning/ddos/what-is-a-ddos-attack/;https://pkg.go.dev/encoding/xml;https://pkg.go.dev/sync#WaitGroup;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#posts;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#submit-your-git-repo;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#ideas-for-extending-the-project;https://nordicapis.com/everything-you-need-to-know-about-api-pagination/;https://blog.boot.dev/tutorials/build-blog-aggregator-golang-deprecated/#solution;https://github.com/bootdotdev/blog-agg-solution-snapshot-v0;https://github.com/bootdotdev/blog/issues;https://blog.boot.dev/golang/pointers-faster-than-values/;https://blog.boot.dev/clean-code/give-up-sooner/;https://blog.boot.dev/webdev/put-that-framework-down/;https://blog.boot.dev/golang/can-go-used-web-development/;https://blog.boot.dev/categories/;https://blog.boot.dev/contact/;https://www.boot.dev/playground/go;https://blog.boot.dev/about/;https://twitter.com/bootdotdev;https://youtube.com/@bootdotdev;https://backendbanter.fm;https://www.linkedin.com/school/bootdotdev;https://www.boot.dev;https://blog.boot.dev/sitemap.xml;https://blog.boot.dev/index.xml;https://blog.boot.dev/faq/;https://blog.boot.dev/terms/;https://blog.boot.dev/privacy/",https://blog.boot.dev/img/300/bootdev-logo-full-small.png;https://blog.boot.dev/img/800/blog_aggregator_art.png.webp;https://blog.boot.dev/img/800/lane-profile.jpeg.webp;https://blog.boot.dev/img/800/arrowpointing.png.webp;https://blog.boot.dev/img/800/arrowpointing.png.webp;https://blog.boot.dev/img/800/goldencitysad.png.webp;https://blog.boot.dev/img/800/goldencitysad.png.webp;https://blog.boot.dev/img/800/frameworkhouse.png.webp;https://blog.boot.dev/img/800/frameworkhouse.png.webp;https://blog.boot.dev/img/800/cangoimage.png.webp;https://blog.boot.dev/img/800/cangoimage.png.webp;https://blog.boot.dev/img/300/bootdev-logo-full-small.png \ No newline at end of file diff --git a/src/crawl.test.ts b/src/crawl.test.ts new file mode 100644 index 0000000..f7ddbf1 --- /dev/null +++ b/src/crawl.test.ts @@ -0,0 +1,152 @@ +import { expect, test } from 'vitest' +import { + extractPageData, + getFirstParagraphFromHTML, + getH1FromHTML, + getImagesFromHTML, + getURLsFromHTML, + normalizeURL +} from "./crawl"; + +test('adds 1 + 2 to equal 3', () => { + expect(normalizeURL('https://rdarius.com/')).toBe('rdarius.com'); + expect(normalizeURL('http://rdarius.com/')).toBe('rdarius.com'); + expect(normalizeURL('https://rdarius.com/some/web/')).toBe('rdarius.com/some/web'); + expect(normalizeURL('http://rdarius.com/some/web/')).toBe('rdarius.com/some/web'); + expect(normalizeURL('https://rdarius.com/?id=1')).toBe('rdarius.com/?id=1'); + expect(normalizeURL('https://rdarius.com/some-page?id=1')).toBe('rdarius.com/some-page?id=1'); + expect(normalizeURL('https://rdarius.com/some-page/?id=1')).toBe('rdarius.com/some-page?id=1'); + expect(normalizeURL('https://rdarius.com/some/web/#some-comment')).toBe('rdarius.com/some/web'); + expect(normalizeURL('https://rdarius.com/some-page?id=1#some-comment')).toBe('rdarius.com/some-page?id=1'); +}) + +test("getH1FromHTML basic", () => { + const inputBody = `

Test Title

`; + const actual = getH1FromHTML(inputBody); + const expected = "Test Title"; + expect(actual).toEqual(expected); +}); + +test("getFirstParagraphFromHTML main priority", () => { + const inputBody = ` + +

Outside paragraph.

+
+

Main paragraph.

+
+ + `; + const actual = getFirstParagraphFromHTML(inputBody); + const expected = "Main paragraph."; + expect(actual).toEqual(expected); +}); + +test("getURLsFromHTML absolute", () => { + const inputURL = "https://blog.boot.dev"; + const inputBody = `Boot.dev`; + + const actual = getURLsFromHTML(inputBody, inputURL); + const expected = ["https://blog.boot.dev"]; + + expect(actual).toEqual(expected); +}); + +test("getURLsFromHTML relative", () => { + const inputURL = "https://blog.boot.dev"; + const inputBody = `Boot.dev`; + + const actual = getURLsFromHTML(inputBody, inputURL); + const expected = ["https://blog.boot.dev/posts"]; + + expect(actual).toEqual(expected); +}); + +test("getImagesFromHTML absolute", () => { + const inputURL = "https://blog.boot.dev"; + const inputBody = `Logo`; + + const actual = getImagesFromHTML(inputBody, inputURL); + const expected = ["https://blog.boot.dev/logo.png"]; + + expect(actual).toEqual(expected); +}); + +test("getImagesFromHTML relative", () => { + const inputURL = "https://blog.boot.dev"; + const inputBody = `Logo`; + + const actual = getImagesFromHTML(inputBody, inputURL); + const expected = ["https://blog.boot.dev/logo.png"]; + + expect(actual).toEqual(expected); +}); + +test("extractPageData basic", () => { + const inputURL = "https://blog.boot.dev"; + const inputBody = ` + +

Test Title

+

This is the first paragraph.

+ Link 1 + Image 1 + + `; + + const actual = extractPageData(inputBody, inputURL); + const expected = { + url: "https://blog.boot.dev", + h1: "Test Title", + first_paragraph: "This is the first paragraph.", + outgoing_links: ["https://blog.boot.dev/link1"], + image_urls: ["https://blog.boot.dev/image1.jpg"], + }; + + expect(actual).toEqual(expected); +}); + +test("extractPageData with multiple links and images", () => { + const inputURL = "https://example.com"; + const inputBody = ` + +

Another Title

+

Main content paragraph.

+ About + Contact + + + + `; + + const actual = extractPageData(inputBody, inputURL); + const expected = { + url: "https://example.com", + h1: "Another Title", + first_paragraph: "Main content paragraph.", + outgoing_links: [ + "https://example.com/about", + "https://example.com/contact", + ], + image_urls: [ + "https://example.com/img/logo.png", + "https://example.com/banner.png", + ], + }; + + expect(actual).toEqual(expected); +}); + +test("extractPageData empty elements", () => { + const inputURL = "https://empty.org"; + const inputBody = `
No useful content
`; + + const actual = extractPageData(inputBody, inputURL); + const expected = { + url: "https://empty.org", + h1: "", + first_paragraph: "", + outgoing_links: [], + image_urls: [], + }; + + expect(actual).toEqual(expected); +}); \ No newline at end of file diff --git a/src/crawl.ts b/src/crawl.ts new file mode 100644 index 0000000..8207bc3 --- /dev/null +++ b/src/crawl.ts @@ -0,0 +1,246 @@ +import {JSDOM} from 'jsdom' +import pLimit from "p-limit"; + +export interface ExtractedPageData { + url: string; + h1: string; + first_paragraph: string; + outgoing_links: string[]; + image_urls: string[]; +} + +function removeTrailingSlash(url: string): string { + return url.endsWith("/") && url.indexOf("/", 9) === url.length - 1 + ? url.slice(0, -1) + : url; +} + +export function normalizeURL(url: string): string { + const parsed = new URL(url); + let final = parsed.hostname; + let path = parsed.pathname.split("/").filter((x) => x); + if (path.length > 0) { + final += "/" + path.join("/"); + } + if (parsed.search) { + final += path.length > 0 ? parsed.search : "/" + parsed.search; + } + return final; +} + +export function getH1FromHTML(html: string): string { + const dom = new JSDOM(html); + const h1 = dom.window.document.body.querySelector('h1'); + if (h1) { + return h1.innerHTML; + } + return ''; +} + +export function getFirstParagraphFromHTML(html: string): string { + const dom = new JSDOM(html); + const p = dom.window.document.body.querySelector('main p'); + if (p) { + return p.innerHTML; + } else { + const secondaryP = dom.window.document.body.querySelector('p'); + if (secondaryP) { + return secondaryP.innerHTML; + } + } + return ''; +} + +export function getURLsFromHTML(html: string, baseURL: string): string[] { + const urls: string[] = []; + const dom = new JSDOM(html); + const a = dom.window.document.querySelectorAll("a"); + + for (const link of a) { + const href = link.getAttribute("href"); + if (!href) continue; + try { + const urlObj = new URL(href, baseURL); + urls.push(removeTrailingSlash(urlObj.toString())); + } catch { + console.error(`Invalid URL found: ${href}`); + } + } + return urls; +} + +export function getImagesFromHTML(html: string, baseURL: string): string[] { + const urls = []; + const dom = new JSDOM(html); + const a = dom.window.document.body.querySelectorAll('img'); + + for (const link of a) { + const src = link.getAttribute("src"); + if (!src) continue; + try { + const urlObj = new URL(src, baseURL); + urls.push(removeTrailingSlash(urlObj.toString())); + } catch { + console.error(`Invalid Image URL found: ${src}`); + } + } + + return urls; +} + +export function extractPageData(html: string, pageURL: string): ExtractedPageData { + return { + url: pageURL, + h1: getH1FromHTML(html), + first_paragraph: getFirstParagraphFromHTML(html), + outgoing_links: getURLsFromHTML(html, pageURL), + image_urls: getImagesFromHTML(html, pageURL), + }; +} + +export class ConcurrentCrawler { + private readonly baseURL: string; + private readonly pages: Record; + private readonly limit: (fn: () => Promise) => Promise; + private readonly maxPages: number; + private shouldStop: boolean; + private readonly allTasks: Set>; + private abortController: AbortController; + private readonly toCrawl: string[]; + + constructor( + baseURL: string, + maxConcurrency: number = 5, + maxPages: number = 50 + ) { + this.baseURL = baseURL; + this.pages = {}; + this.limit = pLimit(maxConcurrency); + this.maxPages = maxPages; + this.shouldStop = false; + this.allTasks = new Set(); + this.abortController = new AbortController(); + this.toCrawl = []; + } + + private addPageVisit( + normalizedURL: string, + data: ExtractedPageData + ): boolean { + if (this.shouldStop) return false; + + if (this.pages[normalizedURL]) { + // already crawled + return false; + } + + this.pages[normalizedURL] = data; + + if (Object.keys(this.pages).length >= this.maxPages) { + console.log("⚠️ Reached maximum number of pages to crawl."); + this.shouldStop = true; + this.abortController.abort(); + return false; + } + + return true; + } + + private async getHTML(currentURL: string): Promise { + return this.limit(async () => { + if (this.shouldStop) return; + try { + const res = await fetch(currentURL, { + headers: { "User-Agent": "BootCrawler/1.0" }, + signal: this.abortController.signal, + }); + if (res.status >= 400) { + console.error(`Error: HTTP ${res.status} for ${currentURL}`); + return; + } + const contentType = res.headers.get("content-type"); + if (!contentType || !contentType.includes("text/html")) { + console.error( + `Error: Non-HTML content-type (${contentType}) for ${currentURL}` + ); + return; + } + return await res.text(); + } catch (err: any) { + if (err.name === "AbortError") { + console.warn(`Fetch aborted for ${currentURL}`); + } else { + console.error(`Error fetching ${currentURL}:`, err); + } + return; + } + }); + } + + private async crawlPage(): Promise { + let currentURL = ''; + if (this.toCrawl.length > 0) { + const currentURLTemp = this.toCrawl.shift(); + if (!currentURLTemp) return; + currentURL = currentURLTemp; + } + + if (this.shouldStop) return; + + const base = new URL(this.baseURL); + let current: URL; + try { + current = new URL(currentURL); + } catch { + return; + } + + if (current.hostname !== base.hostname) return; + + const normalized = normalizeURL(current.href); + const html = await this.getHTML(current.href); + if (!html) return; + + const data = extractPageData(html, current.href); + if (!this.addPageVisit(normalized, data)) return; + + console.log(`🌐 Crawled: ${current.href}`); + + for (const url of data.outgoing_links) { + if (this.shouldStop) break; + //this.crawlPage(url) + this.schedule(url); + } + } + + private schedule(url: string) { + // const tracked = promise.finally(() => this.allTasks.delete(tracked)); + // this.allTasks.add(tracked); + this.toCrawl.push(url); + } + + public async crawl(): Promise> { + this.schedule(this.baseURL); + + do { + await this.crawlPage() + } while (this.toCrawl.length > 0); + + // while (this.allTasks.size > 0) { + // console.log("Active tasks:", this.allTasks.size); + // await Promise.race(this.allTasks); + // } + // console.log(this.pages); // ✅ will now run + + return this.pages; + } +} + +export async function crawlSiteAsync( + baseURL: string, + maxConcurrency: number, + maxPages: number +): Promise> { + const crawler = new ConcurrentCrawler(baseURL, maxConcurrency, maxPages); + return await crawler.crawl(); +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..2ba0cbf --- /dev/null +++ b/src/index.ts @@ -0,0 +1,44 @@ +import {crawlSiteAsync, ExtractedPageData} from "./crawl"; +import {writeCSVReport} from "./report"; + + +async function main() { + const args = process.argv.slice(2); + + if (args.length < 1) { + console.error("Usage: npm run start "); + process.exit(1); + } + if (args.length > 3) { + console.error("Error: Too many arguments."); + process.exit(1); + } + + const baseURL = args[0]; + const maxConcurrency = args[1] ? parseInt(args[1]) : 3; + const maxPages = args[2] ? parseInt(args[2]) : 50; + + console.log(`🚀 Starting crawl at ${baseURL} (concurrency=${maxConcurrency}, maxPages=${maxPages})`); + + let pageData: Record = {}; + writeCSVReport(pageData, "report.csv"); + + try { + pageData = await crawlSiteAsync(baseURL, maxConcurrency, maxPages); + // console.log(pageData); + } catch (err) { + console.error("❌ Crawler failed:", err); + } finally { + try { + if (Object.keys(pageData).length > 0) { + writeCSVReport(pageData, "report.csv"); + } else { + console.warn("⚠️ No pages crawled, skipping report generation."); + } + } catch (err) { + console.error("❌ Failed to write report:", err); + } + } +} + +main().finally(); \ No newline at end of file diff --git a/src/report.ts b/src/report.ts new file mode 100644 index 0000000..3ffa5b1 --- /dev/null +++ b/src/report.ts @@ -0,0 +1,42 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; +import { ExtractedPageData } from "./crawl"; + +function csvEscape(field: string): string { + const str = field ?? ""; + const needsQuoting = /[",\n]/.test(str); + const escaped = str.replace(/"/g, '""'); + return needsQuoting ? `"${escaped}"` : escaped; +} + +export function writeCSVReport( + pageData: Record, + filename = "report.csv" +): void { + const filepath = path.resolve(process.cwd(), filename); + + const headers = [ + "page_url", + "h1", + "first_paragraph", + "outgoing_link_urls", + "image_urls", + ]; + + const rows: string[] = [headers.join(",")]; + + for (const page of Object.values(pageData)) { + const row = [ + csvEscape(page.url), + csvEscape(page.h1), + csvEscape(page.first_paragraph), + csvEscape(page.outgoing_links.join(";")), + csvEscape(page.image_urls.join(";")), + ].join(","); + rows.push(row); + } + + fs.writeFileSync(filepath, rows.join("\n"), "utf-8"); + + console.log(`✅ Report written to ${filepath}`); +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1fe4328 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "strict": true, + "moduleResolution": "Node" + }, + "include": ["./src/**/*.ts"], + "exclude": ["node_modules"] +} \ No newline at end of file