diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7c1e3b9..5521b1e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,10 +1,14 @@ version: 2 updates: + - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" open-pull-requests-limit: 100 - labels: - - "dependencies" - - "github-actions" + + - package-ecosystem: "npm" + directory: "/test/node/" + schedule: + interval: "monthly" + open-pull-requests-limit: 100 diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6ab8a1f..e132270 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -26,67 +26,61 @@ env: jobs: package-tests: - name: Package tests - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest timeout-minutes: 60 strategy: fail-fast: false matrix: version: - - '1.10' # 1.10 = current LTS - os: - - ubuntu-latest + - '1.10' # currently the LTS steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2 - + with: + persist-credentials: false - uses: julia-actions/setup-julia@4a12c5f801ca5ef0458bba44687563ef276522dd # v3.0.0 with: version: ${{ matrix.version }} - - uses: julia-actions/cache@9a93c5fb3e9c1c20b60fc80a478cae53e38618a4 # v3.0.2 - - name: Install dependencies run: julia --color=yes --project -e "using Pkg; Pkg.instantiate()" - - uses: julia-actions/julia-runtest@d60b785c6f2bdf4ebfb18b2b6f7d93b7dfb0efe3 # v1.11.4 - full-test: - name: Full test + full-build-and-test: # This job takes a long time (1+ hours). # So we intentionally skip this job on PR runs. # But we still run this job on Merge Queue jobs. - if: github.event_name != 'pull_request' + # if: github.event_name != 'pull_request' # TODO: Uncomment this line before merging the PR runs-on: ubuntu-latest timeout-minutes: 360 strategy: fail-fast: false - steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2 - + with: + persist-credentials: false - uses: julia-actions/setup-julia@4a12c5f801ca5ef0458bba44687563ef276522dd # v3.0.0 with: - version: '1.10' # 1.10 = current LTS - + version: 'lts' - uses: julia-actions/cache@9a93c5fb3e9c1c20b60fc80a478cae53e38618a4 # v3.0.2 - - name: Install dependencies run: julia --color=yes --project -e "using Pkg; Pkg.instantiate()" - - - run: rm -f versions.json - - - name: Build versions.json + - name: Cleanup before we build run: | - using VersionsJSONUtil - - VersionsJSONUtil.main("versions.json") - shell: julia --project {0} - + make clean + make nuke + make purge_download_cache_dir + - name: Download the old versions.json, so we can re-use it and build on top of it + run: curl -LO https://julialang-s3.julialang.org/bin/versions.json + - name: Build versions.json + run: make build + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Used to authenticate to GitHub API to get list of tags + - name: Run the post-build tests + run: make test-full + - name: Install NPM deps + run: make _npm_ci - name: Validate versions.json against schema - run: npx -p ajv-cli@3.3.0 ajv -s schema.json -d versions.json - - - run: julia --project test/more_tests.jl versions.json - + run: make check-schema - name: Upload versions.json as workflow artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: @@ -95,31 +89,29 @@ jobs: if-no-files-found: error upload-to-s3: - needs: [package-tests, full-test] + needs: + - package-tests + - full-build-and-test if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest timeout-minutes: 60 - steps: - name: Download versions.json from previous job uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: versions - - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # 6.1.0 with: aws-access-key-id: ${{ secrets.AWS_DEPLOY_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_DEPLOY_SECRET_ACCESS_KEY }} aws-region: ${{ env.aws_region }} - - name: Upload versions.json to S3 run: aws s3 cp versions.json s3://${{ env.s3_bucket }}/bin/versions.json --acl public-read --no-progress - - - name: Purge cache + - name: Purge Fastly cache (Fastly sits in front of S3) run: curl -X PURGE https://julialang-s3.julialang.org/bin/versions.json - test-current-s3: + test-current-s3-allowed-to-fail: # We intentionally do not make this job a "required status check" on PRs. # Because if the currently deployed `versions.json` on S3 is already broken, # then this job will fail, which would block us from merging a PR with fixes. @@ -142,4 +134,4 @@ jobs: - name: Download the current versions.json from S3 run: curl -LO https://julialang-s3.julialang.org/bin/versions.json - name: Run the post-build tests on the versions.json that we downloaded from S3 - run: julia --project test/more_tests.jl versions.json + run: julia --project test/post_build.jl versions.json full diff --git a/.gitignore b/.gitignore index a507691..8f78070 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,10 @@ +# Directories: +cache/ +node_modules/ + +# Files: +internal.json versions.json + +# macOS-specific: +.DS_Store diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4a66bc7 --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +# Usage: +# make build +# make test + +ifdef CI +JULIA ?= julia +else +JULIA ?= julia +1.10 +endif + +# This is the default target: +# It's a phony target because you might want to re-build versions.json even if none of the +# files in this repo have changed. +.PHONY: build +build: _instantiate + JULIA_LOAD_PATH='@' $(JULIA) --startup-file=no --project -e 'import VersionsJSONUtil; VersionsJSONUtil.main(".")' + +# These are the post-build tests: +.PHONY: test +test: test-full + +.PHONY: test-full +test-full: _instantiate + JULIA_LOAD_PATH='@:@stdlib' $(JULIA) --startup-file=no --project test/post_build.jl versions.json full + +.PHONY: test-partial +test-partial: _instantiate + JULIA_LOAD_PATH='@:@stdlib' $(JULIA) --startup-file=no --project test/post_build.jl versions.json partial + +.PHONY: check-schema +check-schema: + (cd test/node && npx ajv -s ../../schema.json -d ../../versions.json) + +# ------------------------------------------------------------------------------------------ + +.PHONY: _instantiate +_instantiate: + JULIA_LOAD_PATH='@:@stdlib' $(JULIA) --startup-file=no --project -e 'import Pkg; Pkg.instantiate(); Pkg.precompile()' + +.PHONY: _npm_ci +_npm_ci: + (cd test/node && npm ci) + + +.PHONY: clean +clean: + $(RM) -v internal.json + +.PHONY: nuke +nuke: + $(RM) -v internal.json + $(RM) -v versions.json + +.PHONY: _purge_node_modules +_purge_node_modules: + $(RM) -r test/node/node_modules/ + +.PHONY: purge_download_cache_dir +purge_download_cache_dir: + $(RM) -r cache/ diff --git a/Manifest.toml b/Manifest.toml index 1830bc9..3367c70 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -2,34 +2,7 @@ julia_version = "1.10.11" manifest_format = "2.0" -project_hash = "297ab27881cec0a85c719648b28381580e85162c" - -[[deps.AbstractFFTs]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "d92ad398961a3ed262d8bf04a1a2b8340f915fef" -uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c" -version = "1.5.0" -weakdeps = ["ChainRulesCore", "Test"] - - [deps.AbstractFFTs.extensions] - AbstractFFTsChainRulesCoreExt = "ChainRulesCore" - AbstractFFTsTestExt = "Test" - -[[deps.AbstractTrees]] -git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" -uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" -version = "0.4.5" - -[[deps.Adapt]] -deps = ["LinearAlgebra", "Requires"] -git-tree-sha1 = "35ea197a51ce46fcd01c4a44befce0578a1aaeca" -uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" -version = "4.5.0" -weakdeps = ["SparseArrays", "StaticArrays"] - - [deps.Adapt.extensions] - AdaptSparseArraysExt = "SparseArrays" - AdaptStaticArraysExt = "StaticArrays" +project_hash = "58edaeb5d2e480fd78155d25247bfe12e4a3b837" [[deps.AliasTables]] deps = ["PtrArrays", "Random"] @@ -41,27 +14,9 @@ version = "1.1.3" uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" version = "1.1.1" -[[deps.Arpack]] -deps = ["Arpack_jll", "Libdl", "LinearAlgebra", "Logging"] -git-tree-sha1 = "9b9b347613394885fd1c8c7729bfc60528faa436" -uuid = "7d9fca2a-8960-54d3-9f78-7d1dccf2cb97" -version = "0.5.4" - -[[deps.Arpack_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "libblastrampoline_jll"] -git-tree-sha1 = "7f54761502ff149a9d492e4acefe9805898e29b3" -uuid = "68821587-b530-5797-8361-c406ea357684" -version = "3.5.2+0" - [[deps.Artifacts]] uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" -[[deps.AxisAlgorithms]] -deps = ["LinearAlgebra", "Random", "SparseArrays", "WoodburyMatrices"] -git-tree-sha1 = "01b8ccb13d68535d73d2b0c23e39bd23155fb712" -uuid = "13072b0f-2c55-5437-9ae7-d433b7a33950" -version = "1.1.0" - [[deps.Base64]] uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" @@ -70,86 +25,12 @@ git-tree-sha1 = "0691e34b3bb8be9307330f88d1a3c3f25466c24d" uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35" version = "0.1.9" -[[deps.Bzip2_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1b96ea4a01afe0ea4090c5c8039690672dd13f2e" -uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" -version = "1.0.9+0" - -[[deps.CSV]] -deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "PrecompileTools", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings", "WorkerUtilities"] -git-tree-sha1 = "8d8e0b0f350b8e1c91420b5e64e5de774c2f0f4d" -uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" -version = "0.10.16" - -[[deps.Cairo_jll]] -deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] -git-tree-sha1 = "d0efe2c6fdcdaa1c161d206aa8b933788397ec71" -uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" -version = "1.18.6+0" - -[[deps.ChainRulesCore]] -deps = ["Compat", "LinearAlgebra"] -git-tree-sha1 = "12177ad6b3cad7fd50c8b3825ce24a99ad61c18f" -uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -version = "1.26.1" -weakdeps = ["SparseArrays"] - - [deps.ChainRulesCore.extensions] - ChainRulesCoreSparseArraysExt = "SparseArrays" - -[[deps.Clustering]] -deps = ["Distances", "LinearAlgebra", "NearestNeighbors", "Printf", "Random", "SparseArrays", "Statistics", "StatsBase"] -git-tree-sha1 = "3e22db924e2945282e70c33b75d4dde8bfa44c94" -uuid = "aaaa29a8-35af-508c-8bc3-b662a17a0fe5" -version = "0.15.8" - -[[deps.CodeTracking]] -deps = ["InteractiveUtils", "REPL", "UUIDs"] -git-tree-sha1 = "cfb7a2e89e245a9d5016b70323db412b3a7438d5" -uuid = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" -version = "3.0.2" - [[deps.CodecZlib]] deps = ["TranscodingStreams", "Zlib_jll"] git-tree-sha1 = "962834c22b66e32aa10f7611c08c8ca4e20749a9" uuid = "944b1d66-785c-5afd-91f1-9de20f533193" version = "0.7.8" -[[deps.ColorSchemes]] -deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "PrecompileTools", "Random"] -git-tree-sha1 = "b0fd3f56fa442f81e0a47815c92245acfaaa4e34" -uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4" -version = "3.31.0" - -[[deps.ColorTypes]] -deps = ["FixedPointNumbers", "Random"] -git-tree-sha1 = "67e11ee83a43eb71ddc950302c53bf33f0690dfe" -uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" -version = "0.12.1" - - [deps.ColorTypes.extensions] - StyledStringsExt = "StyledStrings" - - [deps.ColorTypes.weakdeps] - StyledStrings = "f489334b-da3d-4c2e-b8f0-e476e12c162b" - -[[deps.ColorVectorSpace]] -deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Requires", "Statistics", "TensorCore"] -git-tree-sha1 = "8b3b6f87ce8f65a2b4f857528fd8d70086cd72b1" -uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4" -version = "0.11.0" -weakdeps = ["SpecialFunctions"] - - [deps.ColorVectorSpace.extensions] - SpecialFunctionsExt = "SpecialFunctions" - -[[deps.Colors]] -deps = ["ColorTypes", "FixedPointNumbers", "Reexport"] -git-tree-sha1 = "37ea44092930b1811e666c3bc38065d7d87fcc74" -uuid = "5ae59095-9a9b-59fe-a467-6f913c188581" -version = "0.13.1" - [[deps.Compat]] deps = ["TOML", "UUIDs"] git-tree-sha1 = "9d8a54ce4b17aa5bdce0ea5c34bc5e7c340d16ad" @@ -160,11 +41,6 @@ weakdeps = ["Dates", "LinearAlgebra"] [deps.Compat.extensions] CompatLinearAlgebraExt = "LinearAlgebra" -[[deps.Compiler]] -git-tree-sha1 = "382d79bfe72a406294faca39ef0c3cef6e6ce1f1" -uuid = "807dbc54-b67e-4c79-8afb-eafe4df6f2e1" -version = "0.1.1" - [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" @@ -176,85 +52,25 @@ git-tree-sha1 = "21d088c496ea22914fe80906eb5bce65755e5ec8" uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb" version = "2.5.1" -[[deps.Contour]] -git-tree-sha1 = "439e35b0b36e2e5881738abc8857bd92ad6ff9a8" -uuid = "d38c429a-6771-53c6-b99e-75d170b6e991" -version = "0.6.3" - -[[deps.Crayons]] -git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15" -uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f" -version = "4.1.1" - [[deps.DataAPI]] git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" version = "1.16.0" -[[deps.DataFrames]] -deps = ["Compat", "DataAPI", "DataStructures", "Future", "InlineStrings", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrecompileTools", "PrettyTables", "Printf", "Random", "Reexport", "SentinelArrays", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"] -git-tree-sha1 = "d8928e9169ff76c6281f39a659f9bca3a573f24c" -uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" -version = "1.8.1" - [[deps.DataStructures]] deps = ["OrderedCollections"] git-tree-sha1 = "e86f4a2805f7f19bec5129bc9150c38208e5dc23" uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" version = "0.19.4" -[[deps.DataValueInterfaces]] -git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" -uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" -version = "1.0.0" - [[deps.Dates]] deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" -[[deps.Dbus_jll]] -deps = ["Artifacts", "Expat_jll", "JLLWrappers", "Libdl"] -git-tree-sha1 = "473e9afc9cf30814eb67ffa5f2db7df82c3ad9fd" -uuid = "ee1fde0b-3d02-5ea6-8484-8dfef6360eab" -version = "1.16.2+0" - -[[deps.DelimitedFiles]] -deps = ["Mmap"] -git-tree-sha1 = "9e2f36d3c96a820c678f2f1f1782582fcf685bae" -uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" -version = "1.9.1" - -[[deps.Distances]] -deps = ["LinearAlgebra", "Statistics", "StatsAPI"] -git-tree-sha1 = "c7e3a542b999843086e2f29dac96a618c105be1d" -uuid = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7" -version = "0.10.12" -weakdeps = ["ChainRulesCore", "SparseArrays"] - - [deps.Distances.extensions] - DistancesChainRulesCoreExt = "ChainRulesCore" - DistancesSparseArraysExt = "SparseArrays" - [[deps.Distributed]] deps = ["Random", "Serialization", "Sockets"] uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" -[[deps.Distributions]] -deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"] -git-tree-sha1 = "fbcc7610f6d8348428f722ecbe0e6cfe22e672c6" -uuid = "31c24e10-a181-5473-b8eb-7969acd0382f" -version = "0.25.123" - - [deps.Distributions.extensions] - DistributionsChainRulesCoreExt = "ChainRulesCore" - DistributionsDensityInterfaceExt = "DensityInterface" - DistributionsTestExt = "Test" - - [deps.Distributions.weakdeps] - ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" - DensityInterface = "b429d917-457f-4dbc-8f4c-0cc954292b1d" - Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - [[deps.DocStringExtensions]] git-tree-sha1 = "7442a5dfe1ebb773c29cc2962a8980f47221d76c" uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" @@ -265,11 +81,10 @@ deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" version = "1.6.0" -[[deps.EpollShim_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "8a4be429317c42cfae6a7fc03c31bad1970c310d" -uuid = "2702e6a9-849d-5ed8-8c21-79e8b8f9ee43" -version = "0.0.20230411+1" +[[deps.EnumX]] +git-tree-sha1 = "c49898e8438c828577f04b92fc9368c388ac783c" +uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56" +version = "1.0.7" [[deps.ExceptionUnwrapping]] deps = ["Test"] @@ -277,159 +92,19 @@ git-tree-sha1 = "d36f682e590a83d63d1c7dbd287573764682d12a" uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4" version = "0.1.11" -[[deps.Expat_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "27af30de8b5445644e8ffe3bcb0d72049c089cf1" -uuid = "2e619515-83b5-522b-bb60-26c02a35a201" -version = "2.7.3+0" - [[deps.ExprTools]] git-tree-sha1 = "27415f162e6028e81c72b82ef756bf321213b6ec" uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04" version = "0.1.10" -[[deps.FFMPEG]] -deps = ["FFMPEG_jll"] -git-tree-sha1 = "95ecf07c2eea562b5adbd0696af6db62c0f52560" -uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a" -version = "0.4.5" - -[[deps.FFMPEG_jll]] -deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "PCRE2_jll", "Zlib_jll", "libaom_jll", "libass_jll", "libfdk_aac_jll", "libva_jll", "libvorbis_jll", "x264_jll", "x265_jll"] -git-tree-sha1 = "66381d7059b5f3f6162f28831854008040a4e905" -uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5" -version = "8.0.1+1" - -[[deps.FFTA]] -deps = ["AbstractFFTs", "DocStringExtensions", "LinearAlgebra", "MuladdMacro", "Primes", "Random", "Reexport"] -git-tree-sha1 = "65e55303b72f4a567a51b174dd2c47496efeb95a" -uuid = "b86e33f2-c0db-4aa1-a6e0-ab43e668529e" -version = "0.3.1" - -[[deps.FilePathsBase]] -deps = ["Compat", "Dates"] -git-tree-sha1 = "3bab2c5aa25e7840a4b065805c0cdfc01f3068d2" -uuid = "48062228-2e41-5def-b9a4-89aafe57970f" -version = "0.9.24" -weakdeps = ["Mmap", "Test"] - - [deps.FilePathsBase.extensions] - FilePathsBaseMmapExt = "Mmap" - FilePathsBaseTestExt = "Test" - [[deps.FileWatching]] uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" -[[deps.FillArrays]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "2f979084d1e13948a3352cf64a25df6bd3b4dca3" -uuid = "1a297f60-69ca-5386-bcde-b61e274b549b" -version = "1.16.0" -weakdeps = ["PDMats", "SparseArrays", "StaticArrays", "Statistics"] - - [deps.FillArrays.extensions] - FillArraysPDMatsExt = "PDMats" - FillArraysSparseArraysExt = "SparseArrays" - FillArraysStaticArraysExt = "StaticArrays" - FillArraysStatisticsExt = "Statistics" - -[[deps.FixedPointNumbers]] -deps = ["Statistics"] -git-tree-sha1 = "05882d6995ae5c12bb5f36dd2ed3f61c98cbb172" -uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" -version = "0.8.5" - -[[deps.Fontconfig_jll]] -deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"] -git-tree-sha1 = "f85dac9a96a01087df6e3a749840015a0ca3817d" -uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" -version = "2.17.1+0" - -[[deps.Format]] -git-tree-sha1 = "9c68794ef81b08086aeb32eeaf33531668d5f5fc" -uuid = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8" -version = "1.3.7" - -[[deps.FreeType2_jll]] -deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "70329abc09b886fd2c5d94ad2d9527639c421e3e" -uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" -version = "2.14.3+1" - -[[deps.FriBidi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "7a214fdac5ed5f59a22c2d9a885a16da1c74bbc7" -uuid = "559328eb-81f9-559d-9380-de523a88c83c" -version = "1.0.17+0" - -[[deps.Future]] -deps = ["Random"] -uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" - -[[deps.GLFW_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Xorg_libXcursor_jll", "Xorg_libXi_jll", "Xorg_libXinerama_jll", "Xorg_libXrandr_jll", "libdecor_jll", "xkbcommon_jll"] -git-tree-sha1 = "b7bfd56fa66616138dfe5237da4dc13bbd83c67f" -uuid = "0656b61e-2033-5cc2-a64a-77c0f6c09b89" -version = "3.4.1+0" - -[[deps.GR]] -deps = ["Artifacts", "Base64", "DelimitedFiles", "Downloads", "GR_jll", "HTTP", "JSON", "Libdl", "LinearAlgebra", "Preferences", "Printf", "Qt6Wayland_jll", "Random", "Serialization", "Sockets", "TOML", "Tar", "Test", "p7zip_jll"] -git-tree-sha1 = "44716a1a667cb867ee0e9ec8edc31c3e4aa5afdc" -uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" -version = "0.73.24" - - [deps.GR.extensions] - IJuliaExt = "IJulia" - - [deps.GR.weakdeps] - IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" - -[[deps.GR_jll]] -deps = ["Artifacts", "Bzip2_jll", "Cairo_jll", "FFMPEG_jll", "Fontconfig_jll", "FreeType2_jll", "GLFW_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pixman_jll", "Qt6Base_jll", "Zlib_jll", "libpng_jll"] -git-tree-sha1 = "be8a1b8065959e24fdc1b51402f39f3b6f0f6653" -uuid = "d2c73de3-f751-5644-a686-071e5b155ba9" -version = "0.73.24+0" - -[[deps.GettextRuntime_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll"] -git-tree-sha1 = "45288942190db7c5f760f59c04495064eedf9340" -uuid = "b0724c58-0f36-5564-988d-3bb0596ebc4a" -version = "0.22.4+0" - -[[deps.Ghostscript_jll]] -deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Zlib_jll"] -git-tree-sha1 = "38044a04637976140074d0b0621c1edf0eb531fd" -uuid = "61579ee1-b43e-5ca0-a5da-69d92c66a64b" -version = "9.55.1+0" - -[[deps.Glib_jll]] -deps = ["Artifacts", "GettextRuntime_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"] -git-tree-sha1 = "24f6def62397474a297bfcec22384101609142ed" -uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" -version = "2.86.3+0" - -[[deps.Graphite2_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "8a6dbda1fd736d60cc477d99f2e7a042acfa46e8" -uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472" -version = "1.3.15+0" - -[[deps.Grisu]] -git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2" -uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe" -version = "1.0.2" - -[[deps.Gumbo]] -deps = ["AbstractTrees", "Gumbo_jll", "Libdl"] -git-tree-sha1 = "eab9e02310eb2c3e618343c859a12b51e7577f5e" -uuid = "708ec375-b3d6-5a57-a7ce-8257bf98657a" -version = "0.8.3" - -[[deps.Gumbo_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "29070dee9df18d9565276d68a596854b1764aa38" -uuid = "528830af-5a63-567c-a44a-034ed33b8444" -version = "0.10.2+0" +[[deps.GitHub]] +deps = ["Base64", "Dates", "HTTP", "JSON", "MbedTLS", "Sockets", "SodiumSeal", "URIs"] +git-tree-sha1 = "ef535d4e8cb96c4fb6753212744ee737fad1bc50" +uuid = "bc5e4493-9b4d-5f90-b8aa-2b2bcaad7a26" +version = "5.12.0" [[deps.HTTP]] deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "PrecompileTools", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"] @@ -437,18 +112,6 @@ git-tree-sha1 = "51059d23c8bb67911a2e6fd5130229113735fc7e" uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" version = "1.11.0" -[[deps.HarfBuzz_jll]] -deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll"] -git-tree-sha1 = "f923f9a774fcf3f5cb761bfa43aeadd689714813" -uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566" -version = "8.5.1+0" - -[[deps.HypergeometricFunctions]] -deps = ["LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"] -git-tree-sha1 = "68c173f4f449de5b438ee67ed0c9c748dc31a2ec" -uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a" -version = "0.3.28" - [[deps.InlineStrings]] git-tree-sha1 = "8f3d257792a522b4601c24a577954b0a8cd7334d" uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" @@ -462,50 +125,15 @@ version = "1.4.5" ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" Parsers = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -[[deps.IntegerMathUtils]] -git-tree-sha1 = "4c1acff2dc6b6967e7e750633c50bc3b8d83e617" -uuid = "18e54dd8-cb9d-406c-a71d-865a43cbb235" -version = "0.1.3" - [[deps.InteractiveUtils]] deps = ["Markdown"] uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" -[[deps.Interpolations]] -deps = ["Adapt", "AxisAlgorithms", "ChainRulesCore", "LinearAlgebra", "OffsetArrays", "Random", "Ratios", "SharedArrays", "SparseArrays", "StaticArrays", "WoodburyMatrices"] -git-tree-sha1 = "65d505fa4c0d7072990d659ef3fc086eb6da8208" -uuid = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" -version = "0.16.2" - - [deps.Interpolations.extensions] - InterpolationsForwardDiffExt = "ForwardDiff" - InterpolationsUnitfulExt = "Unitful" - - [deps.Interpolations.weakdeps] - ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" - Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" - -[[deps.InvertedIndices]] -git-tree-sha1 = "6da3c4316095de0f5ee2ebd875df8721e7e0bdbe" -uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f" -version = "1.3.1" - [[deps.IrrationalConstants]] git-tree-sha1 = "b2d91fe939cae05960e760110b328288867b5758" uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" version = "0.2.6" -[[deps.IteratorInterfaceExtensions]] -git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" -uuid = "82899510-4779-5014-852e-03e436cf321d" -version = "1.0.0" - -[[deps.JLFzf]] -deps = ["REPL", "Random", "fzf_jll"] -git-tree-sha1 = "82f7acdc599b65e0f8ccd270ffa1467c21cb647b" -uuid = "1019f520-868f-41f5-a6de-eb00f4b6a39c" -version = "0.1.11" - [[deps.JLLWrappers]] deps = ["Artifacts", "Preferences"] git-tree-sha1 = "0533e564aae234aff59ab625543145446d8b6ec2" @@ -524,65 +152,6 @@ version = "1.5.0" [deps.JSON.weakdeps] ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" -[[deps.JpegTurbo_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b6893345fd6658c8e475d40155789f4860ac3b21" -uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" -version = "3.1.4+0" - -[[deps.JuliaInterpreter]] -deps = ["CodeTracking", "InteractiveUtils", "Random", "UUIDs"] -git-tree-sha1 = "58927c485919bf17ea308d9d82156de1adf4b006" -uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a" -version = "0.10.12" - -[[deps.KernelDensity]] -deps = ["Distributions", "DocStringExtensions", "FFTA", "Interpolations", "StatsBase"] -git-tree-sha1 = "4260cfc991b8885bf747801fb60dd4503250e478" -uuid = "5ab0869b-81aa-558d-bb23-cbf5423bbe9b" -version = "0.6.11" - -[[deps.LAME_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "059aabebaa7c82ccb853dd4a0ee9d17796f7e1bc" -uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d" -version = "3.100.3+0" - -[[deps.LERC_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "aaafe88dccbd957a8d82f7d05be9b69172e0cee3" -uuid = "88015f11-f218-50d7-93a8-a6af411a945d" -version = "4.0.1+0" - -[[deps.LLVMOpenMP_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "eb62a3deb62fc6d8822c0c4bef73e4412419c5d8" -uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" -version = "18.1.8+0" - -[[deps.LaTeXStrings]] -git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c" -uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" -version = "1.4.0" - -[[deps.Latexify]] -deps = ["Format", "Ghostscript_jll", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "OrderedCollections", "Requires"] -git-tree-sha1 = "44f93c47f9cd6c7e431f2f2091fcba8f01cd7e8f" -uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" -version = "0.16.10" - - [deps.Latexify.extensions] - DataFramesExt = "DataFrames" - SparseArraysExt = "SparseArrays" - SymEngineExt = "SymEngine" - TectonicExt = "tectonic_jll" - - [deps.Latexify.weakdeps] - DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" - SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - SymEngine = "123dc426-2d89-5057-bbad-38513e3affd8" - tectonic_jll = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5" - [[deps.Lazy]] deps = ["MacroTools"] git-tree-sha1 = "1370f8202dac30758f3c345f9909b97f53d87d3f" @@ -616,42 +185,6 @@ version = "1.11.0+1" [[deps.Libdl]] uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" -[[deps.Libffi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "c8da7e6a91781c41a863611c7e966098d783c57a" -uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" -version = "3.4.7+0" - -[[deps.Libglvnd_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll", "Xorg_libXext_jll"] -git-tree-sha1 = "d36c21b9e7c172a44a10484125024495e2625ac0" -uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29" -version = "1.7.1+1" - -[[deps.Libiconv_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809" -uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" -version = "1.18.0+0" - -[[deps.Libmount_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "97bbca976196f2a1eb9607131cb108c69ec3f8a6" -uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" -version = "2.41.3+0" - -[[deps.Libtiff_jll]] -deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] -git-tree-sha1 = "f04133fe05eff1667d2054c53d59f9122383fe05" -uuid = "89763e89-9b03-5906-acba-b20f662cd828" -version = "4.7.2+0" - -[[deps.Libuuid_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "d0205286d9eceadc518742860bf23f703779a3d6" -uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" -version = "2.41.3+0" - [[deps.LinearAlgebra]] deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" @@ -681,12 +214,6 @@ git-tree-sha1 = "f00544d95982ea270145636c181ceda21c4e2575" uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" version = "1.2.0" -[[deps.LoweredCodeUtils]] -deps = ["CodeTracking", "Compiler", "JuliaInterpreter"] -git-tree-sha1 = "5d4278f755440f70648d80cc6225f51e78e94094" -uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b" -version = "3.5.1" - [[deps.MacroTools]] git-tree-sha1 = "1e0228a030642014fe5cfe68c2c0a818f9e3f522" uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" @@ -707,20 +234,12 @@ deps = ["Artifacts", "Libdl"] uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" version = "2.28.1010+0" -[[deps.Measures]] -git-tree-sha1 = "b513cedd20d9c914783d8ad83d08120702bf2c77" -uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e" -version = "0.3.3" - [[deps.Missings]] deps = ["DataAPI"] git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" version = "1.2.0" -[[deps.Mmap]] -uuid = "a63ad114-7e13-5084-954f-fe012c677804" - [[deps.Mocking]] deps = ["Compat", "ExprTools"] git-tree-sha1 = "2c140d60d7cb82badf06d8783800d0bcd1a7daa2" @@ -731,63 +250,15 @@ version = "0.8.1" uuid = "14a3606d-f60d-562e-9121-12d972cd8159" version = "2025.12.2" -[[deps.MuladdMacro]] -git-tree-sha1 = "cac9cc5499c25554cba55cd3c30543cff5ca4fab" -uuid = "46d2c3a1-f734-5fdb-9937-b9b9aeba4221" -version = "0.2.4" - -[[deps.MultivariateStats]] -deps = ["Arpack", "Distributions", "LinearAlgebra", "SparseArrays", "Statistics", "StatsAPI", "StatsBase"] -git-tree-sha1 = "7c3ff68a904d0f7404e5d2f7f5bc667934d8d616" -uuid = "6f286f6a-111f-5878-ab1e-185364afe411" -version = "0.10.4" - -[[deps.NaNMath]] -deps = ["OpenLibm_jll"] -git-tree-sha1 = "9b8215b1ee9e78a293f99797cd31375471b2bcae" -uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" -version = "1.1.3" - -[[deps.NearestNeighbors]] -deps = ["AbstractTrees", "Distances", "StaticArrays"] -git-tree-sha1 = "e2c3bba08dd6dedfe17a17889131b885b8c082f0" -uuid = "b8a86587-4115-5ab1-83bc-aa920d37bbce" -version = "0.4.27" - [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" version = "1.2.0" -[[deps.Observables]] -git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225" -uuid = "510215fc-4207-5dde-b226-833fc4488ee2" -version = "0.5.5" - -[[deps.OffsetArrays]] -git-tree-sha1 = "117432e406b5c023f665fa73dc26e79ec3630151" -uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" -version = "1.17.0" -weakdeps = ["Adapt"] - - [deps.OffsetArrays.extensions] - OffsetArraysAdaptExt = "Adapt" - -[[deps.Ogg_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b6aa4566bb7ae78498a5e68943863fa8b5231b59" -uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051" -version = "1.3.6+0" - [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" version = "0.3.23+5" -[[deps.OpenLibm_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "05823500-19ac-5b8b-9628-191a04bc5112" -version = "0.8.5+0" - [[deps.OpenSSL]] deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "NetworkOptions", "OpenSSL_jll", "Sockets"] git-tree-sha1 = "1d1aaa7d449b58415f97d2839c318b70ffb525a0" @@ -796,103 +267,26 @@ version = "1.6.1" [[deps.OpenSSL_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "c9cbeda6aceffc52d8a0017e71db27c7a7c0beaf" +git-tree-sha1 = "2ac022577e5eac7da040de17776d51bb770cd895" uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "3.5.5+0" - -[[deps.OpenSpecFun_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] -git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335" -uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" -version = "0.5.6+0" - -[[deps.Opus_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e2bb57a313a74b8104064b7efd01406c0a50d2ff" -uuid = "91d4177d-7536-5919-b921-800302f37372" -version = "1.6.1+0" +version = "3.5.6+0" [[deps.OrderedCollections]] git-tree-sha1 = "05868e21324cede2207c6f0f466b4bfef6d5e7ee" uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" version = "1.8.1" -[[deps.PCRE2_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" -version = "10.42.0+1" - -[[deps.PDMats]] -deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"] -git-tree-sha1 = "e4cff168707d441cd6bf3ff7e4832bdf34278e4a" -uuid = "90014a1f-27ba-587c-ab20-58faa44d9150" -version = "0.11.37" -weakdeps = ["StatsBase"] - - [deps.PDMats.extensions] - StatsBaseExt = "StatsBase" - -[[deps.Pango_jll]] -deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "FriBidi_jll", "Glib_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl"] -git-tree-sha1 = "0662b083e11420952f2e62e17eddae7fc07d5997" -uuid = "36c8627f-9965-5494-a995-c6b170f724f3" -version = "1.57.0+0" - [[deps.Parsers]] deps = ["Dates", "PrecompileTools", "UUIDs"] git-tree-sha1 = "7d2f8f21da5db6a806faf7b9b292296da42b2810" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" version = "2.8.3" -[[deps.Pixman_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] -git-tree-sha1 = "db76b1ecd5e9715f3d043cec13b2ec93ce015d53" -uuid = "30392449-352a-5448-841d-b1acce4e97dc" -version = "0.44.2+0" - [[deps.Pkg]] deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" version = "1.10.0" -[[deps.PlotThemes]] -deps = ["PlotUtils", "Statistics"] -git-tree-sha1 = "41031ef3a1be6f5bbbf3e8073f210556daeae5ca" -uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a" -version = "3.3.0" - -[[deps.PlotUtils]] -deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Random", "Reexport", "StableRNGs", "Statistics"] -git-tree-sha1 = "26ca162858917496748aad52bb5d3be4d26a228a" -uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043" -version = "1.4.4" - -[[deps.Plots]] -deps = ["Base64", "Contour", "Dates", "Downloads", "FFMPEG", "FixedPointNumbers", "GR", "JLFzf", "JSON", "LaTeXStrings", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "Pkg", "PlotThemes", "PlotUtils", "PrecompileTools", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "RelocatableFolders", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "TOML", "UUIDs", "UnicodeFun", "Unzip"] -git-tree-sha1 = "cb20a4eacda080e517e4deb9cfb6c7c518131265" -uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" -version = "1.41.6" - - [deps.Plots.extensions] - FileIOExt = "FileIO" - GeometryBasicsExt = "GeometryBasics" - IJuliaExt = "IJulia" - ImageInTerminalExt = "ImageInTerminal" - UnitfulExt = "Unitful" - - [deps.Plots.weakdeps] - FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" - GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" - IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" - ImageInTerminal = "d8c32880-2388-543b-8c61-d9f865259254" - Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" - -[[deps.PooledArrays]] -deps = ["DataAPI", "Future"] -git-tree-sha1 = "36d8b4b899628fb92c2749eb488d884a926614d3" -uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" -version = "1.4.3" - [[deps.PrecompileTools]] deps = ["Preferences"] git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" @@ -905,75 +299,21 @@ git-tree-sha1 = "8b770b60760d4451834fe79dd483e318eee709c4" uuid = "21216c6a-2e73-6563-6e65-726566657250" version = "1.5.2" -[[deps.PrettyTables]] -deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "Tables"] -git-tree-sha1 = "624de6279ab7d94fc9f672f0068107eb6619732c" -uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" -version = "3.3.2" - - [deps.PrettyTables.extensions] - PrettyTablesTypstryExt = "Typstry" - - [deps.PrettyTables.weakdeps] - Typstry = "f0ed7684-a786-439e-b1e3-3b82803b501e" - -[[deps.Primes]] -deps = ["IntegerMathUtils"] -git-tree-sha1 = "25cdd1d20cd005b52fc12cb6be3f75faaf59bb9b" -uuid = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae" -version = "0.5.7" - [[deps.Printf]] deps = ["Unicode"] uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" +[[deps.ProgressMeter]] +deps = ["Distributed", "Printf"] +git-tree-sha1 = "fbb92c6c56b34e1a2c4c36058f68f332bec840e7" +uuid = "92933f4c-e287-5a05-a399-4b506db050ca" +version = "1.11.0" + [[deps.PtrArrays]] git-tree-sha1 = "4fbbafbc6251b883f4d2705356f3641f3652a7fe" uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d" version = "1.4.0" -[[deps.Qt6Base_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "OpenSSL_jll", "Vulkan_Loader_jll", "Xorg_libSM_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Xorg_libxcb_jll", "Xorg_xcb_util_cursor_jll", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_keysyms_jll", "Xorg_xcb_util_renderutil_jll", "Xorg_xcb_util_wm_jll", "Zlib_jll", "libinput_jll", "xkbcommon_jll"] -git-tree-sha1 = "d7a4bff94f42208ce3cf6bc8e4e7d1d663e7ee8b" -uuid = "c0090381-4147-56d7-9ebc-da0b1113ec56" -version = "6.10.2+1" - -[[deps.Qt6Declarative_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll", "Qt6ShaderTools_jll", "Qt6Svg_jll"] -git-tree-sha1 = "d5b7dd0e226774cbd87e2790e34def09245c7eab" -uuid = "629bc702-f1f5-5709-abd5-49b8460ea067" -version = "6.10.2+1" - -[[deps.Qt6ShaderTools_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll"] -git-tree-sha1 = "4d85eedf69d875982c46643f6b4f66919d7e157b" -uuid = "ce943373-25bb-56aa-8eca-768745ed7b5a" -version = "6.10.2+1" - -[[deps.Qt6Svg_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll"] -git-tree-sha1 = "81587ff5ff25a4e1115ce191e36285ede0334c9d" -uuid = "6de9746b-f93d-5813-b365-ba18ad4a9cf3" -version = "6.10.2+0" - -[[deps.Qt6Wayland_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll", "Qt6Declarative_jll"] -git-tree-sha1 = "672c938b4b4e3e0169a07a5f227029d4905456f2" -uuid = "e99dba38-086e-5de3-a5b1-6e4c66e897c3" -version = "6.10.2+1" - -[[deps.QuadGK]] -deps = ["DataStructures", "LinearAlgebra"] -git-tree-sha1 = "5e8e8b0ab68215d7a2b14b9921a946fee794749e" -uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" -version = "2.11.3" - - [deps.QuadGK.extensions] - QuadGKEnzymeExt = "Enzyme" - - [deps.QuadGK.weakdeps] - Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" - [[deps.REPL]] deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" @@ -982,67 +322,6 @@ uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" deps = ["SHA"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -[[deps.Ratios]] -deps = ["Requires"] -git-tree-sha1 = "1342a47bf3260ee108163042310d26f2be5ec90b" -uuid = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439" -version = "0.4.5" -weakdeps = ["FixedPointNumbers"] - - [deps.Ratios.extensions] - RatiosFixedPointNumbersExt = "FixedPointNumbers" - -[[deps.RecipesBase]] -deps = ["PrecompileTools"] -git-tree-sha1 = "5c3d09cc4f31f5fc6af001c250bf1278733100ff" -uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" -version = "1.3.4" - -[[deps.RecipesPipeline]] -deps = ["Dates", "NaNMath", "PlotUtils", "PrecompileTools", "RecipesBase"] -git-tree-sha1 = "45cf9fd0ca5839d06ef333c8201714e888486342" -uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c" -version = "0.6.12" - -[[deps.Reexport]] -git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" -uuid = "189a3867-3050-52da-a836-e630ba90ab69" -version = "1.2.2" - -[[deps.RelocatableFolders]] -deps = ["SHA", "Scratch"] -git-tree-sha1 = "ffdaf70d81cf6ff22c2b6e733c900c3321cab864" -uuid = "05181044-ff0b-4ac5-8273-598c1e38db00" -version = "1.0.1" - -[[deps.Requires]] -deps = ["UUIDs"] -git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64" -uuid = "ae029012-a4dd-5104-9daa-d747884805df" -version = "1.3.1" - -[[deps.Revise]] -deps = ["CodeTracking", "FileWatching", "InteractiveUtils", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "Preferences", "REPL", "UUIDs"] -git-tree-sha1 = "a44fc6ab46efc588f122ee71d6e2ca83b8645ff7" -uuid = "295af30f-e4ad-537b-8983-00126c2a3abe" -version = "3.14.1" -weakdeps = ["Distributed"] - - [deps.Revise.extensions] - DistributedExt = "Distributed" - -[[deps.Rmath]] -deps = ["Random", "Rmath_jll"] -git-tree-sha1 = "5b3d50eb374cea306873b371d3f8d3915a018f0b" -uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa" -version = "0.9.0" - -[[deps.Rmath_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "58cdd8fb2201a6267e1db87ff148dd6c1dbd8ad8" -uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f" -version = "0.5.1+0" - [[deps.SHA]] uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" version = "0.7.0" @@ -1053,25 +332,9 @@ git-tree-sha1 = "9b81b8393e50b7d4e6d0a9f14e192294d3b7c109" uuid = "6c6a2e73-6563-6170-7368-637461726353" version = "1.3.0" -[[deps.SentinelArrays]] -deps = ["Dates", "Random"] -git-tree-sha1 = "ebe7e59b37c400f694f52b58c93d26201387da70" -uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" -version = "1.4.9" - [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" -[[deps.SharedArrays]] -deps = ["Distributed", "Mmap", "Random", "Serialization"] -uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" - -[[deps.Showoff]] -deps = ["Dates", "Grisu"] -git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de" -uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f" -version = "1.0.3" - [[deps.SimpleBufferStream]] git-tree-sha1 = "f305871d2f381d21527c770d4788c06c097c9bc1" uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7" @@ -1080,6 +343,12 @@ version = "1.2.0" [[deps.Sockets]] uuid = "6462fe0b-24de-5631-8697-dd941f90decc" +[[deps.SodiumSeal]] +deps = ["Base64", "Libdl", "libsodium_jll"] +git-tree-sha1 = "80cef67d2953e33935b41c6ab0a178b9987b1c99" +uuid = "2133526b-2bfb-4018-ac12-889fb3908a75" +version = "0.1.1" + [[deps.SortingAlgorithms]] deps = ["DataStructures"] git-tree-sha1 = "64d974c2e6fdf07f8155b5b2ca2ffa9069b608d9" @@ -1091,38 +360,6 @@ deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" version = "1.10.0" -[[deps.SpecialFunctions]] -deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] -git-tree-sha1 = "2700b235561b0335d5bef7097a111dc513b8655e" -uuid = "276daf66-3868-5448-9aa4-cd146d93841b" -version = "2.7.2" -weakdeps = ["ChainRulesCore"] - - [deps.SpecialFunctions.extensions] - SpecialFunctionsChainRulesCoreExt = "ChainRulesCore" - -[[deps.StableRNGs]] -deps = ["Random"] -git-tree-sha1 = "4f96c596b8c8258cc7d3b19797854d368f243ddc" -uuid = "860ef19b-820b-49d6-a774-d7a799459cd3" -version = "1.0.4" - -[[deps.StaticArrays]] -deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] -git-tree-sha1 = "246a8bb2e6667f832eea063c3a56aef96429a3db" -uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.9.18" -weakdeps = ["ChainRulesCore", "Statistics"] - - [deps.StaticArrays.extensions] - StaticArraysChainRulesCoreExt = "ChainRulesCore" - StaticArraysStatisticsExt = "Statistics" - -[[deps.StaticArraysCore]] -git-tree-sha1 = "6ab403037779dae8c514bad259f32a447262455a" -uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" -version = "1.4.4" - [[deps.Statistics]] deps = ["LinearAlgebra", "SparseArrays"] uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" @@ -1140,37 +377,11 @@ git-tree-sha1 = "aceda6f4e598d331548e04cc6b2124a6148138e3" uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" version = "0.34.10" -[[deps.StatsFuns]] -deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"] -git-tree-sha1 = "91f091a8716a6bb38417a6e6f274602a19aaa685" -uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c" -version = "1.5.2" - - [deps.StatsFuns.extensions] - StatsFunsChainRulesCoreExt = "ChainRulesCore" - StatsFunsInverseFunctionsExt = "InverseFunctions" - - [deps.StatsFuns.weakdeps] - ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" - InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" - -[[deps.StatsPlots]] -deps = ["AbstractFFTs", "Clustering", "DataStructures", "Distributions", "Interpolations", "KernelDensity", "LinearAlgebra", "MultivariateStats", "NaNMath", "Observables", "Plots", "RecipesBase", "RecipesPipeline", "Reexport", "StatsBase", "TableOperations", "Tables", "Widgets"] -git-tree-sha1 = "88cf3587711d9ad0a55722d339a013c4c56c5bbc" -uuid = "f3b207a7-027a-5e70-b257-86293d7955fd" -version = "0.15.8" - -[[deps.StringManipulation]] -deps = ["PrecompileTools"] -git-tree-sha1 = "d05693d339e37d6ab134c5ab53c29fce5ee5d7d5" -uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e" -version = "0.4.4" - [[deps.StructUtils]] deps = ["Dates", "UUIDs"] -git-tree-sha1 = "fa95b3b097bcef5845c142ea2e085f1b2591e92c" +git-tree-sha1 = "aab80fbf866600f3299dd7f6656d80e7be177cfe" uuid = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42" -version = "2.7.1" +version = "2.7.2" [deps.StructUtils.extensions] StructUtilsMeasurementsExt = ["Measurements"] @@ -1182,10 +393,6 @@ version = "2.7.1" StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" -[[deps.SuiteSparse]] -deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"] -uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9" - [[deps.SuiteSparse_jll]] deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" @@ -1202,35 +409,11 @@ git-tree-sha1 = "72df96b3a595b7aab1e101eb07d2a435963a97e2" uuid = "dc5dba14-91b3-4cab-a142-028a31da12f7" version = "1.5.0+2025b" -[[deps.TableOperations]] -deps = ["SentinelArrays", "Tables", "Test"] -git-tree-sha1 = "e383c87cf2a1dc41fa30c093b2a19877c83e1bc1" -uuid = "ab02a1b2-a7df-11e8-156e-fb1833f50b87" -version = "1.2.0" - -[[deps.TableTraits]] -deps = ["IteratorInterfaceExtensions"] -git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" -uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" -version = "1.0.1" - -[[deps.Tables]] -deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"] -git-tree-sha1 = "f2c1efbc8f3a609aadf318094f8fc5204bdaf344" -uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" -version = "1.12.1" - [[deps.Tar]] deps = ["ArgTools", "SHA"] uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" version = "1.10.0" -[[deps.TensorCore]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "1feb45f88d133a655e001435632f019a9a1bcdb6" -uuid = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50" -version = "0.1.1" - [[deps.Test]] deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" @@ -1240,11 +423,13 @@ deps = ["Artifacts", "Dates", "Downloads", "InlineStrings", "Mocking", "Printf", git-tree-sha1 = "d422301b2a1e294e3e4214061e44f338cafe18a2" uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53" version = "1.22.2" -weakdeps = ["RecipesBase"] [deps.TimeZones.extensions] TimeZonesRecipesBaseExt = "RecipesBase" + [deps.TimeZones.weakdeps] + RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" + [[deps.TranscodingStreams]] git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742" uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" @@ -1262,303 +447,21 @@ uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [[deps.Unicode]] uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" -[[deps.UnicodeFun]] -deps = ["REPL"] -git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf" -uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1" -version = "0.4.1" - -[[deps.Unzip]] -git-tree-sha1 = "ca0969166a028236229f63514992fc073799bb78" -uuid = "41fe7b60-77ed-43a1-b4f0-825fd5a5650d" -version = "0.2.0" - -[[deps.Vulkan_Loader_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Wayland_jll", "Xorg_libX11_jll", "Xorg_libXrandr_jll", "xkbcommon_jll"] -git-tree-sha1 = "2f0486047a07670caad3a81a075d2e518acc5c59" -uuid = "a44049a8-05dd-5a78-86c9-5fde0876e88c" -version = "1.3.243+0" - -[[deps.Wayland_jll]] -deps = ["Artifacts", "EpollShim_jll", "Expat_jll", "JLLWrappers", "Libdl", "Libffi_jll"] -git-tree-sha1 = "96478df35bbc2f3e1e791bc7a3d0eeee559e60e9" -uuid = "a2964d1f-97da-50d4-b82a-358c7fce9d89" -version = "1.24.0+0" - -[[deps.WeakRefStrings]] -deps = ["DataAPI", "InlineStrings", "Parsers"] -git-tree-sha1 = "b1be2855ed9ed8eac54e5caff2afcdb442d52c23" -uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5" -version = "1.4.2" - -[[deps.WebCacheUtilities]] -deps = ["AbstractTrees", "CSV", "DataFrames", "Dates", "DelimitedFiles", "Gumbo", "HTTP", "JSON", "Measures", "Plots", "Revise", "Sockets", "StatsPlots", "Test"] -git-tree-sha1 = "4aef9004665b94c002e1140b20775a599f27f77a" -repo-rev = "master" -repo-url = "https://github.com/staticfloat/WebCacheUtilities.jl.git" -uuid = "0c1c26de-fc5f-47ff-87a8-a157289a9bac" -version = "0.1.0" - -[[deps.Widgets]] -deps = ["Colors", "Dates", "Observables", "OrderedCollections"] -git-tree-sha1 = "e9aeb174f95385de31e70bd15fa066a505ea82b9" -uuid = "cc8bc4a8-27d6-5769-a93b-9d913e69aa62" -version = "0.6.7" - -[[deps.WoodburyMatrices]] -deps = ["LinearAlgebra", "SparseArrays"] -git-tree-sha1 = "248a7031b3da79a127f14e5dc5f417e26f9f6db7" -uuid = "efce3f68-66dc-5838-9240-27a6d6f5f9b6" -version = "1.1.0" - -[[deps.WorkerUtilities]] -git-tree-sha1 = "cd1659ba0d57b71a464a29e64dbc67cfe83d54e7" -uuid = "76eceee3-57b5-4d4a-8e66-0e911cebbf60" -version = "1.6.1" - -[[deps.XZ_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "9cce64c0fdd1960b597ba7ecda2950b5ed957438" -uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800" -version = "5.8.2+0" - -[[deps.Xorg_libICE_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a3ea76ee3f4facd7a64684f9af25310825ee3668" -uuid = "f67eecfb-183a-506d-b269-f58e52b52d7c" -version = "1.1.2+0" - -[[deps.Xorg_libSM_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libICE_jll"] -git-tree-sha1 = "9c7ad99c629a44f81e7799eb05ec2746abb5d588" -uuid = "c834827a-8449-5923-a945-d239c165b7dd" -version = "1.2.6+0" - -[[deps.Xorg_libX11_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] -git-tree-sha1 = "808090ede1d41644447dd5cbafced4731c56bd2f" -uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" -version = "1.8.13+0" - -[[deps.Xorg_libXau_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "aa1261ebbac3ccc8d16558ae6799524c450ed16b" -uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" -version = "1.0.13+0" - -[[deps.Xorg_libXcursor_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXfixes_jll", "Xorg_libXrender_jll"] -git-tree-sha1 = "6c74ca84bbabc18c4547014765d194ff0b4dc9da" -uuid = "935fb764-8cf2-53bf-bb30-45bb1f8bf724" -version = "1.2.4+0" - -[[deps.Xorg_libXdmcp_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "52858d64353db33a56e13c341d7bf44cd0d7b309" -uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" -version = "1.1.6+0" - -[[deps.Xorg_libXext_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "1a4a26870bf1e5d26cd585e38038d399d7e65706" -uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" -version = "1.3.8+0" - -[[deps.Xorg_libXfixes_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "75e00946e43621e09d431d9b95818ee751e6b2ef" -uuid = "d091e8ba-531a-589c-9de9-94069b037ed8" -version = "6.0.2+0" - -[[deps.Xorg_libXi_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll", "Xorg_libXfixes_jll"] -git-tree-sha1 = "a376af5c7ae60d29825164db40787f15c80c7c54" -uuid = "a51aa0fd-4e3c-5386-b890-e753decda492" -version = "1.8.3+0" - -[[deps.Xorg_libXinerama_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll"] -git-tree-sha1 = "0ba01bc7396896a4ace8aab67db31403c71628f4" -uuid = "d1454406-59df-5ea1-beac-c340f2130bc3" -version = "1.1.7+0" - -[[deps.Xorg_libXrandr_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll", "Xorg_libXrender_jll"] -git-tree-sha1 = "6c174ef70c96c76f4c3f4d3cfbe09d018bcd1b53" -uuid = "ec84b674-ba8e-5d96-8ba1-2a689ba10484" -version = "1.5.6+0" - -[[deps.Xorg_libXrender_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "7ed9347888fac59a618302ee38216dd0379c480d" -uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" -version = "0.9.12+0" - -[[deps.Xorg_libpciaccess_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "4909eb8f1cbf6bd4b1c30dd18b2ead9019ef2fad" -uuid = "a65dc6b1-eb27-53a1-bb3e-dea574b5389e" -version = "0.18.1+0" - -[[deps.Xorg_libxcb_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXau_jll", "Xorg_libXdmcp_jll"] -git-tree-sha1 = "bfcaf7ec088eaba362093393fe11aa141fa15422" -uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" -version = "1.17.1+0" - -[[deps.Xorg_libxkbfile_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] -git-tree-sha1 = "ed756a03e95fff88d8f738ebc2849431bdd4fd1a" -uuid = "cc61e674-0454-545c-8b26-ed2c68acab7a" -version = "1.2.0+0" - -[[deps.Xorg_xcb_util_cursor_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_jll", "Xorg_xcb_util_renderutil_jll"] -git-tree-sha1 = "9750dc53819eba4e9a20be42349a6d3b86c7cdf8" -uuid = "e920d4aa-a673-5f3a-b3d7-f755a4d47c43" -version = "0.1.6+0" - -[[deps.Xorg_xcb_util_image_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "f4fc02e384b74418679983a97385644b67e1263b" -uuid = "12413925-8142-5f55-bb0e-6d7ca50bb09b" -version = "0.4.1+0" - -[[deps.Xorg_xcb_util_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll"] -git-tree-sha1 = "68da27247e7d8d8dafd1fcf0c3654ad6506f5f97" -uuid = "2def613f-5ad1-5310-b15b-b15d46f528f5" -version = "0.4.1+0" - -[[deps.Xorg_xcb_util_keysyms_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "44ec54b0e2acd408b0fb361e1e9244c60c9c3dd4" -uuid = "975044d2-76e6-5fbe-bf08-97ce7c6574c7" -version = "0.4.1+0" - -[[deps.Xorg_xcb_util_renderutil_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "5b0263b6d080716a02544c55fdff2c8d7f9a16a0" -uuid = "0d47668e-0667-5a69-a72c-f761630bfb7e" -version = "0.3.10+0" - -[[deps.Xorg_xcb_util_wm_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] -git-tree-sha1 = "f233c83cad1fa0e70b7771e0e21b061a116f2763" -uuid = "c22f9ab0-d5fe-5066-847c-f4bb1cd4e361" -version = "0.4.2+0" - -[[deps.Xorg_xkbcomp_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxkbfile_jll"] -git-tree-sha1 = "801a858fc9fb90c11ffddee1801bb06a738bda9b" -uuid = "35661453-b289-5fab-8a00-3d9160c6a3a4" -version = "1.4.7+0" - -[[deps.Xorg_xkeyboard_config_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xkbcomp_jll"] -git-tree-sha1 = "00af7ebdc563c9217ecc67776d1bbf037dbcebf4" -uuid = "33bec58e-1273-512f-9401-5d533626f822" -version = "2.44.0+0" - -[[deps.Xorg_xtrans_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "a63799ff68005991f9d9491b6e95bd3478d783cb" -uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" -version = "1.6.0+0" - [[deps.Zlib_jll]] deps = ["Libdl"] uuid = "83775a58-1f1d-513f-b197-d71354ab007a" version = "1.2.13+1" -[[deps.Zstd_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "446b23e73536f84e8037f5dce465e92275f6a308" -uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" -version = "1.5.7+1" - -[[deps.eudev_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "c3b0e6196d50eab0c5ed34021aaa0bb463489510" -uuid = "35ca27e7-8b34-5b7f-bca9-bdc33f59eb06" -version = "3.2.14+0" - -[[deps.fzf_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b6a34e0e0960190ac2a4363a1bd003504772d631" -uuid = "214eeab7-80f7-51ab-84ad-2988db7cef09" -version = "0.61.1+0" - -[[deps.libaom_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "371cc681c00a3ccc3fbc5c0fb91f58ba9bec1ecf" -uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b" -version = "3.13.1+0" - -[[deps.libass_jll]] -deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "125eedcb0a4a0bba65b657251ce1d27c8714e9d6" -uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0" -version = "0.17.4+0" - [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" version = "5.11.0+0" -[[deps.libdecor_jll]] -deps = ["Artifacts", "Dbus_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pango_jll", "Wayland_jll", "xkbcommon_jll"] -git-tree-sha1 = "9bf7903af251d2050b467f76bdbe57ce541f7f4f" -uuid = "1183f4f0-6f2a-5f1a-908b-139f9cdfea6f" -version = "0.2.2+0" - -[[deps.libdrm_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libpciaccess_jll"] -git-tree-sha1 = "63aac0bcb0b582e11bad965cef4a689905456c03" -uuid = "8e53e030-5e6c-5a89-a30b-be5b7263a166" -version = "2.4.125+1" - -[[deps.libevdev_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "56d643b57b188d30cccc25e331d416d3d358e557" -uuid = "2db6ffa8-e38f-5e21-84af-90c45d0032cc" -version = "1.13.4+0" - -[[deps.libfdk_aac_jll]] +[[deps.libsodium_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "646634dd19587a56ee2f1199563ec056c5f228df" -uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280" -version = "2.0.4+0" - -[[deps.libinput_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "eudev_jll", "libevdev_jll", "mtdev_jll"] -git-tree-sha1 = "91d05d7f4a9f67205bd6cf395e488009fe85b499" -uuid = "36db933b-70db-51c0-b978-0f229ee0e533" -version = "1.28.1+0" - -[[deps.libpng_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] -git-tree-sha1 = "e2a7072fc0cdd7949528c1455a3e5da4122e1153" -uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" -version = "1.6.56+0" - -[[deps.libva_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll", "Xorg_libXext_jll", "Xorg_libXfixes_jll", "libdrm_jll"] -git-tree-sha1 = "7dbf96baae3310fe2fa0df0ccbb3c6288d5816c9" -uuid = "9a156e7d-b971-5f62-b2c9-67348b8fb97c" -version = "2.23.0+0" - -[[deps.libvorbis_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll"] -git-tree-sha1 = "11e1772e7f3cc987e9d3de991dd4f6b2602663a5" -uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a" -version = "1.3.8+0" - -[[deps.mtdev_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "b4d631fd51f2e9cdd93724ae25b2efc198b059b1" -uuid = "009596ad-96f7-51b1-9f1b-5ce2d5e8a71e" -version = "1.1.7+0" +git-tree-sha1 = "011b0a7331b41c25524b64dc42afc9683ee89026" +uuid = "a9144af2-ca23-56d9-984f-0d03f7b5ccf8" +version = "1.0.21+0" [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] @@ -1569,21 +472,3 @@ version = "1.52.0+1" deps = ["Artifacts", "Libdl"] uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" version = "17.6.1+0" - -[[deps.x264_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "14cc7083fc6dff3cc44f2bc435ee96d06ed79aa7" -uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a" -version = "10164.0.1+0" - -[[deps.x265_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl"] -git-tree-sha1 = "e7b67590c14d487e734dcb925924c5dc43ec85f3" -uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76" -version = "4.1.0+0" - -[[deps.xkbcommon_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xkeyboard_config_jll"] -git-tree-sha1 = "a1fc6507a40bf504527d0d4067d718f8e179b2b8" -uuid = "d8fb68d0-12a3-5cfd-a85a-d49703b185fd" -version = "1.13.0+0" diff --git a/Project.toml b/Project.toml index 2776991..d986d70 100644 --- a/Project.toml +++ b/Project.toml @@ -4,17 +4,40 @@ authors = ["Elliot Saba ", "Sascha Mann file.url != url, versions_json[version].files) + return nothing +end + +function filedict_is_complete_and_good(content::OutputJsonContent, version::VersionNumber, url::URI) + filedict = find_filedict(content, version, url) + if isnothing(filedict) + return false + end + return filedict_is_complete_and_good(filedict) end +function filedict_is_complete_and_good(filedict::FileDict) + required_fields = [ + :arch, + :extension, + :kind, + :os, + :sha256, + :size, + :triplet, + :url, + :version, + + # So, as a general rule, `etag` is not actually a required key. + # But in this function, we pretend that it is required. + # Because if we didn't previously record an ETag, then we have no way of knowing now + # if the info (e.g. sha256) that we previously recorded is stale now. + :etag, + # Same goes for `last_modified` + :last_modified, + ] + optional_fields = [ + :asc, + :git_tree_sha1, + :git_tree_sha256, + :optional_field, # TODO: delete this line + ] + for k in required_fields + if isnothing(getfield(filedict, k)) + # The filedict is missing one or more required fields + return false + end + end + if !(fieldnames(typeof(filedict)) ⊆ union(required_fields, optional_fields)) + # The filedict contains a non-allowed field + # This is unexpected, so throw an error + for k in fieldnames(typeof(filedict)) + if !(k in union(required_fields, optional_fields)) + @error "Filedict contains non-allowed field: $k" + end + end + error("filedict contains a non-allowed field") + end + + ext = (filedict.extension)::ExtensionEnum.T + # git-tree-sha1 and git-tree-sha256 are required for .tar.gz files + # They are optional for other files + if ext == ExtensionEnum.tar_gz + required_for_targz = [:git_tree_sha1, :git_tree_sha256] + for k in required_for_targz + if isnothing(getfield(filedict, k)) + # The filedict is missing one or more required targz-specific keys + # TODO: Implement the "required for .tar.gz" part, by uncommenting the following line: + return false + end + end + end + + # Now the (compared to the rest of this function) expensive part: Check our Etag. + url = filedict.url + old_etag = filedict.etag + old_last_modified = filedict.last_modified + new_headinfo = get_new_headinfo_for_url(url) + if isnothing(new_headinfo) + return false + end + if old_etag != new_headinfo.etag + @warn "ETag for URL $url has changed from $(old_etag) to $(new_headinfo.etag)" + return false + end + if old_last_modified != new_headinfo.last_modified + @warn "Last-Modified for URL $url has changed from $(old_last_modified) to $(new_headinfo.last_modified)" + return false + end + + return true +end + +##### -------------------------------------------------------------------------------------- +##### Get ETag and Last-Modified, so we know if we need to re-download and re-checksum files + +Base.@kwdef struct HeadInfo + status::Int + etag::Union{String, Nothing} + last_modified::Union{String, Nothing} +end + +# We call this function before hitting an upstream server. +# By default, it is a sleep(0), but we can make this user-configurable. +# So that way, if a user is using a mirror that has rate-limits, they can configure this +# to help them stay under the limits. +function my_sleep() + sleep(0) + return nothing +end + +function get_new_headinfo_for_url(url::URI) + my_sleep() + response = HTTP.head(url; status_exception = false) + status = response.status + if status != 200 + return HeadInfo(; status, etag=nothing, last_modified=nothing) + end + headers = Dict(HTTP.headers(response)) + etag = get(headers, "ETag", "") + last_modified = get(headers, "Last-Modified", "") + if isempty(strip(etag)) + @warn "Got empty new ETag for URL: $etag" + etag = nothing + end + if isempty(strip(last_modified)) + @warn "Got empty new ETag for URL: $last_modified" + last_modified = nothing + end + head_info = HeadInfo(; status, etag, last_modified) + return head_info +end + +##### -------------------------------------------------------------------------------------- +##### Other stuff - TODO write a better description here + +function assert_sanity_check_tag_number(tag_versions::Vector{VersionNumber}) + # IIRC, we have never deleted a tag from JuliaLang/julia + # So this number should be monotonically non-decreasing over time + sanity_check_minimum = 200 # Increase this number from time to time + num_found_tags = length(tag_versions) + if num_found_tags < sanity_check_minimum + println(stderr, "Found $num_found_tags tags, here is the full list:") + for (i, x) in enumerate(tag_versions) + println(stderr, "$(i). $x") + end + error("Expected >= $sanity_check_minimum tags, but only found $num_found_tags") + end +end + +##### -------------------------------------------------------------------------------------- +##### Utility function for calculating the treehash of a tarball file + +# Example values for algorithm: +# algorithm = "git-sha1" +# algorithm = "git-sha256" function tarball_git_tree_hash(; tarball_path::AbstractString, algorithm::AbstractString) return open(io -> Tar.tree_hash(io; algorithm), `$(exe7z()) x $tarball_path -so`) end -# Get list of tags from the Julia repo +function tarball_git_tree_sha1(tarball_path::AbstractString) + return SHA1(tarball_git_tree_hash(; tarball_path, algorithm = "git-sha1")) +end + +function tarball_git_tree_sha256(tarball_path::AbstractString) + return SHA256(tarball_git_tree_hash(; tarball_path, algorithm = "git-sha256")) +end + +const tarball_git_tree_hash_skiplist = [ + # Corrupt gzip stream: `7z` reports a CRC failure for the embedded tarball. + URI("https://julialang-s3.julialang.org/bin/linux/x86/0.7/julia-0.7.0-alpha-linux-i686.tar.gz"), +] + +##### -------------------------------------------------------------------------------------- +##### Get list of tags + +# Use the GitHub API to get the list of tags from the Julia repo function get_tags() @info("Probing for tag list...") - tags_json_path = WebCacheUtilities.download_to_cache( - "julia_tags.json", - "https://api.github.com/repos/JuliaLang/julia/git/refs/tags", - ) - JSON.parse(String(read(tags_json_path))) + request_path = "repos/JuliaLang/julia/git/refs/tags" + token = strip(get(ENV, "GITHUB_TOKEN", "")) + + + + if !isempty(token) + # The user provided GITHUB_TOKEN, so we use that to authenticate to GitHub API. + # We don't cache this result - this ensures we get the freshest data. + auth = GitHub.authenticate(token) + + # Note: GitHub.jl now has rate-limit support built-in. + # So we don't call my_sleep() here. + return GitHub.gh_get_json(GitHub.DEFAULT_API, "/$request_path"; auth) + else + # The user didn't provide GITHUB_TOKEN, so we have to make an unauthenticated call + # to the GitHub API. + # The rate limit for unauthenticated calls is very low, so we cache this. + @warn "GITHUB_TOKEN not detected. It is recommended that you provide a GITHUB_TOKEN (with read-only access to only public repos)." + tags_json_path = download_to_cache( + "julia_tags.json", + "https://api.github.com/$request_path", + ) + return JSON.parse(String(read(tags_json_path))) + end end -function main(out_path) +##### -------------------------------------------------------------------------------------- +##### The main() function, which is the entrypoint into building versions.json + +function main(output_directory::AbstractString) + cfg = Config(output_directory) + return main(cfg) +end + +function main(cfg::Config) + content = OutputJsonContent(cfg) + return main!(content, cfg) +end + +# If we ever end up adding a tag to the Julia repo that's not parseable as a VersionNumber, +# we can add that tag to this skiplist here. +# +# And then we'll skip those tags when building versions.json +const tag_skiplist = String[] + +function main!(content::OutputJsonContent, cfg::Config) tags = get_tags() - tag_versions = filter(x -> x !== nothing, [vnum_maybe(basename(t["ref"])) for t in tags]) + filter!(t -> !(basename(t["ref"]) in tag_skiplist), tags) + tag_versions = filter(x -> !isnothing(x), [VersionNumber(basename(t["ref"])) for t in tags]) + unique!(tag_versions) + sort!(tag_versions) + if isempty(tag_versions) + error("Did not find any tags, which is obviously incorrect") + end + @info "Found $(length(tag_versions)) tags" + # yes I know it's not "oldest" and "newest" in the temporal sense, it's just minimum and maximum + @info "Oldest tag: $(first(tag_versions))" + @info "Newest tag: $(last(tag_versions))" + assert_sanity_check_tag_number(tag_versions) - meta = Dict() + meta = content.versions_json + internal_json = content.internal_json number_urls_tried = 0 number_urls_success = 0 + num_urls_already_complete = 0 + number_urls_already_known_nonexistent = 0 + number_urls_to_probe = length(tag_versions) * length(julia_platforms) + url_probe_index = 0 + download_progress = Progress(number_urls_to_probe; desc = "Building versions.json: ", output = stdout) + + function download_progress_showvalues(version, filename) + return [ + ("Version", version), + ("Filename", filename), + ("Tried", number_urls_tried), + ("Remaining", number_urls_to_probe - (url_probe_index - 1)), + ("Succeeded", number_urls_success), + ("Already complete", num_urls_already_complete), + ("Known nonexistent", number_urls_already_known_nonexistent), + ] + end + + function update_download_progress!(version, filename; advance::Bool = false) + showvalues = download_progress_showvalues(version, filename) + if advance + next!(download_progress; showvalues) + else + update!(download_progress, url_probe_index - 1; showvalues) + end + return nothing + end + for version in tag_versions + # Write out new versions of our versions.json every time we finish a version. + # The fact that we wait until we've finished a version is intentional. + # We don't want to write out a partial version - we want to wait until we've finished the version. + checkpoint(content, cfg) for platform in julia_platforms + url_probe_index += 1 url = download_url(version, platform) - filename = basename(url) - - # Download this URL to a local file - number_urls_tried += 1 - local filepath - try - print(stdout, "Downloading $(filename)...") - filepath = WebCacheUtilities.download_to_cache(filename, url) - catch ex - if isa(ex, InterruptException) - rethrow(ex) - end - println(stdout, " ✗") - continue + filename = basename(string(url)) + update_download_progress!(version, filename) + + if url_is_known_nonexistent(content, version, url) + number_urls_already_known_nonexistent += 1 + @debug "Skipping $filename as known-bad" + update_download_progress!(version, filename; advance = true) + continue # skip the rest of this for-loop iteration end - number_urls_success += 1 - println(stdout, " ✓") - - if endswith(filename, ".dmg") - kind = "archive" - extension = "dmg" - elseif endswith(filename, ".exe") - kind = "installer" - extension = "exe" - elseif endswith(filename, ".tar.gz") - kind = "archive" - extension = "tar.gz" - elseif endswith(filename, ".zip") - kind = "archive" - extension = "zip" + + if filedict_is_complete_and_good(content, version, url) + num_urls_already_complete += 1 + @debug "Skipping $filename as known-complete-filedict" else - error("Unsupported file extension in filename: $(filename)") - end + # If a stale filedict exists, we need to remove it + # Otherwise, we end up with duplicate entries in the `files` list + delete_filedicts_for_url!(content, version, url) - tarball_hash_path = hit_file_cache("$(filename).sha256") do tarball_hash_path - open(filepath, "r") do io - open(tarball_hash_path, "w") do hash_io - write(hash_io, bytes2hex(sha256(io))) - end + headinfo = get_new_headinfo_for_url(url) + if headinfo.status == 404 + mark_url_as_nonexistent!(content, version, url) + update_download_progress!(version, filename; advance = true) + continue # skip the rest of this for-loop iteration end - end - tarball_hash = String(read(tarball_hash_path)) - if extension == "tar.gz" && !(url in tarball_git_tree_hash_skiplist) - tarball_git_tree_hashes = Dict{String, String}() - tree_hash_path_sha1 = hit_file_cache("$(filename).git-tree-sha1") do tree_hash_path - open(tree_hash_path, "w") do hash_io - write(hash_io, tarball_git_tree_hash(; tarball_path=filepath, algorithm="git-sha1")) - end + # Download this URL to a local file in a temp directory + number_urls_tried += 1 + + # This will download the file to a tempfile + my_sleep() + filepath = Downloads.download(string(url)) + + if filesize(filepath) == 0 + # The file is empty + rm(filepath; force = true) + update_download_progress!(version, filename; advance = true) + continue # skip the rest of this for-loop iteration end - tree_hash_path_sha256 = hit_file_cache("$(filename).git-tree-sha256") do tree_hash_path - open(tree_hash_path, "w") do hash_io - write(hash_io, tarball_git_tree_hash(; tarball_path=filepath, algorithm="git-sha256")) - end + + number_urls_success += 1 + + tarball_hash = open(filepath, "r") do io + bytes2hex(sha256(io)) end - tarball_git_tree_hashes["git-tree-sha1"] = String(read(tree_hash_path_sha1)) - tarball_git_tree_hashes["git-tree-sha256"] = String(read(tree_hash_path_sha256)) - else - tarball_git_tree_hashes = nothing - end - # Initialize overall version key, if needed - if !haskey(meta, version) - meta[version] = Dict( - "stable" => is_stable(version), - "files" => Vector{Dict}(), - ) - end + # Initialize overall version key, if needed + if !haskey(meta, version) + meta[version] = SingleVersionInfo(; stable = is_stable(version)) + end - # Test to see if there is an asc signature: - asc_signature = nothing - if !isa(platform, MacOS) && !isa(platform, Windows) - asc_url = string(url, ".asc") - print(stdout, " Downloading $(basename(asc_url))") - try - asc_filepath = WebCacheUtilities.download_to_cache(basename(asc_url), asc_url) - asc_signature = String(read(asc_filepath)) - println(stdout, " ✓") - catch ex - if isa(ex, InterruptException) - rethrow(ex) - end - println(stdout, " ✗") + if endswith(filename, ".dmg") + kind = "archive" + extension = "dmg" + elseif endswith(filename, ".exe") + kind = "installer" + extension = "exe" + elseif endswith(filename, ".tar.gz") + kind = "archive" + extension = "tar.gz" + elseif endswith(filename, ".zip") + kind = "archive" + extension = "zip" + else + error("Unsupported file extension in filename: filename") end - end + if extension == "tar.gz" && !(url in tarball_git_tree_hash_skiplist) + tree_hash_path_sha1 = tarball_git_tree_sha1(filepath) + tree_hash_path_sha256 = tarball_git_tree_sha256(filepath) + else + tree_hash_path_sha1 = nothing + tree_hash_path_sha256 = nothing + end - # Build up metadata about this file - file_dict = Dict( - "triplet" => triplet(platform), - "os" => meta_os(platform), - "arch" => string(arch(platform)), - "version" => string(version), - "sha256" => tarball_hash, - "size" => filesize(filepath), - "kind" => kind, - "extension" => extension, - "url" => url, - ) - if tarball_git_tree_hashes !== nothing - merge!(file_dict, tarball_git_tree_hashes) + # Build up file_dict, which contains the metadata about this file + file_dict = Dict() + file_dict["arch"] = ArchEnum.T(arch(platform)) + file_dict["triplet"] = unwrap_platform(platform) + file_dict["os"] = OsEnum.T(meta_os(platform)) + file_dict["version"] = version + file_dict["sha256"] = tarball_hash + file_dict["size"] = filesize(filepath) + file_dict["kind"] = KindEnum.T(kind) + file_dict["extension"] = ExtensionEnum.T(extension) + file_dict["url"] = url + + if !isnothing(headinfo.etag) + file_dict["etag"] = headinfo.etag + end + if !isnothing(headinfo.last_modified) + file_dict["last-modified"] = headinfo.last_modified + end + + if !isnothing(tree_hash_path_sha1) + file_dict["git-tree-sha1"] = tree_hash_path_sha1 + end + if !isnothing(tree_hash_path_sha256) + file_dict["git-tree-sha256"] = string(tree_hash_path_sha256) # TODO: remove string() + end + + # Let's be forward-thinking: Make this an array of dictionaries that is + # easy to extensibly match. + push!(meta[version].files, FileDict(file_dict)) + + # Delete the downloaded file + rm(filepath; force = true) end - # Add in `.asc` signature content, if applicable - if asc_signature !== nothing - file_dict["asc"] = asc_signature + + file_dict = find_filedict(content, version, url) + if isnothing(file_dict) + error("A very unexpected error occured: file_dict is nothing") end - # Right now, all we have are archives, but let's be forward-thinking - # and make this an array of dictionaries that is easy to extensibly match - push!(meta[version]["files"], file_dict) + if !isnothing(file_dict.asc) + # Nothing to do in this branch. No need to re-download the asc signature file + # if the file_dict already has the `asc` key. + else + # Test to see if there is an asc signature file available for download + asc_signature = nothing + if !isa(platform, MacOS) && !isa(platform, Windows) + asc_url = URI(string(url, ".asc")) + asc_filename = basename(string(asc_url)) + @debug "Downloading $asc_filename" + try + my_sleep() + asc_filepath = Downloads.download(string(asc_url)) + asc_signature = String(read(asc_filepath)) + rm(asc_filepath) + catch ex + if (ex isa Downloads.RequestError) && (ex.response.status == 404) + mark_url_as_nonexistent!(content, version, url) + update_download_progress!(version, filename; advance = true) + continue # skip the rest of this for-loop iteration + else + rethrow() + end + end + end - # Write out new versions of our versions.json as we go - open(out_path, "w") do io - JSON.print(io, meta, 2) + # Add in `.asc` signature content, if applicable + if !isnothing(asc_signature) + file_dict.asc = asc_signature + end end - - # Delete downloaded file - rm(filepath) + update_download_progress!(version, filename; advance = true) end - end - @info "Tried $(number_urls_tried) versions, successfully downloaded $(number_urls_success)" + end # for version in tag_versions + + # One last checkpoint, now that we've finished the for-loop + checkpoint(content, cfg) + + @info "Tried $(number_urls_tried) versions, successfully downloaded $(number_urls_success). Skipped $num_urls_already_complete already good. Skipped $(number_urls_already_known_nonexistent) known bad." + + return_value = (; + cfg.versions_json_filename, + ) + return cfg end end # module diff --git a/src/sha_structs.jl b/src/sha_structs.jl new file mode 100644 index 0000000..0c8a5e4 --- /dev/null +++ b/src/sha_structs.jl @@ -0,0 +1,36 @@ +const SHA1 = Base.SHA1 + +@static if isdefined(Base, :SHA256) + const SHA256 = Base.SHA256 +else + # const SHA256 = Compat.SHA256 + + struct SHA256 + bytes::NTuple{32, UInt8} + end + function SHA256(bytes::Vector{UInt8}) + length(bytes) == 32 || + throw(ArgumentError("wrong number of bytes for SHA1: Expected 32 bytes, got $(length(bytes))")) + return SHA256(ntuple(i->bytes[i], Val(32))) + end + SHA256(s::AbstractString) = SHA256(hex2bytes(s)) + Base.parse(::Type{SHA256}, s::AbstractString) = SHA256(s) + function Base.tryparse(::Type{SHA256}, s::AbstractString) + try + return parse(SHA256, s) + catch e + if isa(e, ArgumentError) + return nothing + end + rethrow(e) + end + end + + Base.string(hash::SHA256) = bytes2hex(hash.bytes) + Base.print(io::IO, hash::SHA256) = bytes2hex(io, hash.bytes) + Base.show(io::IO, hash::SHA256) = print(io, SHA256str * "(\"", hash, "\")") + + Base.isless(a::SHA256, b::SHA256) = isless(a.bytes, b.bytes) + Base.hash(a::SHA256, h::UInt) = hash((SHA256, a.bytes), h) + Base.:(==)(a::SHA256, b::SHA256) = a.bytes == b.bytes +end diff --git a/src/typed_json.jl b/src/typed_json.jl new file mode 100644 index 0000000..03b7e6d --- /dev/null +++ b/src/typed_json.jl @@ -0,0 +1,171 @@ +using JSON: JSON + +struct OurCustomStyle <: JSON.JSONStyle +end + +### versions.json: + +@enumx ArchEnum aarch64 armv7l i686 powerpc64le x86_64 +function ArchEnum.T(arch::Symbol)::ArchEnum.T + if arch == :aarch64 + return ArchEnum.aarch64 + elseif arch == :armv7l + return ArchEnum.armv7l + elseif arch == :i686 + return ArchEnum.i686 + elseif arch == :powerpc64le + return ArchEnum.powerpc64le + elseif arch == :x86_64 + return ArchEnum.x86_64 + else + error("Unknown arch: $arch") + end +end + +@enumx ExtensionEnum dmg exe tar_gz zip +function ExtensionEnum.T(ext::String)::ExtensionEnum.T + if ext == "dmg" + return ExtensionEnum.dmg + elseif ext == "exe" + return ExtensionEnum.exe + elseif ext == "tar.gz" + return ExtensionEnum.tar_gz + elseif ext == "zip" + return ExtensionEnum.zip + else + error("Unknown extension: $ext") + end +end +function StructUtils.lift(style::OurCustomStyle, ::Type{ExtensionEnum.T}, ext::String) + return ExtensionEnum.T(ext), StructUtils.defaultstate(style) +end +function StructUtils.lower(::OurCustomStyle, ext::ExtensionEnum.T)::String + if ext == ExtensionEnum.dmg + return "dmg" + elseif ext == ExtensionEnum.exe + return "exe" + elseif ext == ExtensionEnum.tar_gz + return "tar.gz" + elseif ext == ExtensionEnum.zip + return "zip" + else + error("Unknown extension: $ext") + end +end + +@enumx KindEnum archive installer +function KindEnum.T(kind::String)::KindEnum.T + if kind == "archive" + return KindEnum.archive + elseif kind == "installer" + return KindEnum.installer + else + error("Unnown kind: $kind") + end +end + +@enumx OsEnum freebsd linux mac winnt +function OsEnum.T(os::String)::OsEnum.T + if os == "freebsd" + return OsEnum.freebsd + elseif os == "linux" + return OsEnum.linux + elseif os == "mac" + return OsEnum.mac + elseif os == "winnt" + return OsEnum.winnt + else + error("Unknown OS: $os") + end +end + +StructUtils.structlike(::OurCustomStyle, ::Type{Platform}) = false +function StructUtils.lift(style::OurCustomStyle, ::Type{Platform}, triplet_str::String) + return parse(Platform, triplet_str), StructUtils.defaultstate(style) +end +function StructUtils.lower(::OurCustomStyle, plat::Platform)::String + return triplet(plat) +end + +StructUtils.structlike(::OurCustomStyle, ::Type{URI}) = false +function StructUtils.lift(style::OurCustomStyle, ::Type{URI}, url::String) + return URI(url), StructUtils.defaultstate(style) +end +function StructUtils.lower(::OurCustomStyle, url::URI)::String + return string(url) +end + +StructUtils.structlike(::OurCustomStyle, ::Type{SHA1}) = false +function StructUtils.lift(style::OurCustomStyle, ::Type{SHA1}, hash::String) + return SHA1(hash), StructUtils.defaultstate(style) +end +function StructUtils.lower(::OurCustomStyle, hash::SHA1)::String + return string(hash) +end + +@kwarg mutable struct FileDict + # The order of fields in this struct is important. + # JSON.jl will serialize the JSON in the exact same order as the order of fields here. + # We choose to order the fields as follows: + # - Required fields first, then optional fields. + # - Within each group, order alphabetically. + # + # Note: The order of fields is not part of our stability guarantees. We are allowed to + # change the order of fields in the future. + + # Required fields: + arch::ArchEnum.T + extension::ExtensionEnum.T + kind::KindEnum.T + os::OsEnum.T + sha256::String + size::Int + triplet::Platform + url::URI + version::VersionNumber + + # Optional fields: + asc::Union{String, Nothing} = nothing + etag::Union{String, Nothing} = nothing + git_tree_sha1::Union{SHA1, Nothing} = nothing &(json=(name="git-tree-sha1",),) + git_tree_sha256::Union{String, Nothing} = nothing &(json=(name="git-tree-sha256",),) # TODO: Use SHA256 (instead of String) + last_modified::Union{String, Nothing} = nothing &(json=(name="last-modified",),) +end +function FileDict(dict::Dict)::FileDict + file = FileDict(; + # Required fields: + arch = dict["arch"], + extension = dict["extension"], + kind = dict["kind"], + os = dict["os"], + sha256 = dict["sha256"], + size = dict["size"], + triplet = dict["triplet"], + url = URI(dict["url"]), + version = dict["version"], + + # Optional fields: + asc = get(dict, "asc", nothing), + etag = get(dict, "etag", nothing), + git_tree_sha1 = get(dict, "git-tree-sha1", nothing), + git_tree_sha256 = get(dict, "git-tree-sha256", nothing), + last_modified = get(dict, "last-modified", nothing), + ) + return file +end + +Base.@kwdef struct SingleVersionInfo + stable::Bool + files::Vector{FileDict} = [] +end + +const VersionsJsonDocument = Dict{VersionNumber, SingleVersionInfo} + +### internal.json: + +struct InternalJsonSingleVersion + known_nonexistent_urls::Vector{URI} +end +InternalJsonSingleVersion() = InternalJsonSingleVersion([]) + +const InternalJsonDocument = Dict{VersionNumber, InternalJsonSingleVersion} diff --git a/src/web_cache_utilities.jl b/src/web_cache_utilities.jl new file mode 100644 index 0000000..306af34 --- /dev/null +++ b/src/web_cache_utilities.jl @@ -0,0 +1,60 @@ +using Dates: Dates, now, UTC, unix2datetime, TimePeriod, Hour +using URIs: URI + +function download_to_cache(filename::String, url::Union{String, URI}) + my_sleep() + file_path = _hit_file_cache(filename) do file_path + r = open(io -> HTTP.get(url; response_stream=io), file_path, "w") + if r.status != 200 + error("Unable to download $(url) to $(filename)") + end + end + return file_path +end + +function _hit_file_cache(creator::Function, filename::String, lifetime::TimePeriod = Hour(24)) + cache_dir = abspath(joinpath(@__DIR__, "..", "cache")) + if !isdir(cache_dir) + mkpath(cache_dir) + end + + cache_path = joinpath(cache_dir, filename) + if isfile(cache_path) + age = now(UTC) - unix2datetime(stat(cache_path).mtime) + if age < lifetime + @debug "Cache hit" filename isfile(cache_path) age Dates.canonicalize(age) lifetime + return abspath(cache_path) + else + @debug "Cache miss. Cache entry found, but stale (too old)" filename isfile(cache_path) age Dates.canonicalize(age) lifetime + end + else + @debug "Cache miss. Cache entry not found" filename isfile(cache_path) + end + + rm(cache_path; force=true) + mktempdir() do tmpdir + tmp_path = joinpath(tmpdir, filename) + + creator(tmp_path) + + if !isfile(tmp_path) + error("Creator function didn't actually create file: $filename") + end + try + mv(tmp_path, cache_path; force = true) + catch + rm(cache_path; force=true) + rethrow() + end + end + + if !isfile(cache_path) + error("Our mv must have somehow failed for file: $filename") + end + # Update the mtime: + touch(cache_path) + + @debug "Successfully saved new cache entry" filename + + return abspath(cache_path) +end diff --git a/test/.tool-versions b/test/.tool-versions new file mode 100644 index 0000000..67a6731 --- /dev/null +++ b/test/.tool-versions @@ -0,0 +1 @@ +nodejs 24.15.0 diff --git a/test/node/package-lock.json b/test/node/package-lock.json new file mode 100644 index 0000000..5266f3f --- /dev/null +++ b/test/node/package-lock.json @@ -0,0 +1,940 @@ +{ + "name": "tests-check-schema", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tests-check-schema", + "dependencies": { + "ajv-cli": "^3.3.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-cli": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/ajv-cli/-/ajv-cli-3.3.0.tgz", + "integrity": "sha512-4eiNNjDlainn5Rm+rU3egxLUUxzevcB+gviBLs9sm6etHNrE7l2JSQ3yoz5hE+eqrth3pTygELOafhSIO8Hiig==", + "license": "MIT", + "dependencies": { + "ajv": "^6.7.0", + "ajv-pack": "^0.3.0", + "fast-json-patch": "^2.0.0", + "glob": "^7.1.0", + "js-yaml": "^3.13.1", + "json-schema-migrate": "^0.2.0", + "json5": "^2.1.3", + "minimist": "^1.2.0" + }, + "bin": { + "ajv": "index.js" + } + }, + "node_modules/ajv-pack": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/ajv-pack/-/ajv-pack-0.3.1.tgz", + "integrity": "sha512-psFkqg+ItqBXjQ0kbdP/Y72Jmz+wHt8MD7bVGdzdxjKsp988QTK5YMQoBsPUotbhnYO8VKPU3vPALYlhO/2gtg==", + "license": "MIT", + "dependencies": { + "js-beautify": "^1.6.4", + "require-from-string": "^1.2.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/editorconfig": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.7.tgz", + "integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==", + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "^9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-patch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-2.2.1.tgz", + "integrity": "sha512-4j5uBaTnsYAV5ebkidvxiLUYOwjQ+JSFljeqfTxCrH9bDmlCQaOJFS84oDJ2rAXZq2yskmk3ORfoP9DCwqFNig==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^2.0.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fast-json-patch/node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-beautify/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/js-beautify/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-migrate": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-0.2.0.tgz", + "integrity": "sha512-dq4/oHWmtw/+0ytnXsDqVn+VsVweTEmzm5jLgguPn9BjSzn6/q58ZiZx3BHiQyJs612f0T5Z+MrUEUUY5DHsRg==", + "license": "MIT", + "dependencies": { + "ajv": "^5.0.0" + } + }, + "node_modules/json-schema-migrate/node_modules/ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha512-Ajr4IcMXq/2QmMkEmSvxqfLN5zGmJ92gHXAeOXq1OekoH2rfDNsgdDoL2f7QaRCy7G/E6TpxBVdRuNraMztGHw==", + "license": "MIT", + "dependencies": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "node_modules/json-schema-migrate/node_modules/fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha512-fueX787WZKCV0Is4/T2cyAdM4+x1S3MXXOAhavE1ys/W42SHAPacLTQhucja22QBYrfGw50M2sRiXPtTGv9Ymw==", + "license": "MIT" + }, + "node_modules/json-schema-migrate/node_modules/json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha512-4JD/Ivzg7PoW8NzdrBSr3UFwC9mHgvI7Z6z3QGBsSHgKaRTUDmyZAAKJo2UbG1kUVfS9WS8bi36N49U1xw43DA==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "license": "ISC" + }, + "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": "1.2.1", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-1.2.1.tgz", + "integrity": "sha512-H7AkJWMobeskkttHyhTVtS0fxpFLjxhbfMa6Bk3wimP7sdPRGL3EyCg3sAQenFfAe+xQ+oAc85Nmtvq0ROM83Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/test/node/package.json b/test/node/package.json new file mode 100644 index 0000000..726e024 --- /dev/null +++ b/test/node/package.json @@ -0,0 +1,7 @@ +{ + "name": "tests-check-schema", + "description": "This is a Node project for checking the versions.json against the schema.", + "dependencies": { + "ajv-cli": "^3.3.0" + } +} diff --git a/test/more_tests.jl b/test/post_build.jl similarity index 55% rename from test/more_tests.jl rename to test/post_build.jl index 82d4ff4..7927b16 100644 --- a/test/more_tests.jl +++ b/test/post_build.jl @@ -1,19 +1,52 @@ -import JSON -import Test - -using Test: @testset, @test +using JSON: JSON +using StatsBase: StatsBase +using Test: Test, @testset, @test, @test_skip +using URIs: URIs, URI + +# We intentionally don't load VersionsJSONUtils in these tests. +# This is because we want these tests to be as independent from VersionsJSONUtils as possible. + +function check_usage() + scriptname = basename(@__FILE__) + if length(ARGS) != 2 + usage_msg = "Usage: julia $scriptname \$filename \$test_type" + println(stderr, usage_msg) + println(stderr, "\$filename should be the filename of the versions.json file") + println(stderr, "\$test_type should be either partial or full") + println(stderr, "") + error(usage_msg) + end + return nothing +end -const filename = only(ARGS) +function get_cli_args() + check_usage() + filename = strip(ARGS[1]) + if !isfile(filename) + error("File does not exist: $filename") + end + test_type_str = strip(ARGS[2]) + if test_type_str == "partial" + test_type = :partial + elseif test_type_str == "full" + test_type = :full + else + error("Invalid value for test_type: $test_type. Valid values are: partial, full") + end + return (; filename, test_type) +end -const dict = JSON.parsefile(filename) +const tarball_git_tree_hash_skiplist = [ + # Corrupt gzip stream: `7z` reports a CRC failure for the embedded tarball. + URI("https://julialang-s3.julialang.org/bin/linux/x86/0.7/julia-0.7.0-alpha-linux-i686.tar.gz"), +] -# This catches regressions like https://github.com/JuliaLang/VersionsJSONUtil.jl/pull/32#issuecomment-2617551478 -for (k_str, _) in pairs(dict) - k_ver = VersionNumber(k_str) - @test k_ver isa VersionNumber -end +check_usage() @testset "Post-build tests" begin + opts = get_cli_args() + dict = JSON.parsefile(opts.filename) + # This is used in the "Make sure we found at least X files" testset below total_files_all_julia_versions = 0 @@ -23,6 +56,7 @@ end for (ver_str, ver_dict) in pairs(dict) ver = VersionNumber(ver_str) + # This catches regressions like https://github.com/JuliaLang/VersionsJSONUtil.jl/pull/32#issuecomment-2617551478 @test ver isa VersionNumber @test ver_dict isa AbstractDict @@ -48,6 +82,7 @@ end # This will be used in the "Tier 1" testset below found_platforms = [] + found_urls = [] @testset "Iterate over filedicts in the filedicts_array" begin for filedict in filedicts_array @@ -64,19 +99,64 @@ end "triplet", "url", "version", + + # etag and last-modified are technically optional. + # Because in theory we don't know what the upstream is, and if it + # supports these headers. + # But in practice, we know that the upstream is S3 + # and we know that it supports these two headers. + # So for the purpose of these tests, we treat these as required + "etag", + "last-modified", ] optional_keys = [ "asc", + ] + + # Technically, git-tree-sha1 and git-tree-sha256 are optional keys. + # However, for the purposes of these tests, we treat them as required + # for tar.gz files, and optional for other files. + @test haskey(filedict, "extension") + ext = filedict["extension"] + treehash_keys = [ "git-tree-sha1", "git-tree-sha256", ] + file_url = URI(filedict["url"]) + if ext == "tar.gz" && !(file_url in tarball_git_tree_hash_skiplist) + append!(required_keys, treehash_keys) + else + append!(optional_keys, treehash_keys) + end + allowed_keys = union(required_keys, optional_keys) - @test required_keys ⊆ collect(keys(filedict)) - @test collect(keys(filedict)) ⊆ allowed_keys + + observed_keys_this_filedict = collect(keys(filedict)) + + if !(required_keys ⊆ observed_keys_this_filedict) + for k in required_keys + if !(k in observed_keys_this_filedict) + @info "Missing required key: $k" + end + end + end + if !(observed_keys_this_filedict ⊆ allowed_keys) + for k in observed_keys_this_filedict + if !(k in allowed_keys) + @info "Key is present, but it shouldn't be: $k" + end + end + end + + @test required_keys ⊆ observed_keys_this_filedict + @test observed_keys_this_filedict ⊆ allowed_keys # This will be used in the "Tier 1" testset below push!(found_platforms, (filedict["triplet"], filedict["extension"])) + # This will be used in the "No duplicate URLs" testset below + push!(found_urls, filedict["url"]) + @testset "arch field" begin allowed_arches = [ "x86_64", @@ -118,12 +198,14 @@ end end @testset "sha256 field" begin @test filedict["sha256"] isa AbstractString - @test occursin(r"^[a-z0-9]*?$", filedict["sha256"]) + @test occursin(r"^[0-9a-f]*?$", filedict["sha256"]) @test length(filedict["sha256"]) == 64 + bytes = hex2bytes(filedict["sha256"]) + @test length(bytes) == 32 end @testset "size field" begin @test filedict["size"] isa Integer - @test filedict["size"] >= 0 # TODO: Make this strictly > + @test filedict["size"] > 0 end @testset "triplet field" begin allowed_triplets = [ @@ -152,10 +234,13 @@ end @testset "url field" begin @test filedict["url"] isa AbstractString @test !isempty(strip(filedict["url"])) - # TODO: Parse the url as a URIs.URI and do some checks on it - # (1) Make sure it parses validly - # (2) Make sure it is HTTP - # (3) Make sure the domain is julialang-s3.julialang.org + url_obj = URI(filedict["url"]) + @test url_obj isa URI + @test url_obj.scheme == "https" + allowed_hosts = [ + "julialang-s3.julialang.org", + ] + @test url_obj.host in allowed_hosts end @testset "version field" begin @test VersionNumber(filedict["version"]) isa VersionNumber @@ -169,6 +254,31 @@ end @test endswith(chomp(filedict["asc"]), "-----END PGP SIGNATURE-----") end end + @testset "etag field (optional)" begin + if haskey(filedict, "etag") + etag = lowercase(strip(filedict["etag"])) + @test !isempty(etag) + @test etag != "null" + @test etag != "nothing" + + # Guaranteed to be ASCII, per spec + # > Entity tag that uniquely represents the requested resource. + # It is a string of ASCII characters placed between double quotes + # Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag + @test isascii(etag) + end + end + @testset "last-modified field (optional)" begin + if haskey(filedict, "last-modified") + @test !isempty(strip(filedict["last-modified"])) + last_modified = lowercase(strip(filedict["last-modified"])) + @test !isempty(last_modified) + @test last_modified != "null" + @test last_modified != "nothing" + + @test isascii(last_modified) + end + end end # for filedict in filedicts_array end # testset "Iterate over filedicts in the filedicts_array" @@ -220,23 +330,37 @@ end end end + @testset "No duplicate URLs" begin + for (url, count) in StatsBase.countmap(found_urls) + if count != 1 + @info "URL $url appeared $count times" + end + @test count == 1 + end + end end end - @testset "Make sure we found at least X files" begin - @test total_files_all_julia_versions >= 2190 # increase this value over time - end + @testset "Testset to make sure we found at least N versions and files" begin + skip_these_tests = opts.test_type == :partial - @testset "Make sure we found at least N Julia versions" begin - julia_versions_str = collect(keys(dict)) - julia_versions = VersionNumber.(julia_versions_str) - unique!(julia_versions) - @test length(julia_versions) >= 193 # increase this value over time + @testset "Make sure we found at least N files" begin + # Increase this value over time: + @test total_files_all_julia_versions >= 2190 skip=skip_these_tests + end - julia_versions_v1 = filter(x -> x.major == 1, julia_versions) - @test length(julia_versions_v1) >= 140 # increase this value over time + @testset "Make sure we found at least N Julia versions" begin + julia_versions_str = collect(keys(dict)) + julia_versions = VersionNumber.(julia_versions_str) + unique!(julia_versions) + julia_versions_v1 = filter(x -> x.major == 1, julia_versions) + julia_stable_versions_v1 = filter(x -> x.prerelease == (), julia_versions_v1) + + # Increase these values over time: + @test length(julia_versions) >= 193 skip=skip_these_tests + @test length(julia_versions_v1) >= 140 skip=skip_these_tests + @test length(julia_stable_versions_v1) >= 71 skip=skip_these_tests + end - julia_stable_versions_v1 = filter(x -> x.prerelease == (), julia_versions_v1) - @test length(julia_stable_versions_v1) >= 71 # increase this value over time end end diff --git a/test/runtests.jl b/test/runtests.jl index bf3186e..6144b3e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,7 +1,9 @@ -using Pkg.BinaryPlatforms, JSON -using VersionsJSONUtil -import VersionsJSONUtil: WindowsPortable, WindowsTarball, MacOSTarball -using Test +using JSON: JSON +using Pkg: Pkg +using Test: Test, @testset, @test +using Pkg.BinaryPlatforms: Linux, MacOS, Windows, FreeBSD +using URIs: URIs, URI +using VersionsJSONUtil: VersionsJSONUtil, WindowsPortable, WindowsTarball, MacOSTarball const download_urls = Dict( v"1.7.0-beta3" => Dict( @@ -29,7 +31,7 @@ const download_urls = Dict( @testset "VersionsJSONUtil.jl" begin @testset "Download URLs for $v" for v in keys(download_urls) for (p, url) in download_urls[v] - @test VersionsJSONUtil.download_url(v, p) == url + @test VersionsJSONUtil.download_url(v, p) == URI(url) end end end