this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

ts v1

alice 3026b6dc 57e56da6

+2543 -2
+2 -1
.gitignore
··· 1 1 build/ 2 2 .lock* 3 3 .clangd 4 - scripts/__pycache__/ 4 + __pycache__ 5 + node_modules
+730
scripts/bun.lock
··· 1 + { 2 + "lockfileVersion": 1, 3 + "workspaces": { 4 + "": { 5 + "name": "tidface-scripts", 6 + "dependencies": { 7 + "@types/commander": "^2.12.5", 8 + "@vvo/tzdb": "^6", 9 + "airport-data": "^1.0.1", 10 + "cheerio": "^1", 11 + "commander": "^13.1.0", 12 + "csv-parse": "^5", 13 + "geo-tz": "^8", 14 + "luxon": "^3", 15 + }, 16 + "devDependencies": { 17 + "@types/jest": "^29", 18 + "@types/luxon": "^3", 19 + "@types/node": "^22", 20 + "jest": "^29", 21 + "ts-jest": "^29", 22 + "ts-node": "^10", 23 + "typescript": "^5", 24 + }, 25 + }, 26 + }, 27 + "packages": { 28 + "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], 29 + 30 + "@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="], 31 + 32 + "@babel/compat-data": ["@babel/compat-data@7.26.8", "", {}, "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ=="], 33 + 34 + "@babel/core": ["@babel/core@7.26.10", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.10", "@babel/helper-compilation-targets": "^7.26.5", "@babel/helper-module-transforms": "^7.26.0", "@babel/helpers": "^7.26.10", "@babel/parser": "^7.26.10", "@babel/template": "^7.26.9", "@babel/traverse": "^7.26.10", "@babel/types": "^7.26.10", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ=="], 35 + 36 + "@babel/generator": ["@babel/generator@7.27.0", "", { "dependencies": { "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw=="], 37 + 38 + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.0", "", { "dependencies": { "@babel/compat-data": "^7.26.8", "@babel/helper-validator-option": "^7.25.9", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA=="], 39 + 40 + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.25.9", "", { "dependencies": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw=="], 41 + 42 + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.26.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9", "@babel/traverse": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw=="], 43 + 44 + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.26.5", "", {}, "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg=="], 45 + 46 + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.25.9", "", {}, "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="], 47 + 48 + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.25.9", "", {}, "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="], 49 + 50 + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.25.9", "", {}, "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw=="], 51 + 52 + "@babel/helpers": ["@babel/helpers@7.27.0", "", { "dependencies": { "@babel/template": "^7.27.0", "@babel/types": "^7.27.0" } }, "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg=="], 53 + 54 + "@babel/parser": ["@babel/parser@7.27.0", "", { "dependencies": { "@babel/types": "^7.27.0" }, "bin": "./bin/babel-parser.js" }, "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg=="], 55 + 56 + "@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="], 57 + 58 + "@babel/plugin-syntax-bigint": ["@babel/plugin-syntax-bigint@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg=="], 59 + 60 + "@babel/plugin-syntax-class-properties": ["@babel/plugin-syntax-class-properties@7.12.13", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA=="], 61 + 62 + "@babel/plugin-syntax-class-static-block": ["@babel/plugin-syntax-class-static-block@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw=="], 63 + 64 + "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.26.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A=="], 65 + 66 + "@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="], 67 + 68 + "@babel/plugin-syntax-json-strings": ["@babel/plugin-syntax-json-strings@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA=="], 69 + 70 + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA=="], 71 + 72 + "@babel/plugin-syntax-logical-assignment-operators": ["@babel/plugin-syntax-logical-assignment-operators@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig=="], 73 + 74 + "@babel/plugin-syntax-nullish-coalescing-operator": ["@babel/plugin-syntax-nullish-coalescing-operator@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ=="], 75 + 76 + "@babel/plugin-syntax-numeric-separator": ["@babel/plugin-syntax-numeric-separator@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug=="], 77 + 78 + "@babel/plugin-syntax-object-rest-spread": ["@babel/plugin-syntax-object-rest-spread@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA=="], 79 + 80 + "@babel/plugin-syntax-optional-catch-binding": ["@babel/plugin-syntax-optional-catch-binding@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q=="], 81 + 82 + "@babel/plugin-syntax-optional-chaining": ["@babel/plugin-syntax-optional-chaining@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg=="], 83 + 84 + "@babel/plugin-syntax-private-property-in-object": ["@babel/plugin-syntax-private-property-in-object@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg=="], 85 + 86 + "@babel/plugin-syntax-top-level-await": ["@babel/plugin-syntax-top-level-await@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw=="], 87 + 88 + "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ=="], 89 + 90 + "@babel/template": ["@babel/template@7.27.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/parser": "^7.27.0", "@babel/types": "^7.27.0" } }, "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA=="], 91 + 92 + "@babel/traverse": ["@babel/traverse@7.27.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.27.0", "@babel/parser": "^7.27.0", "@babel/template": "^7.27.0", "@babel/types": "^7.27.0", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA=="], 93 + 94 + "@babel/types": ["@babel/types@7.27.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg=="], 95 + 96 + "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="], 97 + 98 + "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], 99 + 100 + "@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="], 101 + 102 + "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="], 103 + 104 + "@jest/console": ["@jest/console@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0" } }, "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg=="], 105 + 106 + "@jest/core": ["@jest/core@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-changed-files": "^29.7.0", "jest-config": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-resolve-dependencies": "^29.7.0", "jest-runner": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg=="], 107 + 108 + "@jest/environment": ["@jest/environment@29.7.0", "", { "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0" } }, "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw=="], 109 + 110 + "@jest/expect": ["@jest/expect@29.7.0", "", { "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" } }, "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ=="], 111 + 112 + "@jest/expect-utils": ["@jest/expect-utils@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3" } }, "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA=="], 113 + 114 + "@jest/fake-timers": ["@jest/fake-timers@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ=="], 115 + 116 + "@jest/globals": ["@jest/globals@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/types": "^29.6.3", "jest-mock": "^29.7.0" } }, "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ=="], 117 + 118 + "@jest/reporters": ["@jest/reporters@29.7.0", "", { "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", "v8-to-istanbul": "^9.0.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg=="], 119 + 120 + "@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], 121 + 122 + "@jest/source-map": ["@jest/source-map@29.6.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" } }, "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw=="], 123 + 124 + "@jest/test-result": ["@jest/test-result@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA=="], 125 + 126 + "@jest/test-sequencer": ["@jest/test-sequencer@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "slash": "^3.0.0" } }, "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw=="], 127 + 128 + "@jest/transform": ["@jest/transform@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", "write-file-atomic": "^4.0.2" } }, "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw=="], 129 + 130 + "@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="], 131 + 132 + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], 133 + 134 + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], 135 + 136 + "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="], 137 + 138 + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="], 139 + 140 + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="], 141 + 142 + "@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="], 143 + 144 + "@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="], 145 + 146 + "@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="], 147 + 148 + "@tsconfig/node10": ["@tsconfig/node10@1.0.11", "", {}, "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw=="], 149 + 150 + "@tsconfig/node12": ["@tsconfig/node12@1.0.11", "", {}, "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="], 151 + 152 + "@tsconfig/node14": ["@tsconfig/node14@1.0.3", "", {}, "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow=="], 153 + 154 + "@tsconfig/node16": ["@tsconfig/node16@1.0.4", "", {}, "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="], 155 + 156 + "@turf/boolean-point-in-polygon": ["@turf/boolean-point-in-polygon@7.2.0", "", { "dependencies": { "@turf/helpers": "^7.2.0", "@turf/invariant": "^7.2.0", "@types/geojson": "^7946.0.10", "point-in-polygon-hao": "^1.1.0", "tslib": "^2.8.1" } }, "sha512-lvEOjxeXIp+wPXgl9kJA97dqzMfNexjqHou+XHVcfxQgolctoJiRYmcVCWGpiZ9CBf/CJha1KmD1qQoRIsjLaA=="], 157 + 158 + "@turf/helpers": ["@turf/helpers@7.2.0", "", { "dependencies": { "@types/geojson": "^7946.0.10", "tslib": "^2.8.1" } }, "sha512-cXo7bKNZoa7aC7ydLmUR02oB3IgDe7MxiPuRz3cCtYQHn+BJ6h1tihmamYDWWUlPHgSNF0i3ATc4WmDECZafKw=="], 159 + 160 + "@turf/invariant": ["@turf/invariant@7.2.0", "", { "dependencies": { "@turf/helpers": "^7.2.0", "@types/geojson": "^7946.0.10", "tslib": "^2.8.1" } }, "sha512-kV4u8e7Gkpq+kPbAKNC21CmyrXzlbBgFjO1PhrHPgEdNqXqDawoZ3i6ivE3ULJj2rSesCjduUaC/wyvH/sNr2Q=="], 161 + 162 + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], 163 + 164 + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], 165 + 166 + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], 167 + 168 + "@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="], 169 + 170 + "@types/commander": ["@types/commander@2.12.5", "", { "dependencies": { "commander": "*" } }, "sha512-YXGZ/rz+s57VbzcvEV9fUoXeJlBt5HaKu5iUheiIWNsJs23bz6AnRuRiZBRVBLYyPnixNvVnuzM5pSaxr8Yp/g=="], 171 + 172 + "@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="], 173 + 174 + "@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="], 175 + 176 + "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="], 177 + 178 + "@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="], 179 + 180 + "@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="], 181 + 182 + "@types/jest": ["@types/jest@29.5.14", "", { "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ=="], 183 + 184 + "@types/luxon": ["@types/luxon@3.6.2", "", {}, "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw=="], 185 + 186 + "@types/node": ["@types/node@22.14.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw=="], 187 + 188 + "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="], 189 + 190 + "@types/yargs": ["@types/yargs@17.0.33", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA=="], 191 + 192 + "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="], 193 + 194 + "@vvo/tzdb": ["@vvo/tzdb@6.161.0", "", {}, "sha512-rvk2x77vnY87Tu1d8QuJk300WWzk8OP9/cDw2KgxEdjlYpLarJx82j2sPUpiy1wnjSuTTnYwpjBgZnNS5Iyb+w=="], 195 + 196 + "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], 197 + 198 + "acorn-walk": ["acorn-walk@8.3.4", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g=="], 199 + 200 + "airport-data": ["airport-data@1.0.1", "", {}, "sha512-Y3k2q5I/CwLKW1mul7ECRQQSEQpBm5jebmgutjO/DLbnBp6CG7jaKD58uu8O8HS0oip23TurO6czXBh5Cet9zg=="], 201 + 202 + "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], 203 + 204 + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 205 + 206 + "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], 207 + 208 + "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], 209 + 210 + "arg": ["arg@4.1.3", "", {}, "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="], 211 + 212 + "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], 213 + 214 + "array-source": ["array-source@0.0.4", "", {}, "sha512-frNdc+zBn80vipY+GdcJkLEbMWj3xmzArYApmUGxoiV8uAu/ygcs9icPdsGdA26h0MkHUMW6EN2piIvVx+M5Mw=="], 215 + 216 + "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], 217 + 218 + "babel-jest": ["babel-jest@29.7.0", "", { "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg=="], 219 + 220 + "babel-plugin-istanbul": ["babel-plugin-istanbul@6.1.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" } }, "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA=="], 221 + 222 + "babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@29.6.3", "", { "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg=="], 223 + 224 + "babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.1.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw=="], 225 + 226 + "babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="], 227 + 228 + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 229 + 230 + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], 231 + 232 + "brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], 233 + 234 + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], 235 + 236 + "browserslist": ["browserslist@4.24.4", "", { "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" } }, "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A=="], 237 + 238 + "bs-logger": ["bs-logger@0.2.6", "", { "dependencies": { "fast-json-stable-stringify": "2.x" } }, "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog=="], 239 + 240 + "bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="], 241 + 242 + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], 243 + 244 + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], 245 + 246 + "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], 247 + 248 + "caniuse-lite": ["caniuse-lite@1.0.30001715", "", {}, "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw=="], 249 + 250 + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], 251 + 252 + "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], 253 + 254 + "cheerio": ["cheerio@1.0.0", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "encoding-sniffer": "^0.2.0", "htmlparser2": "^9.1.0", "parse5": "^7.1.2", "parse5-htmlparser2-tree-adapter": "^7.0.0", "parse5-parser-stream": "^7.1.2", "undici": "^6.19.5", "whatwg-mimetype": "^4.0.0" } }, "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww=="], 255 + 256 + "cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="], 257 + 258 + "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], 259 + 260 + "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], 261 + 262 + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], 263 + 264 + "co": ["co@4.6.0", "", {}, "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ=="], 265 + 266 + "collect-v8-coverage": ["collect-v8-coverage@1.0.2", "", {}, "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q=="], 267 + 268 + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 269 + 270 + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 271 + 272 + "commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="], 273 + 274 + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], 275 + 276 + "concat-stream": ["concat-stream@2.0.0", "", { "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A=="], 277 + 278 + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], 279 + 280 + "create-jest": ["create-jest@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "prompts": "^2.0.1" }, "bin": { "create-jest": "bin/create-jest.js" } }, "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q=="], 281 + 282 + "create-require": ["create-require@1.1.1", "", {}, "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="], 283 + 284 + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 285 + 286 + "css-select": ["css-select@5.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg=="], 287 + 288 + "css-what": ["css-what@6.1.0", "", {}, "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw=="], 289 + 290 + "csv-parse": ["csv-parse@5.6.0", "", {}, "sha512-l3nz3euub2QMg5ouu5U09Ew9Wf6/wQ8I++ch1loQ0ljmzhmfZYrH9fflS22i/PQEvsPvxCwxgz5q7UB8K1JO4Q=="], 291 + 292 + "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], 293 + 294 + "dedent": ["dedent@1.5.3", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ=="], 295 + 296 + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], 297 + 298 + "detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="], 299 + 300 + "diff": ["diff@4.0.2", "", {}, "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="], 301 + 302 + "diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="], 303 + 304 + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], 305 + 306 + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], 307 + 308 + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], 309 + 310 + "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], 311 + 312 + "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], 313 + 314 + "electron-to-chromium": ["electron-to-chromium@1.5.141", "", {}, "sha512-qS+qH9oqVYc1ooubTiB9l904WVyM6qNYxtOEEGReoZXw3xlqeYdFr5GclNzbkAufWgwWLEPoDi3d9MoRwwIjGw=="], 315 + 316 + "emittery": ["emittery@0.13.1", "", {}, "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="], 317 + 318 + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], 319 + 320 + "encoding-sniffer": ["encoding-sniffer@0.2.0", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg=="], 321 + 322 + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], 323 + 324 + "error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="], 325 + 326 + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], 327 + 328 + "escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], 329 + 330 + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], 331 + 332 + "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], 333 + 334 + "exit": ["exit@0.1.2", "", {}, "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ=="], 335 + 336 + "expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="], 337 + 338 + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], 339 + 340 + "fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="], 341 + 342 + "file-source": ["file-source@0.6.1", "", { "dependencies": { "stream-source": "0.3" } }, "sha512-1R1KneL7eTXmXfKxC10V/9NeGOdbsAXJ+lQ//fvvcHUgtaZcZDWNJNblxAoVOyV1cj45pOtUrR3vZTBwqcW8XA=="], 343 + 344 + "filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="], 345 + 346 + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], 347 + 348 + "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], 349 + 350 + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], 351 + 352 + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 353 + 354 + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], 355 + 356 + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], 357 + 358 + "geo-tz": ["geo-tz@8.1.4", "", { "dependencies": { "@turf/boolean-point-in-polygon": "^7.1.0", "@turf/helpers": "^7.1.0", "geobuf": "^3.0.2", "pbf": "^3.2.1" } }, "sha512-xayeOC05wgy6JATU/k7GFHTMfSimzL1Fi3KSzt2GqvEnP1ZFXyQ9V4VAiTrTYhZSmRr0dbchZkximSegHZNUfA=="], 359 + 360 + "geobuf": ["geobuf@3.0.2", "", { "dependencies": { "concat-stream": "^2.0.0", "pbf": "^3.2.1", "shapefile": "~0.6.6" }, "bin": { "geobuf2json": "bin/geobuf2json", "json2geobuf": "bin/json2geobuf", "shp2geobuf": "bin/shp2geobuf" } }, "sha512-ASgKwEAQQRnyNFHNvpd5uAwstbVYmiTW0Caw3fBb509tNTqXyAAPMyFs5NNihsLZhLxU1j/kjFhkhLWA9djuVg=="], 361 + 362 + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], 363 + 364 + "get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="], 365 + 366 + "get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], 367 + 368 + "glob": ["glob@7.2.3", "", { "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" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], 369 + 370 + "globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], 371 + 372 + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], 373 + 374 + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 375 + 376 + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], 377 + 378 + "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], 379 + 380 + "htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="], 381 + 382 + "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], 383 + 384 + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], 385 + 386 + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], 387 + 388 + "import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="], 389 + 390 + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], 391 + 392 + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], 393 + 394 + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], 395 + 396 + "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], 397 + 398 + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], 399 + 400 + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], 401 + 402 + "is-generator-fn": ["is-generator-fn@2.1.0", "", {}, "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ=="], 403 + 404 + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], 405 + 406 + "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], 407 + 408 + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 409 + 410 + "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], 411 + 412 + "istanbul-lib-instrument": ["istanbul-lib-instrument@6.0.3", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" } }, "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q=="], 413 + 414 + "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], 415 + 416 + "istanbul-lib-source-maps": ["istanbul-lib-source-maps@4.0.1", "", { "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" } }, "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw=="], 417 + 418 + "istanbul-reports": ["istanbul-reports@3.1.7", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g=="], 419 + 420 + "jake": ["jake@10.9.2", "", { "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", "filelist": "^1.0.4", "minimatch": "^3.1.2" }, "bin": { "jake": "bin/cli.js" } }, "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA=="], 421 + 422 + "jest": ["jest@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", "import-local": "^3.0.2", "jest-cli": "^29.7.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw=="], 423 + 424 + "jest-changed-files": ["jest-changed-files@29.7.0", "", { "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0" } }, "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w=="], 425 + 426 + "jest-circus": ["jest-circus@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", "jest-each": "^29.7.0", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0", "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw=="], 427 + 428 + "jest-cli": ["jest-cli@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "chalk": "^4.0.0", "create-jest": "^29.7.0", "exit": "^0.1.2", "import-local": "^3.0.2", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg=="], 429 + 430 + "jest-config": ["jest-config@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", "@jest/types": "^29.6.3", "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-circus": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-get-type": "^29.6.3", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-runner": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "peerDependencies": { "@types/node": "*", "ts-node": ">=9.0.0" }, "optionalPeers": ["@types/node", "ts-node"] }, "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ=="], 431 + 432 + "jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="], 433 + 434 + "jest-docblock": ["jest-docblock@29.7.0", "", { "dependencies": { "detect-newline": "^3.0.0" } }, "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g=="], 435 + 436 + "jest-each": ["jest-each@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "jest-util": "^29.7.0", "pretty-format": "^29.7.0" } }, "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ=="], 437 + 438 + "jest-environment-node": ["jest-environment-node@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw=="], 439 + 440 + "jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="], 441 + 442 + "jest-haste-map": ["jest-haste-map@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA=="], 443 + 444 + "jest-leak-detector": ["jest-leak-detector@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw=="], 445 + 446 + "jest-matcher-utils": ["jest-matcher-utils@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g=="], 447 + 448 + "jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="], 449 + 450 + "jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="], 451 + 452 + "jest-pnp-resolver": ["jest-pnp-resolver@1.2.3", "", { "peerDependencies": { "jest-resolve": "*" }, "optionalPeers": ["jest-resolve"] }, "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w=="], 453 + 454 + "jest-regex-util": ["jest-regex-util@29.6.3", "", {}, "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg=="], 455 + 456 + "jest-resolve": ["jest-resolve@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" } }, "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA=="], 457 + 458 + "jest-resolve-dependencies": ["jest-resolve-dependencies@29.7.0", "", { "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" } }, "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA=="], 459 + 460 + "jest-runner": ["jest-runner@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", "jest-docblock": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-leak-detector": "^29.7.0", "jest-message-util": "^29.7.0", "jest-resolve": "^29.7.0", "jest-runtime": "^29.7.0", "jest-util": "^29.7.0", "jest-watcher": "^29.7.0", "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" } }, "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ=="], 461 + 462 + "jest-runtime": ["jest-runtime@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/globals": "^29.7.0", "@jest/source-map": "^29.6.3", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" } }, "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ=="], 463 + 464 + "jest-snapshot": ["jest-snapshot@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", "@jest/expect-utils": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", "expect": "^29.7.0", "graceful-fs": "^4.2.9", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "natural-compare": "^1.4.0", "pretty-format": "^29.7.0", "semver": "^7.5.3" } }, "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw=="], 465 + 466 + "jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="], 467 + 468 + "jest-validate": ["jest-validate@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", "pretty-format": "^29.7.0" } }, "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw=="], 469 + 470 + "jest-watcher": ["jest-watcher@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", "jest-util": "^29.7.0", "string-length": "^4.0.1" } }, "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g=="], 471 + 472 + "jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="], 473 + 474 + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], 475 + 476 + "js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="], 477 + 478 + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], 479 + 480 + "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], 481 + 482 + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], 483 + 484 + "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], 485 + 486 + "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], 487 + 488 + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], 489 + 490 + "locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], 491 + 492 + "lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="], 493 + 494 + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], 495 + 496 + "luxon": ["luxon@3.6.1", "", {}, "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ=="], 497 + 498 + "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], 499 + 500 + "make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="], 501 + 502 + "makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="], 503 + 504 + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], 505 + 506 + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], 507 + 508 + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], 509 + 510 + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 511 + 512 + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 513 + 514 + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], 515 + 516 + "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], 517 + 518 + "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], 519 + 520 + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], 521 + 522 + "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], 523 + 524 + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], 525 + 526 + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], 527 + 528 + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], 529 + 530 + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], 531 + 532 + "p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], 533 + 534 + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], 535 + 536 + "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], 537 + 538 + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], 539 + 540 + "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="], 541 + 542 + "parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="], 543 + 544 + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], 545 + 546 + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], 547 + 548 + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 549 + 550 + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], 551 + 552 + "path-source": ["path-source@0.1.3", "", { "dependencies": { "array-source": "0.0", "file-source": "0.6" } }, "sha512-dWRHm5mIw5kw0cs3QZLNmpUWty48f5+5v9nWD2dw3Y0Hf+s01Ag8iJEWV0Sm0kocE8kK27DrIowha03e1YR+Qw=="], 553 + 554 + "pbf": ["pbf@3.3.0", "", { "dependencies": { "ieee754": "^1.1.12", "resolve-protobuf-schema": "^2.1.0" }, "bin": { "pbf": "bin/pbf" } }, "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q=="], 555 + 556 + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 557 + 558 + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 559 + 560 + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], 561 + 562 + "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], 563 + 564 + "point-in-polygon-hao": ["point-in-polygon-hao@1.2.4", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-x2pcvXeqhRHlNRdhLs/tgFapAbSSe86wa/eqmj1G6pWftbEs5aVRJhRGM6FYSUERKu0PjekJzMq0gsI2XyiclQ=="], 565 + 566 + "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="], 567 + 568 + "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], 569 + 570 + "protocol-buffers-schema": ["protocol-buffers-schema@3.6.0", "", {}, "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw=="], 571 + 572 + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], 573 + 574 + "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], 575 + 576 + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], 577 + 578 + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], 579 + 580 + "resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="], 581 + 582 + "resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="], 583 + 584 + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], 585 + 586 + "resolve-protobuf-schema": ["resolve-protobuf-schema@2.1.0", "", { "dependencies": { "protocol-buffers-schema": "^3.3.1" } }, "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ=="], 587 + 588 + "resolve.exports": ["resolve.exports@2.0.3", "", {}, "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A=="], 589 + 590 + "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], 591 + 592 + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], 593 + 594 + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], 595 + 596 + "semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], 597 + 598 + "shapefile": ["shapefile@0.6.6", "", { "dependencies": { "array-source": "0.0", "commander": "2", "path-source": "0.1", "slice-source": "0.4", "stream-source": "0.3", "text-encoding": "^0.6.4" }, "bin": { "dbf2json": "bin/dbf2json", "shp2json": "bin/shp2json" } }, "sha512-rLGSWeK2ufzCVx05wYd+xrWnOOdSV7xNUW5/XFgx3Bc02hBkpMlrd2F1dDII7/jhWzv0MSyBFh5uJIy9hLdfuw=="], 599 + 600 + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 601 + 602 + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 603 + 604 + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], 605 + 606 + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], 607 + 608 + "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], 609 + 610 + "slice-source": ["slice-source@0.4.1", "", {}, "sha512-YiuPbxpCj4hD9Qs06hGAz/OZhQ0eDuALN0lRWJez0eD/RevzKqGdUx1IOMUnXgpr+sXZLq3g8ERwbAH0bCb8vg=="], 611 + 612 + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], 613 + 614 + "source-map-support": ["source-map-support@0.5.13", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w=="], 615 + 616 + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], 617 + 618 + "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], 619 + 620 + "stream-source": ["stream-source@0.3.5", "", {}, "sha512-ZuEDP9sgjiAwUVoDModftG0JtYiLUV8K4ljYD1VyUMRWtbVf92474o4kuuul43iZ8t/hRuiDAx1dIJSvirrK/g=="], 621 + 622 + "string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="], 623 + 624 + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], 625 + 626 + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], 627 + 628 + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 629 + 630 + "strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="], 631 + 632 + "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], 633 + 634 + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], 635 + 636 + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 637 + 638 + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], 639 + 640 + "test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="], 641 + 642 + "text-encoding": ["text-encoding@0.6.4", "", {}, "sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg=="], 643 + 644 + "tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="], 645 + 646 + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], 647 + 648 + "ts-jest": ["ts-jest@29.3.2", "", { "dependencies": { "bs-logger": "^0.2.6", "ejs": "^3.1.10", "fast-json-stable-stringify": "^2.1.0", "jest-util": "^29.0.0", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", "semver": "^7.7.1", "type-fest": "^4.39.1", "yargs-parser": "^21.1.1" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", "@jest/transform": "^29.0.0", "@jest/types": "^29.0.0", "babel-jest": "^29.0.0", "jest": "^29.0.0", "typescript": ">=4.3 <6" }, "optionalPeers": ["@babel/core", "@jest/transform", "@jest/types", "babel-jest"], "bin": { "ts-jest": "cli.js" } }, "sha512-bJJkrWc6PjFVz5g2DGCNUo8z7oFEYaz1xP1NpeDU7KNLMWPpEyV8Chbpkn8xjzgRDpQhnGMyvyldoL7h8JXyug=="], 649 + 650 + "ts-node": ["ts-node@10.9.2", "", { "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", "@tsconfig/node16": "^1.0.2", "acorn": "^8.4.1", "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "peerDependencies": { "@swc/core": ">=1.2.50", "@swc/wasm": ">=1.2.50", "@types/node": "*", "typescript": ">=2.7" }, "optionalPeers": ["@swc/core", "@swc/wasm"], "bin": { "ts-node": "dist/bin.js", "ts-script": "dist/bin-script-deprecated.js", "ts-node-cwd": "dist/bin-cwd.js", "ts-node-esm": "dist/bin-esm.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js" } }, "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ=="], 651 + 652 + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 653 + 654 + "type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="], 655 + 656 + "type-fest": ["type-fest@4.40.0", "", {}, "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw=="], 657 + 658 + "typedarray": ["typedarray@0.0.6", "", {}, "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="], 659 + 660 + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], 661 + 662 + "undici": ["undici@6.21.2", "", {}, "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g=="], 663 + 664 + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 665 + 666 + "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], 667 + 668 + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], 669 + 670 + "v8-compile-cache-lib": ["v8-compile-cache-lib@3.0.1", "", {}, "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="], 671 + 672 + "v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="], 673 + 674 + "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="], 675 + 676 + "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="], 677 + 678 + "whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="], 679 + 680 + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 681 + 682 + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], 683 + 684 + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], 685 + 686 + "write-file-atomic": ["write-file-atomic@4.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg=="], 687 + 688 + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], 689 + 690 + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], 691 + 692 + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], 693 + 694 + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], 695 + 696 + "yn": ["yn@3.1.1", "", {}, "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q=="], 697 + 698 + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 699 + 700 + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 701 + 702 + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 703 + 704 + "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], 705 + 706 + "@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], 707 + 708 + "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], 709 + 710 + "babel-plugin-istanbul/istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="], 711 + 712 + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 713 + 714 + "filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], 715 + 716 + "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], 717 + 718 + "p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], 719 + 720 + "parse5/entities": ["entities@6.0.0", "", {}, "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw=="], 721 + 722 + "shapefile/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], 723 + 724 + "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 725 + 726 + "babel-plugin-istanbul/istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 727 + 728 + "filelist/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], 729 + } 730 + }
+122
scripts/generateAirportTzList.test.ts
··· 1 + import path from 'path'; 2 + import os from 'os'; 3 + import fs from 'fs/promises'; 4 + import { generateCCode } from './generateAirportTzList'; 5 + 6 + // --------------------------------------------------------------------------- 7 + // Jest setup: mock external dependencies (airport-data package and fetch) 8 + // --------------------------------------------------------------------------- 9 + 10 + jest.mock('airport-data', () => { 11 + // Minimal stub covering the three airports we use in tests 12 + return [ 13 + { 14 + iata: 'JFK', 15 + name: 'John F. Kennedy International Airport', 16 + city: 'New York', 17 + country: 'US', 18 + latitude: 40.6413, 19 + longitude: -73.7781, 20 + tz: 'America/New_York', 21 + type: 'large_airport', 22 + }, 23 + { 24 + iata: 'LHR', 25 + name: 'London Heathrow Airport', 26 + city: 'London', 27 + country: 'GB', 28 + latitude: 51.4700, 29 + longitude: -0.4543, 30 + tz: 'Europe/London', 31 + type: 'large_airport', 32 + }, 33 + { 34 + iata: 'FEN', 35 + name: 'Fernando de Noronha Airport', 36 + city: 'Fernando de Noronha', 37 + country: 'BR', 38 + latitude: -3.8549, 39 + longitude: -32.4233, 40 + tz: 'America/Fortaleza', // Intentionally wrong to test correction logic 41 + type: 'large_airport', 42 + }, 43 + ]; 44 + }); 45 + 46 + // Mock global fetch so that CSV downloads are fast & deterministic 47 + const fakeRoutesCSV = `0,0,JFK,0,LHR,0,0,0,0,0\n1,0,LHR,0,JFK,0,0,0,0,0\n`; 48 + const fakeOurAirportsCSV = `id,ident,type,name,latitude_deg,longitude_deg,iso_country,iata_code,scheduled_service\n1,ABC,large_airport,John F Kennedy,0,0,US,JFK,yes\n2,DEF,large_airport,London Heathrow,0,0,GB,LHR,yes\n3,GHI,large_airport,Fernando de Noronha,0,0,BR,FEN,yes\n`; 49 + 50 + global.fetch = jest.fn(async (url: string) => { 51 + if (url.includes('routes.dat')) { 52 + return { 53 + ok: true, 54 + text: async () => fakeRoutesCSV, 55 + } as any; 56 + } 57 + if (url.includes('airports.csv')) { 58 + return { 59 + ok: true, 60 + text: async () => fakeOurAirportsCSV, 61 + } as any; 62 + } 63 + throw new Error(`Unexpected fetch to ${url}`); 64 + }) as any; 65 + 66 + // --------------------------------------------------------------------------- 67 + // Test helpers 68 + // --------------------------------------------------------------------------- 69 + 70 + afterAll(() => { 71 + // @ts-expect-error restore fetch 72 + delete global.fetch; 73 + }); 74 + 75 + const tmpFile = () => path.join(os.tmpdir(), `airport_tz_${Date.now()}.c`); 76 + 77 + // --------------------------------------------------------------------------- 78 + // Tests 79 + // --------------------------------------------------------------------------- 80 + 81 + describe('generateAirportTzList', () => { 82 + jest.setTimeout(15000); // allow a bit more time for Luxon calculations 83 + 84 + const airportsList: Array<[string, string]> = [ 85 + ['JFK', 'John F. Kennedy International Airport'], 86 + ['LHR', 'London Heathrow Airport'], 87 + ['FEN', 'Fernando de Noronha Airport'], 88 + ]; 89 + 90 + test('generateCCode writes a C file containing all unique airport codes', async () => { 91 + const out = tmpFile(); 92 + await generateCCode(airportsList, out, 5, 5); 93 + 94 + const content = await fs.readFile(out, 'utf-8'); 95 + 96 + // Expect the three airport codes to be present exactly once each in the code pool section 97 + ['JFK', 'LHR', 'FEN'].forEach(code => { 98 + const matches = content.match(new RegExp(`"${code}"`, 'g')); 99 + expect(matches).not.toBeNull(); 100 + expect(matches!.length).toBe(1); 101 + }); 102 + 103 + // Ensure the Noronha timezone correction took place – should show up in bucket comment 104 + expect(content).toContain('America/Noronha'); 105 + 106 + // Validate the macro count equals 3 (unique codes) 107 + const poolCountMatch = content.match(/#define\s+AIRPORT_CODE_POOL_COUNT\s+(\d+)/); 108 + expect(poolCountMatch).not.toBeNull(); 109 + expect(Number(poolCountMatch![1])).toBe(3); 110 + }); 111 + 112 + test('generateCCode creates distinct buckets for each standard offset', async () => { 113 + const out = tmpFile(); 114 + await generateCCode(airportsList, out, 5, 5); 115 + const content = await fs.readFile(out, 'utf-8'); 116 + 117 + // Extract all std_offset_hours occurrences 118 + const stdOffsets = Array.from(content.matchAll(/\{\s+(-?\d+\.\d+)f,/g)).map(m => Number(m[1])); 119 + // Expect three distinct standard offsets: -5, 0, -2 hours 120 + expect(new Set(stdOffsets)).toEqual(new Set([-5, 0, -2])); 121 + }); 122 + });
+770
scripts/generateAirportTzList.ts
··· 1 + // TODO: Convert generate_airport_tz_list.py to TypeScript 2 + 3 + import { program } from 'commander'; 4 + import * as fs from 'fs/promises'; 5 + import * as path from 'path'; 6 + import * as cheerio from 'cheerio'; 7 + // Use require for airport-data as it lacks types 8 + const airports = require('airport-data'); 9 + import { find as findTz } from 'geo-tz'; // Use geo-tz instead of timezonefinder 10 + // Remove node-fetch import, use global fetch 11 + import { parse } from 'csv-parse'; // Import csv-parse 12 + import { findDstTransitions, DstTransitions } from './tzCommon'; 13 + 14 + // --- MEMOIZATION CACHES --- 15 + const findTzCache = new Map<string, string[]>(); 16 + const findDstTransitionsCache = new Map<string, DstTransitions | null>(); 17 + 18 + // Helper for memoizing findTz (geo-tz) 19 + function memoizedFindTz(latitude: number, longitude: number): string[] { 20 + const key = `${latitude}_${longitude}`; 21 + if (findTzCache.has(key)) { 22 + // console.log(`Cache hit for findTz: ${key}`); 23 + return findTzCache.get(key)!; 24 + } 25 + // console.log(`Cache miss for findTz: ${key}`); 26 + try { 27 + const result = findTz(latitude, longitude); 28 + findTzCache.set(key, result); 29 + return result; 30 + } catch (e) { 31 + // Cache errors/empty results too 32 + // console.warn(`geo-tz error for ${latitude},${longitude}: ${e}`); 33 + findTzCache.set(key, []); // Cache empty array on error 34 + return []; 35 + } 36 + } 37 + 38 + // Helper for memoizing findDstTransitions 39 + function memoizedFindDstTransitions(tz: string, year: number): DstTransitions | null { 40 + if (!tz) return null; // Handle null/empty tz string input 41 + const key = `${tz}_${year}`; 42 + if (findDstTransitionsCache.has(key)) { 43 + // console.log(`Cache hit for findDstTransitions: ${key}`); 44 + return findDstTransitionsCache.get(key)!; 45 + } 46 + // console.log(`Cache miss for findDstTransitions: ${key}`); 47 + try { 48 + const result = findDstTransitions(tz, year); 49 + findDstTransitionsCache.set(key, result); 50 + return result; 51 + } catch (e) { 52 + // Cache null on error 53 + // console.warn(`findDstTransitions error for ${tz}, ${year}: ${e}`); 54 + findDstTransitionsCache.set(key, null); 55 + return null; 56 + } 57 + } 58 + 59 + // Define interfaces for data structures 60 + interface AirportDataEntry { 61 + iata?: string; 62 + name?: string; 63 + city?: string; 64 + country?: string; 65 + latitude?: number | string; // Types might be inconsistent 66 + longitude?: number | string; 67 + tz?: string; 68 + type?: string; 69 + source?: string; 70 + } 71 + 72 + interface OurAirportInfo { 73 + iata: string; 74 + type: string; 75 + scheduled_service: string; 76 + } 77 + 78 + interface AirportInfo { 79 + iata: string; 80 + name: string; 81 + city: string; 82 + country: string; 83 + latitude: number; 84 + longitude: number; 85 + tz: string; 86 + correctedTz: string; 87 + type?: string; 88 + source?: string; 89 + scheduled_service?: string; 90 + route_hits: number; 91 + } 92 + 93 + interface TzBucketKey { 94 + stdOffsetSeconds: number; 95 + dstOffsetSeconds: number; 96 + dstStartTimestamp: number; 97 + dstEndTimestamp: number; 98 + } 99 + 100 + interface TzBucketData { 101 + std: number; 102 + dst: number; 103 + start: number; 104 + end: number; 105 + tzNames: Set<string>; 106 + codes: string[]; 107 + offset?: number; 108 + count?: number; 109 + } 110 + 111 + // Helper function to create bucket keys 112 + function getBucketKey(details: DstTransitions): string { 113 + return `${details[0]}_${details[1]}_${details[2]}_${details[3]}`; 114 + } 115 + 116 + // Placeholder functions matching Python script structure 117 + 118 + async function parseTopHtml(htmlPath: string): Promise<Array<[string, string]>> { 119 + console.log(`Parsing HTML file: ${htmlPath}`); 120 + const htmlContent = await fs.readFile(htmlPath, 'utf-8'); 121 + const $ = cheerio.load(htmlContent); 122 + const results: Array<[string, string]> = []; 123 + $('tr').each((_, tr) => { 124 + const tds = $(tr).find('td'); 125 + if (tds.length < 3) return; 126 + const iata = $(tds[2]).text().trim().toUpperCase(); 127 + if (!iata || iata.length !== 3) return; // Basic validation 128 + // Improve name extraction - handle potential h2 tags etc. 129 + let name = $(tds[1]).find('h2').first().text().trim(); 130 + if (!name) { 131 + name = $(tds[1]).text().trim(); 132 + } 133 + // Clean up common suffixes (like in Python) 134 + if (name.endsWith(' International Airport')) { 135 + name = name.substring(0, name.length - ' International Airport'.length); 136 + } else if (name.endsWith(' Airport')) { 137 + name = name.substring(0, name.length - ' Airport'.length); 138 + } 139 + results.push([iata, name.trim()]); 140 + }); 141 + console.log(`Found ${results.length} airports in HTML.`); 142 + return results; 143 + } 144 + 145 + // Define type for a route record 146 + type RouteRecord = [string, string]; // [source_iata, destination_iata] 147 + 148 + async function downloadRoutesCsv(): Promise<RouteRecord[]> { 149 + console.log('Downloading and parsing routes data...'); 150 + const url = "https://raw.githubusercontent.com/jpatokal/openflights/master/data/routes.dat"; 151 + try { 152 + const response = await fetch(url); 153 + if (!response.ok) { 154 + throw new Error(`Failed to fetch ${url}: ${response.statusText}`); 155 + } 156 + const text = await response.text(); 157 + console.log('Routes data downloaded, parsing...'); 158 + 159 + const records: RouteRecord[] = []; 160 + const parser = parse({ 161 + delimiter: ',', 162 + columns: false, // Data has no header row 163 + skip_empty_lines: true, 164 + trim: true, 165 + }); 166 + 167 + parser.on('readable', function(){ 168 + let record; 169 + while ((record = parser.read()) !== null) { 170 + // Indices based on routes.dat format: 2=Source IATA, 4=Dest IATA 171 + const srcIata = record[2]; 172 + const dstIata = record[4]; 173 + // Basic validation for 3-letter IATA codes 174 + if (srcIata && srcIata.length === 3 && /^[A-Z]+$/.test(srcIata) && 175 + dstIata && dstIata.length === 3 && /^[A-Z]+$/.test(dstIata)) { 176 + records.push([srcIata, dstIata]); 177 + } 178 + } 179 + }); 180 + 181 + parser.on('error', function(err){ 182 + console.error('CSV Parsing Error:', err.message); 183 + }); 184 + 185 + return new Promise((resolve, reject) => { 186 + parser.on('end', function(){ 187 + console.log(`Finished parsing routes. Found ${records.length} valid routes.`); 188 + resolve(records); 189 + }); 190 + parser.write(text); 191 + parser.end(); 192 + }); 193 + 194 + } catch (error) { 195 + console.error('Error downloading or parsing routes data:', error); 196 + throw error; // Re-throw error to be handled by caller 197 + } 198 + } 199 + 200 + async function rankAirportsByRoutes(routes: RouteRecord[]): Promise<Map<string, number>> { 201 + console.log('Ranking airports by routes...'); 202 + const counts = new Map<string, number>(); 203 + 204 + for (const [srcIata, dstIata] of routes) { 205 + counts.set(srcIata, (counts.get(srcIata) || 0) + 1); 206 + counts.set(dstIata, (counts.get(dstIata) || 0) + 1); 207 + } 208 + 209 + console.log(`Airport ranking complete. Found ${counts.size} unique airports in routes.`); 210 + return counts; // Return map of IATA -> count 211 + } 212 + 213 + async function downloadOurAirportsCsv(): Promise<Map<string, OurAirportInfo>> { 214 + console.log('Downloading and parsing OurAirports data...'); 215 + const url = "https://davidmegginson.github.io/ourairports-data/airports.csv"; 216 + const ourAirportsMap = new Map<string, OurAirportInfo>(); 217 + try { 218 + const response = await fetch(url); 219 + if (!response.ok) { 220 + throw new Error(`Failed to fetch ${url}: ${response.statusText}`); 221 + } 222 + const text = await response.text(); 223 + console.log('OurAirports data downloaded, parsing...'); 224 + 225 + const parser = parse({ 226 + delimiter: ',', 227 + columns: true, // Use header row 228 + skip_empty_lines: true, 229 + trim: true, 230 + }); 231 + 232 + parser.on('readable', function() { 233 + let record; 234 + while ((record = parser.read()) !== null) { 235 + // Extract relevant columns: iata_code, type, scheduled_service 236 + const iata = record.iata_code; 237 + const type = record.type; 238 + const scheduled = record.scheduled_service; 239 + if (iata && iata.length === 3 && /^[A-Z0-9]+$/.test(iata)) { 240 + ourAirportsMap.set(iata.toUpperCase(), { 241 + iata: iata.toUpperCase(), 242 + type: type || '', // Handle potential missing values 243 + scheduled_service: scheduled || '' 244 + }); 245 + } 246 + } 247 + }); 248 + 249 + parser.on('error', function(err) { 250 + console.error('OurAirports CSV Parsing Error:', err.message); 251 + }); 252 + 253 + return new Promise((resolve, reject) => { 254 + parser.on('end', function() { 255 + console.log(`Finished parsing OurAirports. Found ${ourAirportsMap.size} airports with IATA.`); 256 + resolve(ourAirportsMap); 257 + }); 258 + parser.write(text); 259 + parser.end(); 260 + }); 261 + 262 + } catch (error) { 263 + console.error('Error downloading or parsing OurAirports data:', error); 264 + console.warn('Proceeding without OurAirports classification data.'); 265 + return ourAirportsMap; // Return empty map on error 266 + } 267 + } 268 + 269 + async function generateCCode( 270 + airportsList: Array<[string, string]>, 271 + outPath: string, 272 + groupSize: number, 273 + maxBucket: number 274 + ): Promise<void> { 275 + console.log(`Generating C code for ${outPath}...`); 276 + console.log(`Group size: ${groupSize}, Max bucket size: ${maxBucket}`); 277 + 278 + const year = new Date().getUTCFullYear(); 279 + 280 + // Load airport data using require 281 + const airportDataArray: AirportDataEntry[] = airports as any[]; 282 + const initialAirportDb = new Map<string, AirportInfo>(); 283 + 284 + for (const airport of airportDataArray) { 285 + if (airport.iata) { 286 + const iataUpper = airport.iata.toUpperCase(); 287 + const lat = typeof airport.latitude === 'number' ? airport.latitude : parseFloat(airport.latitude ?? 'NaN'); 288 + const lon = typeof airport.longitude === 'number' ? airport.longitude : parseFloat(airport.longitude ?? 'NaN'); 289 + 290 + if (isNaN(lat) || isNaN(lon) || !airport.name || !airport.city || !airport.country) { 291 + continue; 292 + } 293 + 294 + initialAirportDb.set(iataUpper, { 295 + iata: iataUpper, 296 + name: airport.name, 297 + city: airport.city, 298 + country: airport.country, 299 + latitude: lat, 300 + longitude: lon, 301 + tz: airport.tz || '', 302 + correctedTz: '', 303 + type: airport.type, 304 + source: airport.source, 305 + scheduled_service: undefined, 306 + route_hits: 0, 307 + }); 308 + } 309 + } 310 + console.log(`Loaded ${initialAirportDb.size} initial airports.`); 311 + 312 + // Fix known incorrect TZs from airport-data 313 + const fenAirport = initialAirportDb.get('FEN'); 314 + if (fenAirport && fenAirport.tz !== 'America/Noronha') { 315 + console.log('[DATA_FIX] Correcting FEN timezone from', fenAirport.tz, 'to America/Noronha'); 316 + fenAirport.tz = 'America/Noronha'; 317 + } 318 + 319 + // --- Fetch/Parse/Rank Routes --- 320 + console.log('Processing routes...'); 321 + let routeCounts = new Map<string, number>(); 322 + try { 323 + const routes = await downloadRoutesCsv(); 324 + routeCounts = await rankAirportsByRoutes(routes); 325 + for (const [iata, count] of routeCounts.entries()) { 326 + const airportInfo = initialAirportDb.get(iata); 327 + if (airportInfo) { 328 + airportInfo.route_hits = count; 329 + } 330 + } 331 + console.log('Integrated route counts.'); 332 + } catch (error) { 333 + console.warn('Could not get route counts, proceeding without them.'); 334 + } 335 + 336 + // --- Fetch/Parse OurAirports --- 337 + const ourAirportsData = await downloadOurAirportsCsv(); 338 + for (const [iata, oaInfo] of ourAirportsData.entries()) { 339 + const airportInfo = initialAirportDb.get(iata); 340 + if (airportInfo) { 341 + airportInfo.type = oaInfo.type; // Overwrite type if available 342 + airportInfo.scheduled_service = oaInfo.scheduled_service; 343 + } 344 + } 345 + if (ourAirportsData.size > 0) { 346 + console.log('Integrated OurAirports classification data.'); 347 + } 348 + 349 + // --- Correct timezones and build initial buckets --- 350 + console.log('Correcting timezones and building initial buckets...'); 351 + findTzCache.clear(); 352 + findDstTransitionsCache.clear(); 353 + const airportDb = new Map<string, AirportInfo>(); 354 + const tzBuckets = new Map<string, TzBucketData>(); 355 + const groupKeys = new Map<number, string[]>(); // Map: std_offset_s -> [bucketKey, ...] 356 + 357 + let foundFen = false; 358 + for (const airport of initialAirportDb.values()) { 359 + const isFen = airport.iata === 'FEN'; 360 + if (isFen) console.log(`[NORONHA_DEBUG] Processing FEN airport record.`); 361 + let correctedTz: string | undefined | null = airport.tz; 362 + // Only fall back to geo-tz if the original tz is empty/missing 363 + if (!correctedTz) { 364 + if (isFen) console.log(`[NORONHA_DEBUG] FEN had no original TZ, looking up via geo-tz.`); 365 + try { 366 + const foundTzs = memoizedFindTz(airport.latitude, airport.longitude); 367 + if (foundTzs && foundTzs.length > 0) { 368 + correctedTz = foundTzs[0]; 369 + } else { 370 + continue; // Skip if no TZ can be determined 371 + } 372 + } catch { 373 + continue; // Skip airports that still fail TZ lookup 374 + } 375 + } 376 + 377 + if (isFen) console.log(`[NORONHA_DEBUG] FEN correctedTz: ${correctedTz}`); 378 + if (correctedTz === 'America/Noronha') foundFen = true; // Track if we process this TZ 379 + 380 + // Update airport info with corrected TZ 381 + const updatedAirportInfo = { ...airport, correctedTz }; 382 + airportDb.set(updatedAirportInfo.iata, updatedAirportInfo); 383 + 384 + // Get DST details for the corrected timezone 385 + let dstDetails: DstTransitions | null = null; 386 + try { 387 + // *** Use memoized version *** 388 + dstDetails = memoizedFindDstTransitions(correctedTz, year); 389 + } catch (e) { 390 + if (isFen) console.error(`[NORONHA_DEBUG] Error getting DST for FEN/Noronha: ${e}`); 391 + // This catch block is less likely needed now due to memoized wrapper handling errors 392 + // console.warn(`Could not get DST transitions for ${correctedTz} (Airport: ${airport.iata}): ${e}`); 393 + continue; 394 + } 395 + 396 + if (isFen) console.log(`[NORONHA_DEBUG] FEN dstDetails: ${JSON.stringify(dstDetails)}`); 397 + 398 + if (!dstDetails) continue; // Skip if memoized function returned null (error or invalid TZ) 399 + 400 + // --- Bucket creation/update logic --- (Restored) 401 + const key = getBucketKey(dstDetails); 402 + const stdOffset = dstDetails[0]; 403 + 404 + if (!tzBuckets.has(key)) { 405 + tzBuckets.set(key, { 406 + std: stdOffset, 407 + dst: dstDetails[1], 408 + start: dstDetails[2], 409 + end: dstDetails[3], 410 + tzNames: new Set([correctedTz]), 411 + codes: [], 412 + }); 413 + const keysForStdOffset = groupKeys.get(stdOffset) || []; 414 + keysForStdOffset.push(key); 415 + groupKeys.set(stdOffset, keysForStdOffset); 416 + } else { 417 + tzBuckets.get(key)!.tzNames.add(correctedTz); 418 + } 419 + // --- End bucket logic --- 420 + } 421 + console.log(`Finished correcting timezones. Cache sizes: findTz=${findTzCache.size}, findDst=${findDstTransitionsCache.size}`); 422 + console.log(`[NORONHA_DEBUG] Did we process America/Noronha TZ? ${foundFen}`); 423 + 424 + // Pre-group airports by standard offset for fallback optimization 425 + console.log('Pre-grouping airports by standard offset...'); 426 + const airportsByStdOffset = new Map<number, AirportInfo[]>(); 427 + for (const airportInfo of airportDb.values()) { 428 + if (!airportInfo.correctedTz) continue; 429 + try { 430 + const dstDetails = memoizedFindDstTransitions(airportInfo.correctedTz, year); 431 + if (dstDetails) { 432 + const stdOffset = dstDetails[0]; 433 + const list = airportsByStdOffset.get(stdOffset) || []; 434 + list.push(airportInfo); 435 + airportsByStdOffset.set(stdOffset, list); 436 + } 437 + } catch { 438 + // Ignore errors here, airport just won't be in the pre-grouped map 439 + } 440 + } 441 + console.log(`Finished pre-grouping into ${airportsByStdOffset.size} standard offset groups.`); 442 + 443 + // Check if the Noronha bucket exists after grouping 444 + const noronhaKey = `${-7200}_${-7200}_${0}_${0}`; // Expected key: std=-2h, dst=-2h, start=0, end=0 445 + console.log(`[NORONHA_DEBUG] Does Noronha bucket key (${noronhaKey}) exist in tzBuckets? ${tzBuckets.has(noronhaKey)}`); 446 + 447 + // Determine fallback codes 448 + const getFallbackCodes = (stdOffsetSeconds: number): string[] => { 449 + // Retrieve pre-filtered candidates for this standard offset 450 + const initialCandidates = airportsByStdOffset.get(stdOffsetSeconds) || []; 451 + 452 + if (initialCandidates.length === 0) return []; 453 + 454 + // Sort the much smaller candidate list 455 + const candidates = initialCandidates.sort((a, b) => b.route_hits - a.route_hits); // Sort by route hits desc 456 + 457 + if (candidates.length === 0) return []; 458 + 459 + const result: string[] = []; 460 + const scheduledYes = (a: AirportInfo) => a.scheduled_service === 'yes'; 461 + 462 + // Tier 1: Large, scheduled 463 + const large = candidates.filter(a => a.type === 'large_airport' && scheduledYes(a)); 464 + result.push(...large.slice(0, maxBucket > 0 ? maxBucket : 3).map(a => a.iata)); 465 + 466 + // Tier 2: Medium, scheduled 467 + let remain = (maxBucket > 0 ? maxBucket : 3) - result.length; 468 + if (remain > 0) { 469 + const medium = candidates.filter(a => a.type === 'medium_airport' && scheduledYes(a) && !result.includes(a.iata)); 470 + result.push(...medium.slice(0, Math.min(remain, 2)).map(a => a.iata)); 471 + } 472 + 473 + // Tier 3: Small, scheduled 474 + remain = (maxBucket > 0 ? maxBucket : 3) - result.length; 475 + if (remain > 0) { 476 + const small = candidates.filter(a => a.type === 'small_airport' && scheduledYes(a) && !result.includes(a.iata)); 477 + result.push(...small.slice(0, 1).map(a => a.iata)); 478 + } 479 + 480 + // Tier 4: Ensure at least one if possible 481 + if (result.length === 0) { 482 + result.push(candidates[0].iata); // Add the top-ranked overall 483 + } 484 + 485 + // Enforce max bucket size strictly if needed (though applied later too) 486 + return maxBucket > 0 ? result.slice(0, maxBucket) : result; 487 + }; 488 + 489 + // --- Group codes from HTML list & apply fallbacks --- 490 + const groupCodes = new Map<number, string[]>(); 491 + const seenHtmlIatas = new Set<string>(); 492 + 493 + for (const [iata] of airportsList) { 494 + const airportInfo = airportDb.get(iata.toUpperCase()); 495 + if (!airportInfo || !airportInfo.correctedTz) continue; 496 + try { 497 + // *** Use memoized version *** 498 + const dstDetails = memoizedFindDstTransitions(airportInfo.correctedTz, year); 499 + if (dstDetails) { 500 + const stdOffset = dstDetails[0]; 501 + const codes = groupCodes.get(stdOffset) || []; 502 + if (!seenHtmlIatas.has(airportInfo.iata)) { 503 + codes.push(airportInfo.iata); 504 + seenHtmlIatas.add(airportInfo.iata); 505 + groupCodes.set(stdOffset, codes); 506 + } 507 + } 508 + } catch { /* Ignore airports whose TZ causes errors here */ } 509 + } 510 + 511 + // Trim HTML groups to groupSize 512 + if (groupSize > 0) { 513 + for (const stdOffset of groupCodes.keys()) { 514 + groupCodes.set(stdOffset, groupCodes.get(stdOffset)!.slice(0, groupSize)); 515 + } 516 + } 517 + 518 + // Apply fallbacks for standard offsets that exist in buckets but have no HTML codes 519 + for (const stdOffset of groupKeys.keys()) { 520 + if (!groupCodes.has(stdOffset) || groupCodes.get(stdOffset)!.length === 0) { 521 + const fallbacks = getFallbackCodes(stdOffset); 522 + if (fallbacks.length > 0) { 523 + groupCodes.set(stdOffset, fallbacks); 524 + console.log(`Applied fallback for std offset ${stdOffset/3600}h: ${fallbacks.join(', ')}`); 525 + } else { 526 + console.warn(`No fallback codes found for std offset ${stdOffset/3600}h`); 527 + } 528 + } 529 + } 530 + console.log(`Grouped codes from HTML/fallbacks into ${groupCodes.size} standard offset groups.`); 531 + 532 + // --- Assign codes to final buckets --- 533 + const usedCodes = new Set<string>(); 534 + 535 + const assignToBucket = (iataCode: string): boolean => { 536 + if (usedCodes.has(iataCode)) return false; 537 + const airportInfo = airportDb.get(iataCode); 538 + if (!airportInfo || !airportInfo.correctedTz) return false; 539 + 540 + try { 541 + // *** Use memoized version *** 542 + const dstDetails = memoizedFindDstTransitions(airportInfo.correctedTz, year); 543 + if (!dstDetails) return false; 544 + const key = getBucketKey(dstDetails); 545 + const bucket = tzBuckets.get(key); 546 + 547 + if (bucket) { 548 + // Apply maxBucket limit here during assignment 549 + if (maxBucket <= 0 || bucket.codes.length < maxBucket) { 550 + bucket.codes.push(iataCode); 551 + usedCodes.add(iataCode); 552 + return true; 553 + } 554 + } 555 + } catch { 556 + // Ignore errors during assignment 557 + } 558 + return false; 559 + }; 560 + 561 + // Assign codes from the HTML/fallback groups 562 + for (const codes of groupCodes.values()) { 563 + for (const iata of codes) { 564 + assignToBucket(iata); 565 + } 566 + } 567 + 568 + // --- Final safety pass for empty buckets --- 569 + console.log('Running safety pass for empty buckets...'); 570 + let safetyPassAssigned = 0; 571 + for (const bucket of tzBuckets.values()) { 572 + if (bucket.codes.length === 0) { 573 + // Find potential candidates actually in this bucket's zones 574 + const candidates = Array.from(airportDb.values()) 575 + .filter(a => bucket.tzNames.has(a.correctedTz) && !usedCodes.has(a.iata)) 576 + .sort((a, b) => b.route_hits - a.route_hits); 577 + 578 + if (candidates.length > 0) { 579 + // Try assigning the best candidate 580 + if (assignToBucket(candidates[0].iata)) { 581 + safetyPassAssigned++; 582 + } 583 + } 584 + } 585 + } 586 + console.log(`Safety pass assigned codes to ${safetyPassAssigned} previously empty buckets.`); 587 + 588 + // Apply maxBucket limit definitively after all assignments 589 + if (maxBucket > 0) { 590 + for (const bucket of tzBuckets.values()) { 591 + if (bucket.codes.length > maxBucket) { 592 + bucket.codes = bucket.codes.slice(0, maxBucket); 593 + } 594 + } 595 + } 596 + 597 + // --- Finalize Buckets and Generate Pools --- 598 + const sortedBuckets = Array.from(tzBuckets.values()).sort((a, b) => { 599 + if (a.std !== b.std) return a.std - b.std; 600 + if (a.dst !== b.dst) return a.dst - b.dst; 601 + return a.start - b.start; 602 + }); 603 + 604 + const codePool: string[] = []; 605 + const namePool: string[] = []; 606 + const poolCodeSet = new Set<string>(); // Track codes added to pool 607 + 608 + // Calculate final offsets and counts 609 + for (const bucket of sortedBuckets) { 610 + const uniqueCodesForBucket: string[] = []; 611 + for (const code of bucket.codes) { 612 + if (!poolCodeSet.has(code)) { 613 + uniqueCodesForBucket.push(code); 614 + poolCodeSet.add(code); 615 + } 616 + } 617 + 618 + bucket.offset = codePool.length; // Offset is current size before adding 619 + bucket.count = uniqueCodesForBucket.length; 620 + 621 + // Add unique codes and their names to the pools 622 + for (const code of uniqueCodesForBucket) { 623 + codePool.push(code); 624 + const airportInfo = airportDb.get(code); 625 + let name = airportInfo?.name || code; // Fallback to code if name missing 626 + // Clean name 627 + if (name.endsWith(' International Airport')) { 628 + name = name.substring(0, name.length - ' International Airport'.length); 629 + } else if (name.endsWith(' Airport')) { 630 + name = name.substring(0, name.length - ' Airport'.length); 631 + } 632 + namePool.push(name.trim().replace(/\"/g, '\\"')); // Escape quotes for C string 633 + } 634 + } 635 + 636 + // --- Generate C Code String --- 637 + let cContent = `// Auto-generated by generateAirportTzList.ts\n`; 638 + cContent += `// Generated on: ${new Date().toISOString()}\n`; 639 + cContent += `// Year-specific DST data for ${year}\n\n`; 640 + cContent += `#include <stdint.h>\n\n`; 641 + 642 + // Airport Code Pool (3-letter IATA codes) 643 + cContent += `// Total airport codes: ${codePool.length}\n`; 644 + cContent += `static const char airport_code_pool[] =\n`; 645 + if (codePool.length > 0) { 646 + for (let i = 0; i < codePool.length; i++) { 647 + if (i % 8 === 0) cContent += ` `; // Indent 648 + cContent += `"${codePool[i]}"`; 649 + if ((i + 1) % 8 === 0 || i === codePool.length - 1) { 650 + cContent += `\n`; 651 + } else { 652 + cContent += ` `; 653 + } 654 + } 655 + cContent += `;\n\n`; 656 + } else { 657 + cContent += ` ""; // Empty pool\n`; 658 + cContent += `; 659 + 660 + `; 661 + } 662 + 663 + // Airport Name Pool (pointers to strings) 664 + cContent += `// Total airport names: ${namePool.length}\n`; 665 + cContent += `static const char* airport_name_pool[] = {\n`; 666 + if (namePool.length > 0) { 667 + for (const name of namePool) { 668 + cContent += ` "${name}",\n`; 669 + } 670 + } else { 671 + cContent += ` // Empty pool\n`; 672 + } 673 + cContent += `};\n\n`; 674 + 675 + // TzInfo struct definition 676 + cContent += `typedef struct {\n`; 677 + cContent += ` float std_offset_hours;\n`; 678 + cContent += ` float dst_offset_hours;\n`; 679 + cContent += ` int64_t dst_start_utc;\n`; 680 + cContent += ` int64_t dst_end_utc;\n`; 681 + cContent += ` int name_offset; // Index into airport_code_pool & airport_name_pool\n`; 682 + cContent += ` int name_count; // Number of unique airports for this tz variant\n`; 683 + cContent += `} TzInfo;\n\n`; 684 + 685 + // airport_tz_list array initialization 686 + cContent += `// Total timezone variants: ${sortedBuckets.length}\n`; 687 + cContent += `static const TzInfo airport_tz_list[] = {\n`; 688 + if (sortedBuckets.length > 0) { 689 + for (const bucket of sortedBuckets) { 690 + const std_h = bucket.std / 3600.0; 691 + const dst_h = bucket.dst / 3600.0; 692 + // Escape any backslashes potentially in timezone names for the comment 693 + const tzComment = Array.from(bucket.tzNames).slice(0,3).join(', ').replace(/\\/g, '\\'); 694 + cContent += ` { ${std_h.toFixed(2)}f, ${dst_h.toFixed(2)}f, ${bucket.start}LL, ${bucket.end}LL, ${bucket.offset ?? 0}, ${bucket.count ?? 0} }, // ${tzComment}...\n`; 695 + } 696 + } else { 697 + cContent += ` // Empty list\n`; 698 + } 699 + cContent += `};\n\n`; 700 + 701 + // Definitions for counts 702 + cContent += `#define AIRPORT_TZ_LIST_COUNT (sizeof(airport_tz_list)/sizeof(airport_tz_list[0]))\n`; 703 + cContent += `#define AIRPORT_CODE_POOL_COUNT ${codePool.length}\n`; 704 + cContent += `#define AIRPORT_NAME_POOL_COUNT ${namePool.length}\n`; 705 + 706 + // --- Write C Code to File --- 707 + // --- 7. Write C Code to File --- 708 + await fs.writeFile(outPath, cContent, 'utf-8'); 709 + 710 + console.log(`Successfully generated ${outPath} with ${sortedBuckets.length} tz buckets and ${codePool.length} unique airports.`); 711 + } 712 + 713 + async function main() { 714 + program 715 + .version('1.0.0') 716 + .description('Generate airport_tz_list.c: hybrid grouping, DST buckets, fallback.') 717 + .option('--html <path>', 'Path to GetToCenter HTML file (top1000.html)', path.join(__dirname, 'top1000.html')) 718 + .option('--out <path>', 'C output file path', path.join(__dirname, '../src/c/airport_tz_list.c')) 719 + .option('--top <number>', 'Number of airports per std offset group (from HTML)', (val) => parseInt(val, 10), 10) 720 + .option('--max-bucket <number>', 'Max unique airports per DST bucket', (val) => parseInt(val, 10), 10) 721 + .parse(process.argv); 722 + 723 + const options = program.opts(); 724 + 725 + console.log('Starting airport timezone list generation...'); 726 + console.log('Options:', options); 727 + 728 + // Validate HTML file existence 729 + try { 730 + await fs.access(options.html); 731 + } catch (error) { 732 + console.error(`ERROR: HTML file not found: ${options.html}`); 733 + process.exit(1); 734 + } 735 + 736 + // Parse HTML 737 + const airportsList = await parseTopHtml(options.html); 738 + if (!airportsList || airportsList.length === 0) { 739 + console.error(`ERROR: No airports found in HTML: ${options.html}`); 740 + process.exit(1); 741 + } 742 + 743 + // Generate C code 744 + try { 745 + await generateCCode(airportsList, options.out, options.top, options.maxBucket); 746 + console.log('Airport timezone list generation finished successfully.'); 747 + } catch (error) { 748 + console.error('Airport timezone list generation failed:', error); 749 + process.exit(1); 750 + } 751 + } 752 + 753 + // ------------------------------------------------------------ 754 + // Exports for unit testing 755 + // ------------------------------------------------------------ 756 + export { 757 + generateCCode, 758 + memoizedFindDstTransitions, 759 + memoizedFindTz, 760 + }; 761 + 762 + // ------------------------------------------------------------ 763 + // CLI entry point – only run when invoked directly 764 + // ------------------------------------------------------------ 765 + if (require.main === module) { 766 + main().catch(error => { 767 + console.error('An unhandled error occurred:', error); 768 + process.exit(1); 769 + }); 770 + }
+123
scripts/generateExpectedTransitions.ts
··· 1 + // scripts/generateExpectedTransitions.ts 2 + // Temporary script to generate expected DST transitions for tzCommon.test.ts 3 + 4 + import { DateTime } from 'luxon'; 5 + import { IANAZone } from 'luxon'; 6 + import { DstTransitions } from './tzCommon'; // Import the type 7 + 8 + const TARGET_YEAR = 2025; 9 + 10 + const ZONES_TO_TEST = [ 11 + 'America/New_York', 12 + 'Europe/London', 13 + 'Pacific/Auckland', 14 + 'Asia/Tokyo', 15 + 'Pacific/Apia', 16 + 'Australia/Lord_Howe', 17 + 'America/Denver', 18 + 'Asia/Katmandu', 19 + 'Invalid/Zone' // Keep invalid zone to test its handling 20 + ]; 21 + 22 + interface TzDetailsInternal { 23 + offsetSeconds: number; 24 + isDST: boolean; 25 + } 26 + 27 + // Simplified version of getTzDetails for this script 28 + function getDetails(zoneName: string, dt: DateTime): TzDetailsInternal | undefined { 29 + if (!IANAZone.isValidZone(zoneName)) return undefined; 30 + const dtInZone = dt.setZone(zoneName); 31 + if (!dtInZone.isValid) return undefined; 32 + return { offsetSeconds: dtInZone.offset * 60, isDST: dtInZone.isInDST }; 33 + } 34 + 35 + // Simplified core logic from findDstTransitions 36 + function calculateTransitions(zoneName: string, year: number): DstTransitions { 37 + let stdOffsetSec: number | null = null; 38 + let dstOffsetSec: number | null = null; 39 + let startTs = 0; 40 + let endTs = 0; 41 + 42 + // Define prevIsDST and prevOffsetSec before the conditional block 43 + let prevIsDST: boolean; 44 + let prevOffsetSec: number; 45 + 46 + let currentDt = DateTime.utc(year, 1, 1).minus({ hours: 1 }); 47 + const initialDetails = getDetails(zoneName, currentDt); 48 + 49 + if (!initialDetails) { 50 + if (!IANAZone.isValidZone(zoneName)) return [0, 0, 0, 0]; 51 + currentDt = DateTime.utc(year, 1, 1); 52 + const retryDetails = getDetails(zoneName, currentDt); 53 + if (!retryDetails) return [0, 0, 0, 0]; 54 + prevIsDST = retryDetails.isDST; 55 + prevOffsetSec = retryDetails.offsetSeconds; 56 + // Assign std/dst offsets based on retry details 57 + if (!retryDetails.isDST) stdOffsetSec = retryDetails.offsetSeconds; 58 + else dstOffsetSec = retryDetails.offsetSeconds; 59 + } else { 60 + prevIsDST = initialDetails.isDST; 61 + prevOffsetSec = initialDetails.offsetSeconds; 62 + // Assign std/dst offsets based on initial details 63 + if (!initialDetails.isDST) stdOffsetSec = initialDetails.offsetSeconds; 64 + else dstOffsetSec = initialDetails.offsetSeconds; 65 + } 66 + 67 + const totalHours = (DateTime.utc(year + 1, 1, 1).diff(DateTime.utc(year, 1, 1), 'days').days * 24) + 3; 68 + 69 + for (let i = 0; i < totalHours; i++) { 70 + currentDt = currentDt.plus({ hours: 1 }); 71 + const details = getDetails(zoneName, currentDt); 72 + if (!details) continue; 73 + 74 + const { offsetSeconds: curOffsetSec, isDST: curIsDST } = details; 75 + 76 + if (curIsDST) dstOffsetSec = curOffsetSec; 77 + else stdOffsetSec = curOffsetSec; 78 + 79 + if (curIsDST !== prevIsDST && currentDt.year === year) { 80 + const ts = Math.floor(currentDt.toMillis() / 1000); 81 + if (!prevIsDST && curIsDST) startTs = ts; 82 + else if (prevIsDST && !curIsDST) endTs = ts; 83 + } 84 + prevIsDST = curIsDST; 85 + prevOffsetSec = curOffsetSec; 86 + } 87 + 88 + if (stdOffsetSec === null) stdOffsetSec = prevOffsetSec; 89 + if (dstOffsetSec === null) dstOffsetSec = stdOffsetSec; 90 + 91 + if (Math.abs(stdOffsetSec - dstOffsetSec) < 60) { 92 + startTs = 0; 93 + endTs = 0; 94 + dstOffsetSec = stdOffsetSec; 95 + } 96 + 97 + // IMPORTANT: Match the return order [std, dst, START, END] used by findDstTransitions logic 98 + // Sort timestamps for Southern Hemisphere if needed? NO, the logic *finds* them in chronological order. 99 + // The issue in tests was likely misinterpreting which timestamp was start/end for SH. 100 + // findDstTransitions assigns startTs when moving *into* DST, endTs when moving *out*. This is consistent. 101 + 102 + // For SH: Spring forward (start DST) happens later in the year. 103 + // Fall back (end DST) happens earlier in the year. 104 + // The `findDstTransitions` logic correctly captures the TS of the *event*. 105 + 106 + // Let's return exactly what the loop finds. Tests should expect this order. 107 + 108 + return [stdOffsetSec, dstOffsetSec, startTs, endTs]; 109 + } 110 + 111 + // --- Generate the output --- 112 + console.log(`// Generated for year ${TARGET_YEAR} by generateExpectedTransitions.ts`); 113 + console.log('const expectedTransitions: { zone: string; expected: DstTransitions }[] = ['); 114 + 115 + ZONES_TO_TEST.forEach(zone => { 116 + const transitions = calculateTransitions(zone, TARGET_YEAR); 117 + console.log(` { 118 + zone: '${zone}', 119 + expected: [${transitions.join(', ')}] as DstTransitions, 120 + },`); 121 + }); 122 + 123 + console.log('];');
+17
scripts/jest.config.js
··· 1 + module.exports = { 2 + preset: "ts-jest", 3 + testEnvironment: "node", 4 + roots: ["<rootDir>"], 5 + testMatch: ["**/*.test.ts"], 6 + moduleFileExtensions: ["ts", "js", "json", "node"], 7 + // Transform TypeScript files using ts-jest 8 + transform: { 9 + "^.+\\.tsx?$": "ts-jest", 10 + }, 11 + // Silence verbose warnings from ts-jest about diagnostics for faster runs 12 + globals: { 13 + "ts-jest": { 14 + diagnostics: false, 15 + }, 16 + }, 17 + };
+30
scripts/package.json
··· 1 + { 2 + "name": "tidface-scripts", 3 + "version": "1.0.0", 4 + "private": true, 5 + "description": "Scripts for generating C code for tidface", 6 + "scripts": { 7 + "generate:tz": "ts-node generateTzList.ts", 8 + "generate:airport": "ts-node generateAirportTzList.ts", 9 + "test": "jest" 10 + }, 11 + "dependencies": { 12 + "@types/commander": "^2.12.5", 13 + "@vvo/tzdb": "^6", 14 + "airport-data": "^1.0.1", 15 + "cheerio": "^1", 16 + "commander": "^13.1.0", 17 + "csv-parse": "^5", 18 + "geo-tz": "^8", 19 + "luxon": "^3" 20 + }, 21 + "devDependencies": { 22 + "typescript": "^5", 23 + "ts-node": "^10", 24 + "@types/node": "^22", 25 + "@types/luxon": "^3", 26 + "jest": "^29", 27 + "ts-jest": "^29", 28 + "@types/jest": "^29" 29 + } 30 + }
+53
scripts/status.md
··· 1 + # Project Status Report: Timezone List Generation (TS Conversion) 2 + 3 + **Date:** 2025-04-24 4 + 5 + **Goal:** Convert the Python script `generate_airport_tz_list.py` to TypeScript (`generateAirportTzList.ts`), ensuring functional equivalence and correctness in generating the `src/c/airport_tz_list.c` file. Performance optimization was a secondary goal. 6 + 7 + **Summary:** 8 + 9 + The conversion is largely complete, but debugging focused on achieving correct timezone bucket counts and DST transition timestamps compared to the original Python version and IANA definitions. Performance tuning was attempted but led to correctness issues, prioritizing correctness for now. 10 + 11 + **Key Issues Encountered & Resolutions:** 12 + 13 + 1. **Initial Performance:** The initial TS script was significantly slower than Python. 14 + * **Attempted Fix:** Implemented memoization for `geo-tz` lookups and DST transition calculations. Introduced an optimized `findDstTransitions` in `tzCommon.ts` using day-by-day iteration + binary search for the exact hour. 15 + * **Status:** Optimization attempts complicated correctness analysis (see point 5). Performance is currently secondary to correctness. 16 + 17 + 2. **Missing Timezone (`America/Noronha` / FEN):** The TS script initially missed the bucket for Fernando de Noronha (IATA: FEN). 18 + * **Root Cause:** The `airport-data` package provided an incorrect timezone (`America/Fortaleza`) for FEN. 19 + * **Resolution:** Added a manual data fix step in `generateAirportTzList.ts` immediately after loading airport data to force FEN's timezone to `America/Noronha`. Verified with debug logs and external sources (Wikipedia). 20 + * **Status:** Resolved. FEN and its corresponding timezone bucket (UTC-2, no DST) are now correctly generated. 21 + 22 + 3. **Bucket Count Discrepancy:** Initial comparisons showed TS generating 60 buckets while a specific Python run generated 61. 23 + * **Root Cause:** Analysis revealed Python (`zoneinfo`) created a separate bucket for `Africa/Casablanca` (0 -> +1 offset) due to its unique 2025 Ramadan DST schedule, while Luxon (TS) initially grouped it with `Europe/London` (also 0 -> +1 offset). Python also had a bug where it failed to assign FEN to the `America/Noronha` bucket. 24 + * **Resolution:** Confirmed the Python FEN assignment was a bug. Ensured TS uses a strict 4-value key (`std_offset`, `dst_offset`, `start_ts`, `end_ts`) for bucketing, which *should* correctly separate Casablanca if Luxon provides distinct transition timestamps. 25 + * **Status:** The strict bucketing logic is in place in TS. The final bucket count depends on Luxon's data/logic for 2025 Casablanca DST. 26 + 27 + 4. **DST Timestamp Discrepancy:** Systematic differences were observed between Luxon and `zoneinfo` timestamps for DST transitions. 28 + * **Root Cause:** Luxon returns the precise UTC timestamp of the offset change instant. `zoneinfo` returns the UTC timestamp corresponding to the *local* "wall clock" time of the transition (e.g., 2 AM local). 29 + * **Resolution:** Decided Luxon's approach (UTC instant) is technically more correct according to IANA definitions and prioritizes correctness over matching the previous Python implementation's specific behavior. 30 + * **Status:** Using Luxon's default timestamp calculation. 31 + 32 + 5. **`tzCommon.ts` Loop Accuracy:** The optimized day+binary search loop introduced for performance produced different results than the simple hour-by-hour loop. 33 + * **Observation:** The optimized loop resulted in 58 buckets, merging zones (like Beirut/Chisinau) that the slow loop correctly separated (resulting in 60 buckets). Timestamps also differed slightly. 34 + * **Resolution:** Prioritizing verifiable correctness, the decision was made to revert `tzCommon.ts` to the **slower, hour-by-hour iteration** method. 35 + * **Status:** The hour-by-hour loop is currently active in `tzCommon.ts`. 36 + 37 + **Current State (as of 2025-04-24T18:18:00Z):** 38 + 39 + * `scripts/generateAirportTzList.ts`: Contains the FEN data fix and uses strict 4-value bucketing. 40 + * `scripts/tzCommon.ts`: Contains the **slow, simple hour-by-hour loop** for `findDstTransitions`. 41 + * The last run (using the optimized loop) generated `src/c/airport_tz_list.c` with **58 buckets**. 42 + * The previous run using the *slow* loop generated **60 buckets**, which appeared more correct. 43 + 44 + **Next Immediate Step:** 45 + 46 + 1. Run `npm run generate:airport` with the current codebase (using the slow loop in `tzCommon.ts`). 47 + 2. Verify the console output reports **60 buckets**. 48 + 3. Inspect the generated `src/c/airport_tz_list.c` to confirm the structure and timestamps match expectations based on the previous reliable "slow loop" run (e.g., check separation of Beirut/Chisinau). 49 + 50 + **Future Considerations:** 51 + 52 + * If the ~4-minute runtime of the slow loop becomes unacceptable, revisit the optimized day+binary search in `tzCommon.ts` to identify and fix the cause of the timestamp discrepancies compared to the hour-by-hour method. 53 + * Consider adding automated tests comparing generated bucket counts and key timestamps against known values for specific years/zones.
+348
scripts/temp_py_out.c
··· 1 + // Auto-generated by generate_airport_tz_list.py 2 + // Year-specific DST data for 2025 3 + 4 + #include <stdint.h> 5 + 6 + static const char airport_code_pool[] = 7 + "PPG" "HNL" "KOA" "LIH" "ITO" "MKK" "JHM" "LNY" 8 + "AKB" "NHV" "GMR" "ANC" "LAX" "SFO" "LAS" "SEA" 9 + "YVR" "SAN" "PDX" "OAK" "SJC" "SMF" "PHX" "DEN" 10 + "SLC" "YYC" "YEG" "ABQ" "ELP" "MEX" "ORD" "DFW" 11 + "IAH" "MSP" "MDW" "DAL" "STL" "BNA" "AUS" "IPC" 12 + "CUN" "HAV" "ATL" "JFK" "YYZ" "CLT" "MCO" "MIA" 13 + "EWR" "BOS" "DTW" "FLL" "MUN" "CGB" "MAO" "SXM" 14 + "CGR" "PVH" "BVB" "YHZ" "SCL" "YYT" "GRU" "CGH" 15 + "BSB" "GIG" "AEP" "EZE" "CNF" "VCP" "SDU" "POA" 16 + "FEN" "FSP" "GOH" "SID" "PDL" "TER" "DKR" "LHR" 17 + "LGW" "DUB" "MAN" "LIS" "STN" "LTN" "EDI" "LPA" 18 + "BHX" "CMN" "ALG" "CDG" "AMS" "FRA" "MAD" "BCN" 19 + "MUC" "FCO" "ZRH" "CPH" "PMI" "JNB" "CPT" "TLV" 20 + "BEY" "BZY" "ATH" "HEL" "OTP" "KBP" "HER" "SOF" 21 + "CAI" "IST" "SVO" "DOH" "JED" "SAW" "DME" "AYT" 22 + "RUH" "VKO" "LED" "THR" "MHD" "IKA" "SYZ" "AWZ" 23 + "KIH" "IFN" "TBZ" "BND" "PGU" "DXB" "AUH" "SHJ" 24 + "MRU" "TBS" "KUF" "RUN" "DWC" "ASF" "BUS" "KBL" 25 + "SVX" "UFA" "TJM" "SGC" "CEK" "PEE" "NUX" "REN" 26 + "NJC" "SLY" "DEL" "BOM" "BLR" "CCU" "MAA" "HYD" 27 + "COK" "PNQ" "AMD" "GOI" "KTM" "OMS" "RGN" "MDL" 28 + "CGK" "BKK" "DMK" "SGN" "HAN" "SUB" "HKT" "KNO" 29 + "DAD" "CNX" "PEK" "HKG" "PVG" "CAN" "SIN" "KUL" 30 + "CTU" "SZX" "TPE" "KMG" "EUC" "HND" "ICN" "NRT" 31 + "CJU" "KIX" "GMP" "FUK" "CTS" "OKA" "PUS" "DRW" 32 + "ASP" "AYQ" "ADL" "PLO" "MGB" "OLP" "BNE" "OOL" 33 + "CNS" "VVO" "KHV" "TSV" "SYD" "MEL" "CBR" "HBA" 34 + "LDH" "VLI" "NLK" "NAN" "PKC" "AKL" "CHC" "WLG" 35 + "ZQN" "NSN" "DUD" "NPE" "PMR" "CHT" "APW" "CXI" 36 + ; 37 + 38 + static const char* airport_name_pool[] = { 39 + "Pago Pago", 40 + "Daniel K Inouye", 41 + "Ellison Onizuka Kona International At Keahole", 42 + "Lihue", 43 + "Hilo", 44 + "Molokai", 45 + "Kapalua", 46 + "Lanai", 47 + "Atka", 48 + "Nuku Hiva", 49 + "Totegegie", 50 + "Ted Stevens Anchorage", 51 + "Los Angeles", 52 + "San Francisco", 53 + "Harry Reid", 54 + "Seattle-Tacoma", 55 + "Vancouver", 56 + "San Diego", 57 + "Portland", 58 + "Metro Oakland", 59 + "Norman Y Mineta San Jose", 60 + "Sacramento", 61 + "Phoenix Sky Harbor", 62 + "Denver", 63 + "Salt Lake City", 64 + "Calgary", 65 + "Edmonton", 66 + "Albuquerque International Sunport", 67 + "El Paso", 68 + "Licenciado Benito Juarez", 69 + "Chicago O'Hare", 70 + "Dallas-Fort Worth", 71 + "George Bush Intcntl/Houston", 72 + "Minneapolis-St Paul International/Wold-Chamberlain", 73 + "Chicago Midway", 74 + "Dallas Love Field", 75 + "St Louis Lambert", 76 + "Nashville", 77 + "Austin-Bergstrom", 78 + "Mataveri", 79 + "Cancun", 80 + "Jose Marti", 81 + "Hartsfield - Jackson Atlanta", 82 + "John F Kennedy", 83 + "Toronto Pearson", 84 + "Charlotte/Douglas", 85 + "Orlando", 86 + "Miami", 87 + "Newark Liberty", 88 + "General Edward Lawrence Logan", 89 + "Detroit Metro Wayne County", 90 + "Fort Lauderdale/Hollywood", 91 + "Maturin", 92 + "Marechal Rondon", 93 + "Eduardo Gomes", 94 + "Princess Juliana", 95 + "Campo Grande", 96 + "Governador Jorge Teixeira de Oliveira", 97 + "Atlas Brasil Cantanhede", 98 + "Halifax Robert L. Stanfield", 99 + "Comodoro Arturo Merino Benitez", 100 + "St. John's", 101 + "Guarulhos - Governador Andre Franco Montoro", 102 + "Congonhas", 103 + "Presidente Juscelino Kubistschek", 104 + "Galeao - Antonio Carlos Jobim", 105 + "Jorge Newbery Airpark", 106 + "Ministro Pistarini", 107 + "Tancredo Neves", 108 + "Viracopos", 109 + "Santos Dumont", 110 + "Salgado Filho", 111 + "Fernando de Noronha", 112 + "St Pierre", 113 + "Godthaab / Nuuk", 114 + "Amilcar Cabral", 115 + "João Paulo II", 116 + "Lajes Field", 117 + "Leopold Sedar Senghor", 118 + "London Heathrow", 119 + "London Gatwick", 120 + "Dublin", 121 + "Manchester", 122 + "Lisbon Portela", 123 + "London Stansted", 124 + "London Luton", 125 + "Edinburgh", 126 + "Gran Canaria", 127 + "Birmingham", 128 + "Mohammed V", 129 + "Houari Boumediene", 130 + "Charles de Gaulle", 131 + "Amsterdam Airport Schiphol", 132 + "Frankfurt am Main", 133 + "Madrid Barajas", 134 + "Barcelona", 135 + "Munich", 136 + "Leonardo Da Vinci (Fiumicino)", 137 + "Zurich", 138 + "Copenhagen Kastrup", 139 + "Palma De Mallorca", 140 + "O. R. Tambo", 141 + "Cape Town", 142 + "Ben Gurion", 143 + "Beirut Rafic Hariri", 144 + "Balti", 145 + "Eleftherios Venizelos", 146 + "Helsinki Vantaa", 147 + "Henri Coanda", 148 + "Boryspil", 149 + "Heraklion International Nikos Kazantzakis", 150 + "Sofia", 151 + "Cairo", 152 + "Istanbul", 153 + "Sheremetyevo", 154 + "Hamad", 155 + "King Abdulaziz", 156 + "Sabiha Gokcen", 157 + "Domodedovo", 158 + "Antalya", 159 + "King Khaled", 160 + "Vnukovo", 161 + "Pulkovo", 162 + "Mehrabad", 163 + "Mashhad", 164 + "Imam Khomeini", 165 + "Shiraz Shahid Dastghaib", 166 + "Ahwaz", 167 + "Kish", 168 + "Esfahan Shahid Beheshti", 169 + "Tabriz", 170 + "Bandar Abbas", 171 + "Persian Gulf", 172 + "Dubai", 173 + "Abu Dhabi", 174 + "Sharjah", 175 + "Sir Seewoosagur Ramgoolam", 176 + "Tbilisi", 177 + "Kurumoch", 178 + "Roland Garros", 179 + "Al Maktoum", 180 + "Astrakhan", 181 + "Batumi", 182 + "Kabul", 183 + "Koltsovo", 184 + "Ufa", 185 + "Roshchino", 186 + "Surgut", 187 + "Chelyabinsk Balandino", 188 + "Bolshoye Savino", 189 + "Novy Urengoy", 190 + "Orenburg Central", 191 + "Nizhnevartovsk", 192 + "Salekhard", 193 + "Indira Gandhi", 194 + "Chhatrapati Shivaji", 195 + "Bengaluru", 196 + "Netaji Subhash Chandra Bose", 197 + "Chennai", 198 + "Rajiv Gandhi International Airport Shamshabad", 199 + "Cochin", 200 + "Pune", 201 + "Sardar Vallabhbhai Patel", 202 + "Dabolim", 203 + "Tribhuvan", 204 + "Omsk Central", 205 + "Yangon", 206 + "Mandalay", 207 + "Soekarno-Hatta", 208 + "Suvarnabhumi", 209 + "Don Mueang", 210 + "Tan Son Nhat", 211 + "Noi Bai", 212 + "Juanda", 213 + "Phuket", 214 + "Polonia", 215 + "Da Nang", 216 + "Chiang Mai", 217 + "Beijing Capital", 218 + "Chek Lap Kok", 219 + "Shanghai Pudong", 220 + "Guangzhou Baiyun", 221 + "Singapore Changi", 222 + "Kuala Lumpur", 223 + "Chengdu Shuangliu", 224 + "Shenzhen Bao'an", 225 + "Taiwan Taoyuan", 226 + "Kunming Wujiaba", 227 + "Eucla", 228 + "Tokyo", 229 + "Incheon", 230 + "Narita", 231 + "Jeju", 232 + "Kansai", 233 + "Gimpo", 234 + "Fukuoka", 235 + "New Chitose", 236 + "Naha", 237 + "Gimhae", 238 + "Darwin", 239 + "Alice Springs", 240 + "Ayers Rock Connellan", 241 + "Adelaide", 242 + "Port Lincoln", 243 + "Mount Gambier", 244 + "Olympic Dam", 245 + "Brisbane", 246 + "Gold Coast", 247 + "Cairns", 248 + "Vladivostok", 249 + "Khabarovsk-Novy", 250 + "Townsville", 251 + "Sydney Kingsford Smith", 252 + "Melbourne", 253 + "Canberra", 254 + "Hobart", 255 + "Lord Howe Island", 256 + "Port Vila Bauerfield", 257 + "Norfolk Island", 258 + "Nadi", 259 + "Yelizovo", 260 + "Auckland", 261 + "Christchurch", 262 + "Wellington", 263 + "Queenstown", 264 + "Nelson", 265 + "Dunedin", 266 + "Napier", 267 + "Palmerston North", 268 + "Chatham Islands-Tuuta", 269 + "Faleolo", 270 + "Cassidy", 271 + }; 272 + 273 + typedef struct { 274 + float std_offset_hours; 275 + float dst_offset_hours; 276 + int64_t dst_start_utc; 277 + int64_t dst_end_utc; 278 + int name_offset; 279 + int name_count; 280 + } TzInfo; 281 + 282 + static const TzInfo airport_tz_list[] = { 283 + { -11.00f, -11.00f, 0LL, 0LL, 0, 1 }, 284 + { -10.00f, -10.00f, 0LL, 0LL, 1, 7 }, 285 + { -10.00f, -9.00f, 1741489200LL, 1762048800LL, 8, 1 }, 286 + { -9.50f, -9.50f, 0LL, 0LL, 9, 1 }, 287 + { -9.00f, -9.00f, 0LL, 0LL, 10, 1 }, 288 + { -9.00f, -8.00f, 1741489200LL, 1762048800LL, 11, 1 }, 289 + { -8.00f, -7.00f, 1741489200LL, 1762048800LL, 12, 10 }, 290 + { -7.00f, -7.00f, 0LL, 0LL, 22, 1 }, 291 + { -7.00f, -6.00f, 1741489200LL, 1762048800LL, 23, 6 }, 292 + { -6.00f, -6.00f, 0LL, 0LL, 29, 1 }, 293 + { -6.00f, -5.00f, 1741489200LL, 1762048800LL, 30, 9 }, 294 + { -6.00f, -5.00f, 1757199600LL, 1743890400LL, 39, 1 }, 295 + { -5.00f, -5.00f, 0LL, 0LL, 40, 1 }, 296 + { -5.00f, -4.00f, 1741482000LL, 1762045200LL, 41, 1 }, 297 + { -5.00f, -4.00f, 1741489200LL, 1762048800LL, 42, 10 }, 298 + { -4.00f, -4.00f, 0LL, 0LL, 52, 7 }, 299 + { -4.00f, -3.00f, 1741489200LL, 1762048800LL, 59, 1 }, 300 + { -4.00f, -3.00f, 1757206800LL, 1743897600LL, 60, 1 }, 301 + { -3.50f, -2.50f, 1741489200LL, 1762048800LL, 61, 1 }, 302 + { -3.00f, -3.00f, 0LL, 0LL, 62, 11 }, 303 + { -3.00f, -2.00f, 1741489200LL, 1762048800LL, 73, 1 }, 304 + { -2.00f, -2.00f, 0LL, 0LL, 74, 0 }, 305 + { -2.00f, -1.00f, 1743292800LL, 1761436800LL, 74, 1 }, 306 + { -1.00f, -1.00f, 0LL, 0LL, 75, 1 }, 307 + { -1.00f, 0.00f, 1743296400LL, 1761440400LL, 76, 2 }, 308 + { 0.00f, 0.00f, 0LL, 0LL, 78, 1 }, 309 + { 0.00f, 1.00f, 1743300000LL, 1761444000LL, 79, 10 }, 310 + { 0.00f, 1.00f, 1743908400LL, 1740279600LL, 89, 1 }, 311 + { 1.00f, 1.00f, 0LL, 0LL, 90, 1 }, 312 + { 1.00f, 2.00f, 1743303600LL, 1761447600LL, 91, 10 }, 313 + { 2.00f, 2.00f, 0LL, 0LL, 101, 2 }, 314 + { 2.00f, 3.00f, 1743130800LL, 1761444000LL, 103, 1 }, 315 + { 2.00f, 3.00f, 1743296400LL, 1761436800LL, 104, 1 }, 316 + { 2.00f, 3.00f, 1743303600LL, 1761447600LL, 105, 1 }, 317 + { 2.00f, 3.00f, 1743307200LL, 1761451200LL, 106, 6 }, 318 + { 2.00f, 3.00f, 1745542800LL, 1761868800LL, 112, 1 }, 319 + { 3.00f, 3.00f, 0LL, 0LL, 113, 10 }, 320 + { 3.50f, 3.50f, 0LL, 0LL, 123, 10 }, 321 + { 4.00f, 4.00f, 0LL, 0LL, 133, 10 }, 322 + { 4.50f, 4.50f, 0LL, 0LL, 143, 1 }, 323 + { 5.00f, 5.00f, 0LL, 0LL, 144, 10 }, 324 + { 5.50f, 5.50f, 0LL, 0LL, 154, 10 }, 325 + { 5.75f, 5.75f, 0LL, 0LL, 164, 1 }, 326 + { 6.00f, 6.00f, 0LL, 0LL, 165, 1 }, 327 + { 6.50f, 6.50f, 0LL, 0LL, 166, 2 }, 328 + { 7.00f, 7.00f, 0LL, 0LL, 168, 10 }, 329 + { 8.00f, 8.00f, 0LL, 0LL, 178, 10 }, 330 + { 8.75f, 8.75f, 0LL, 0LL, 188, 1 }, 331 + { 9.00f, 9.00f, 0LL, 0LL, 189, 10 }, 332 + { 9.50f, 9.50f, 0LL, 0LL, 199, 3 }, 333 + { 9.50f, 10.50f, 1759633200LL, 1743908400LL, 202, 4 }, 334 + { 10.00f, 10.00f, 0LL, 0LL, 206, 6 }, 335 + { 10.00f, 11.00f, 1759633200LL, 1743908400LL, 212, 4 }, 336 + { 10.50f, 11.00f, 1759633200LL, 1743904800LL, 216, 1 }, 337 + { 11.00f, 11.00f, 0LL, 0LL, 217, 1 }, 338 + { 11.00f, 12.00f, 1759633200LL, 1743908400LL, 218, 1 }, 339 + { 12.00f, 12.00f, 0LL, 0LL, 219, 2 }, 340 + { 12.00f, 13.00f, 1759028400LL, 1743908400LL, 221, 8 }, 341 + { 12.75f, 13.75f, 1759032000LL, 1743912000LL, 229, 1 }, 342 + { 13.00f, 13.00f, 0LL, 0LL, 230, 1 }, 343 + { 14.00f, 14.00f, 0LL, 0LL, 231, 1 }, 344 + }; 345 + 346 + #define AIRPORT_TZ_LIST_COUNT (sizeof(airport_tz_list)/sizeof(airport_tz_list[0])) 347 + #define AIRPORT_CODE_POOL_COUNT (sizeof(airport_code_pool)/3) 348 + #define AIRPORT_NAME_POOL_COUNT (sizeof(airport_name_pool)/sizeof(airport_name_pool[0]))
+19
scripts/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2020", 4 + "module": "CommonJS", 5 + "strict": true, 6 + "esModuleInterop": true, 7 + "moduleResolution": "node", 8 + "outDir": "dist", // Output JS within scripts/dist 9 + "rootDir": ".", // Root is the scripts directory 10 + "resolveJsonModule": true, 11 + "skipLibCheck": true, 12 + "baseUrl": ".", // Helps with potential pathing issues 13 + "paths": { 14 + "*": ["node_modules/*", "types/*"] // Look in local node_modules and types 15 + } 16 + }, 17 + "include": ["./**/*.ts"], // Include all .ts files within scripts/ 18 + "exclude": ["node_modules", "dist"] 19 + }
+99
scripts/tzCommon.test.ts
··· 1 + import { execSync } from 'child_process'; 2 + import path from 'path'; 3 + import { findDstTransitions, DstTransitions } from './tzCommon'; 4 + 5 + // Function to get expected result from Python script 6 + const getExpectedFromPython = (zone: string, year: number): DstTransitions => { 7 + const pythonScriptPath = path.resolve(__dirname, 'tz_common.py'); 8 + const command = `python3 "${pythonScriptPath}" "${zone}" ${year}`; 9 + try { 10 + const stdout = execSync(command, { encoding: 'utf-8' }); 11 + // Parse the output tuple string, e.g., "(-18000, -14400, 1741503600, 1762063200)\n" 12 + const match = stdout.trim().match(/^\((.*)\)$/); 13 + if (match && match[1]) { 14 + const values = match[1].split(', ').map(Number); 15 + if (values.length === 4 && values.every(v => !isNaN(v))) { 16 + return values as DstTransitions; 17 + } 18 + } 19 + throw new Error(`Failed to parse Python output: ${stdout}`); 20 + } catch (error: any) { 21 + console.error(`Error executing Python script for ${zone}/${year}:`, error.stderr || error.message); 22 + // Return a default/error value or rethrow, depending on desired test behavior 23 + // Returning [0,0,0,0] for invalid zones handled by Python script itself 24 + if (error.stderr && error.stderr.includes('Invalid zone')) { 25 + // Match the python script's likely return for truly invalid IANA names 26 + // Note: findDstTransitions in Python returns (0,0,0,0) on initial get_tz_details failure. 27 + return [0, 0, 0, 0]; 28 + } 29 + // Rethrow for unexpected errors (e.g., python not found, script errors) 30 + throw new Error(`Python script execution failed for ${zone}/${year}: ${error.message}`); 31 + } 32 + }; 33 + 34 + // Generated for year 2025 by generateExpectedTransitions.ts 35 + const expectedTransitions: { zone: string; expected: DstTransitions }[] = [ 36 + { 37 + zone: 'America/New_York', 38 + expected: [-18000, -14400, 1741503600, 1762063200] as DstTransitions, 39 + }, 40 + { 41 + zone: 'Europe/London', 42 + expected: [0, 3600, 1743296400, 1761440400] as DstTransitions, 43 + }, 44 + { 45 + zone: 'Pacific/Auckland', 46 + expected: [43200, 46800, 1758981600, 1743861600] as DstTransitions, 47 + }, 48 + { 49 + zone: 'Asia/Tokyo', 50 + expected: [32400, 32400, 0, 0] as DstTransitions, 51 + }, 52 + { 53 + zone: 'Pacific/Apia', 54 + // Note: Expected output shows Apia has no DST in 2025 according to Luxon/IANA data used. 55 + expected: [46800, 46800, 0, 0] as DstTransitions, 56 + }, 57 + { 58 + zone: 'Australia/Lord_Howe', 59 + expected: [37800, 39600, 1759593600, 1743865200] as DstTransitions, 60 + }, 61 + { 62 + zone: 'America/Denver', 63 + expected: [-25200, -21600, 1741510800, 1762070400] as DstTransitions, 64 + }, 65 + { 66 + zone: 'Asia/Katmandu', 67 + expected: [20700, 20700, 0, 0] as DstTransitions, 68 + }, 69 + { 70 + zone: 'Invalid/Zone', 71 + expected: [0, 0, 0, 0] as DstTransitions, 72 + }, 73 + ]; 74 + 75 + describe('tzCommon', () => { 76 + describe('findDstTransitions', () => { 77 + const year = 2025; // Match the year used for generation 78 + 79 + test.each(expectedTransitions)('should match generated expected value for $zone in $year', ({ zone, expected }) => { 80 + const result = findDstTransitions(zone, year); 81 + 82 + // Explicitly compare each element 83 + expect(result[0]).toBe(expected[0]); // stdOffsetSeconds 84 + expect(result[1]).toBe(expected[1]); // dstOffsetSeconds 85 + expect(result[2]).toBe(expected[2]); // dstStartUtcTimestamp 86 + expect(result[3]).toBe(expected[3]); // dstEndUtcTimestamp 87 + }); 88 + 89 + // Keep cache test as it verifies TS internal behavior 90 + test('should use cache for repeated calls', () => { 91 + const zone = 'America/Los_Angeles'; 92 + const year = 2026; // Use a different year 93 + 94 + const result1 = findDstTransitions(zone, year); 95 + const result2 = findDstTransitions(zone, year); 96 + expect(result1).toEqual(result2); 97 + }); 98 + }); 99 + });
+200
scripts/tzCommon.ts
··· 1 + import { DateTime } from 'luxon'; 2 + import { IANAZone } from 'luxon'; 3 + 4 + /** 5 + * Cache for findDstTransitions results. Key: "zoneName:year" 6 + */ 7 + const transitionCache = new Map<string, DstTransitions>(); 8 + 9 + /** 10 + * Type definition for the return value of findDstTransitions. 11 + */ 12 + export type DstTransitions = [ 13 + stdOffsetSeconds: number, 14 + dstOffsetSeconds: number, 15 + dstStartUtcTimestamp: number, 16 + dstEndUtcTimestamp: number, 17 + ]; 18 + 19 + /** 20 + * Represents the details of a timezone at a specific moment. 21 + */ 22 + interface TzDetails { 23 + offsetSeconds: number; 24 + isDST: boolean; 25 + } 26 + 27 + /** 28 + * Return (offset_seconds, isDST) or undefined if the timezone is invalid or offset is null. 29 + */ 30 + export function getTzDetails(zoneName: string, dt: DateTime): TzDetails | undefined { 31 + if (!IANAZone.isValidZone(zoneName)) { 32 + return undefined; 33 + } 34 + // Ensure the input DateTime is in UTC before setting the zone 35 + const dtInZone = dt.setZone(zoneName); 36 + 37 + if (!dtInZone.isValid) { 38 + // Failed to apply the zone 39 + console.warn(`[tzCommon] Failed to set zone ${zoneName} for datetime ${dt.toISO()}`); 40 + return undefined; 41 + } 42 + 43 + const offsetMinutes = dtInZone.offset; 44 + const isDST = dtInZone.isInDST; 45 + 46 + // offset can be null if invalid, checked by dtInZone.isValid 47 + const offsetSeconds = offsetMinutes * 60; 48 + return { offsetSeconds, isDST }; 49 + } 50 + 51 + /** 52 + * Return [std_offset_sec, dst_offset_sec, dst_start_utc_ts, dst_end_utc_ts]. 53 + * If the zone does not observe DST, std == dst and transition timestamps are 0. 54 + * Caches results per zone/year combination. 55 + */ 56 + export function findDstTransitions(zoneName: string, year: number): DstTransitions { 57 + const cacheKey = `${zoneName}:${year}`; 58 + if (transitionCache.has(cacheKey)) { 59 + return transitionCache.get(cacheKey)!; 60 + } 61 + 62 + // Validate zone early 63 + if (!IANAZone.isValidZone(zoneName)) { 64 + return [0, 0, 0, 0]; 65 + } 66 + 67 + /* 68 + * Optimised algorithm 69 + * ------------------- 70 + * 1. Walk through the year in STEP_HOURS chunks (default 6h) instead of hour-by-hour. 71 + * 2. When we detect a DST state change between two checkpoints we perform a binary 72 + * search down to *hour* granularity to locate the transition start hour. This 73 + * guarantees the **same output** as the previous naive hour-by-hour loop while 74 + * reducing the worst-case iterations by ~6× (1460 vs 8760 for a non-leap year). 75 + * 3. We collect the first STD→DST transition (startTs) and the first DST→STD 76 + * transition (endTs) that occur **inside the target year**. 77 + */ 78 + 79 + const STEP_HOURS = 6; // coarse step, must be power-of-two divisor of 24. 80 + const startOfYear = DateTime.utc(year, 1, 1); 81 + const startCursor = startOfYear.minus({ hours: 1 }); // one hour before the year 82 + const endBoundary = DateTime.utc(year + 1, 1, 1).plus({ hours: 2 }); // small buffer 83 + 84 + let cursor = startCursor; 85 + const firstDetails = getTzDetails(zoneName, cursor); 86 + if (!firstDetails) { 87 + // Should never happen for valid zones but keep behaviour consistent. 88 + return [0, 0, 0, 0]; 89 + } 90 + 91 + let prevIsDST = firstDetails.isDST; 92 + let prevOffsetSec = firstDetails.offsetSeconds; 93 + 94 + // Track discovered offsets 95 + let stdOffsetSec: number | null = prevIsDST ? null : prevOffsetSec; 96 + let dstOffsetSec: number | null = prevIsDST ? prevOffsetSec : null; 97 + 98 + let startTs = 0; 99 + let endTs = 0; 100 + 101 + // Helper: binary search between two datetimes (inclusive lower, exclusive upper) 102 + // to find the *first* hour whose DST flag differs from the lower bound. 103 + const refineTransitionHour = ( 104 + lower: DateTime, 105 + upper: DateTime, 106 + lowerIsDST: boolean, 107 + ): DateTime => { 108 + // Ensure bounds differ in DST state 109 + let low = lower; 110 + let high = upper; 111 + while (high.diff(low, 'hours').hours > 1) { 112 + const mid = low.plus({ milliseconds: high.diff(low).as('milliseconds') / 2 }); 113 + const midDetails = getTzDetails(zoneName, mid) as TzDetails; // assume valid 114 + if (midDetails.isDST === lowerIsDST) { 115 + low = mid; 116 + } else { 117 + high = mid; 118 + } 119 + } 120 + // `high` is now within the first hour of the new DST state 121 + return high.startOf('hour'); 122 + }; 123 + 124 + // Coarse walk across the year 125 + while (cursor <= endBoundary) { 126 + const nextCursor = cursor.plus({ hours: STEP_HOURS }); 127 + const details = getTzDetails(zoneName, nextCursor); 128 + if (!details) { 129 + // On theoretically invalid zones continue (should not happen) 130 + cursor = nextCursor; 131 + continue; 132 + } 133 + 134 + const curIsDST = details.isDST; 135 + const curOffsetSec = details.offsetSeconds; 136 + 137 + // Track offsets 138 + if (curIsDST) { 139 + dstOffsetSec = curOffsetSec; 140 + } else { 141 + stdOffsetSec = curOffsetSec; 142 + } 143 + 144 + // Detect toggle between prev and current checkpoints 145 + if (curIsDST !== prevIsDST) { 146 + // Refine to hour-precision between cursor and nextCursor 147 + const transitionHour = refineTransitionHour(cursor, nextCursor, prevIsDST); 148 + const transitionTs = Math.floor(transitionHour.toMillis() / 1000); 149 + 150 + // Determine direction and assign start/end if the transition sits in target year 151 + if (!prevIsDST && curIsDST) { 152 + if (transitionHour.year === year) { 153 + startTs = transitionTs; // first second OF new hour when DST begins 154 + } 155 + } else if (prevIsDST && !curIsDST) { 156 + if (transitionHour.year === year) { 157 + endTs = transitionTs; // first second OF new hour when DST ends 158 + } 159 + } 160 + 161 + // Update prevIsDST to new state for subsequent iterations 162 + prevIsDST = curIsDST; 163 + prevOffsetSec = curOffsetSec; 164 + 165 + // Move cursor forward to just after the transition to avoid re-detecting same one 166 + cursor = transitionHour.plus({ hours: 1 }); 167 + continue; 168 + } 169 + 170 + // No toggle, advance cursor 171 + prevIsDST = curIsDST; 172 + prevOffsetSec = curOffsetSec; 173 + cursor = nextCursor; 174 + } 175 + 176 + // Final fallbacks – replicate slow loop behaviour 177 + if (stdOffsetSec === null && dstOffsetSec !== null) { 178 + stdOffsetSec = dstOffsetSec; 179 + } 180 + if (dstOffsetSec === null && stdOffsetSec !== null) { 181 + dstOffsetSec = stdOffsetSec; 182 + } 183 + if (stdOffsetSec === null) { 184 + stdOffsetSec = prevOffsetSec; 185 + } 186 + if (dstOffsetSec === null) { 187 + dstOffsetSec = stdOffsetSec; 188 + } 189 + 190 + // Treat zones with <60-second difference as non-DST 191 + if (Math.abs(stdOffsetSec - dstOffsetSec) < 60) { 192 + dstOffsetSec = stdOffsetSec; 193 + startTs = 0; 194 + endTs = 0; 195 + } 196 + 197 + const result: DstTransitions = [stdOffsetSec, dstOffsetSec, startTs, endTs]; 198 + transitionCache.set(cacheKey, result); 199 + return result; 200 + }
+30 -1
scripts/tz_common.py
··· 72 72 end_ts = 0 73 73 dst_offset_sec = std_offset_sec 74 74 75 - return std_offset_sec, dst_offset_sec, start_ts, end_ts 75 + return std_offset_sec, dst_offset_sec, start_ts, end_ts 76 + 77 + # --- Main execution (for CLI testing) --- 78 + if __name__ == "__main__": 79 + import sys 80 + if len(sys.argv) != 3: 81 + print("Usage: python tz_common.py <timezone_name> <year>", file=sys.stderr) 82 + sys.exit(1) 83 + 84 + tz_name_arg = sys.argv[1] 85 + try: 86 + year_arg = int(sys.argv[2]) 87 + except ValueError: 88 + print(f"Error: Invalid year '{sys.argv[2]}'", file=sys.stderr) 89 + sys.exit(1) 90 + 91 + # Ensure zoneinfo is available if needed by get_tz_details 92 + try: 93 + import zoneinfo 94 + except ImportError: 95 + print("Error: Python 3.9+ with zoneinfo is required to run this script directly.", file=sys.stderr) 96 + sys.exit(1) 97 + 98 + try: 99 + result = find_dst_transitions(tz_name_arg, year_arg) 100 + # Print tuple directly for easy parsing 101 + print(result) 102 + except Exception as e: 103 + print(f"Error processing {tz_name_arg} for {year_arg}: {e}", file=sys.stderr) 104 + sys.exit(1)