diff --git a/.gitignore b/.gitignore index e20f678a..a9ad5a15 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,10 @@ .dev/ .mint +# Node / pnpm +node_modules/ +.pnpm-store/ + # Swift / Xcode / SwiftPM .build/ .swiftpm/ diff --git a/dev.yml b/dev.yml index 6133bdc7..dafe1e4b 100644 --- a/dev.yml +++ b/dev.yml @@ -2,7 +2,6 @@ name: checkout-kit up: - packages: - - quicktype - rover - mint - xcbeautify @@ -21,6 +20,7 @@ up: version: v22.14.0 package_manager: pnpm@10.33.1 packages: + - protocol - platforms/react-native - platforms/web - custom: @@ -137,6 +137,9 @@ commands: update-upstream: desc: Update vendored UCP snapshot files from upstream run: ./protocol/scripts/update_ucp_snapshot.sh "$@" + check-tools: + desc: Verify protocol codegen tools match protocol/package.json + run: ./protocol/scripts/check_codegen_tools.sh "$@" # Android android: diff --git a/protocol/package.json b/protocol/package.json new file mode 100644 index 00000000..cbdcc13b --- /dev/null +++ b/protocol/package.json @@ -0,0 +1,17 @@ +{ + "name": "@shopify/checkout-kit-protocol-tooling", + "version": "0.0.0", + "private": true, + "license": "MIT", + "description": "Tooling for generating Shopify Checkout Kit protocol models.", + "packageManager": "pnpm@10.33.1+sha512.05ba3c1d5d1c18f68df06470d74055e62d41fc110a0c660db1b2dfb2785327f04cf0f68345d4609bc52089e7fa0343c31593b2f9594e2c5d5da426230acc9820", + "scripts": { + "check-tools": "./scripts/check_codegen_tools.sh", + "codegen:kotlin": "./scripts/generate_models.sh --lang kotlin", + "codegen:swift": "./scripts/generate_models.sh --lang swift", + "codegen:typescript": "./scripts/generate_models.sh --lang typescript" + }, + "devDependencies": { + "quicktype": "23.2.6" + } +} diff --git a/protocol/pnpm-lock.yaml b/protocol/pnpm-lock.yaml new file mode 100644 index 00000000..0491d303 --- /dev/null +++ b/protocol/pnpm-lock.yaml @@ -0,0 +1,838 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + quicktype: + specifier: 23.2.6 + version: 23.2.6 + +packages: + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@glideapps/ts-necessities@2.2.3': + resolution: {integrity: sha512-gXi0awOZLHk3TbW55GZLCPP6O+y/b5X1pBXKBVckFONSwF1z1E5ND2BGJsghQFah+pW7pkkyFb2VhUQI2qhL5w==} + + '@glideapps/ts-necessities@2.4.0': + resolution: {integrity: sha512-mDC+qosuNa4lxR3ioMBb6CD0XLRsQBplU+zRPUYiMLXKeVPZ6UYphdNG/EGReig0YyfnVlBKZEXl1wzTotYmPA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@mark.probst/typescript-json-schema@0.55.0': + resolution: {integrity: sha512-jI48mSnRgFQxXiE/UTUCVCpX8lK3wCFKLF1Ss2aEreboKNuLQGt3e0/YFqWVHe/WENxOaqiJvwOz+L/SrN2+qQ==} + hasBin: true + + '@tsconfig/node10@1.0.12': + resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@16.18.126': + resolution: {integrity: sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} + engines: {node: '>=0.4.0'} + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + array-back@3.1.0: + resolution: {integrity: sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==} + engines: {node: '>=6'} + + array-back@6.2.3: + resolution: {integrity: sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw==} + engines: {node: '>=12.17'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + + browser-or-node@3.0.0: + resolution: {integrity: sha512-iczIdVJzGEYhP5DqQxYM9Hh7Ztpqqi+CXZpSmX8ALFs9ecXkQIeqRyM6TfxEfMVpwhl3dSuDvxdzzo9sUOIVBQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + chalk-template@0.4.0: + resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==} + engines: {node: '>=12'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + collection-utils@1.0.1: + resolution: {integrity: sha512-LA2YTIlR7biSpXkKYwwuzGjwL5rjWEZVOSnvdUc7gObvWe4WkjxOpfrdhoP7Hs09YWDVfg0Mal9BpAqLfVEzQg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + command-line-args@5.2.1: + resolution: {integrity: sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==} + engines: {node: '>=4.0.0'} + + command-line-usage@7.0.4: + resolution: {integrity: sha512-85UdvzTNx/+s5CkSgBm/0hzP80RFHAa7PsfeADE5ezZF3uHz3/Tqj9gIKGT9PTtpycc3Ua64T0oVulGfKxzfqg==} + engines: {node: '>=12.20.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-fetch@4.1.0: + resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==} + + diff@4.0.4: + resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} + engines: {node: '>=0.3.1'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + find-replace@3.0.0: + resolution: {integrity: sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==} + engines: {node: '>=4.0.0'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + graphql@0.11.7: + resolution: {integrity: sha512-x7uDjyz8Jx+QPbpCFCMQ8lltnQa4p4vSYHx6ADe8rVYRTdsyhCJbvSty5DAsLVmU6cGakl+r8HQYolKHxk/tiw==} + deprecated: 'No longer supported; please update to a newer version. Details: https://github.com/graphql/graphql-js#version-support' + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-url@1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + + iterall@1.1.3: + resolution: {integrity: sha512-Cu/kb+4HiNSejAPhSaN1VukdNTTi/r4/e+yykqjlG/IW+1gZH5b4+Bq3whDX4tvbYugta3r8KTMUiqT3fIGxuQ==} + + js-base64@3.7.8: + resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + + path-equal@1.2.5: + resolution: {integrity: sha512-i73IctDr3F2W+bsOWDyyVm/lqsXO47aY9nsFZUjTT/aljSbkxHxxCoyZ9UUrM8jK0JVod+An+rl48RCsvWM+9g==} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + quicktype-core@23.2.6: + resolution: {integrity: sha512-asfeSv7BKBNVb9WiYhFRBvBZHcRutPRBwJMxW0pefluK4kkKu4lv0IvZBwFKvw2XygLcL1Rl90zxWDHYgkwCmA==} + + quicktype-graphql-input@23.2.6: + resolution: {integrity: sha512-jHQ8XrEaccZnWA7h/xqUQhfl+0mR5o91T6k3I4QhlnZSLdVnbycrMq4FHa9EaIFcai783JKwSUl1+koAdJq4pg==} + + quicktype-typescript-input@23.2.6: + resolution: {integrity: sha512-dCNMxR+7PGs9/9Tsth9H6LOQV1G+Tv4sUGT8ZUfDRJ5Hq371qOYLma5BnLX6VxkPu8JT7mAMpQ9VFlxstX6Qaw==} + + quicktype@23.2.6: + resolution: {integrity: sha512-rlD1jF71bOmDn6SQ/ToLuuRkMQ7maxo5oVTn5dPCl11ymqoJCFCvl7FzRfh+fkDFmWt2etl+JiIEdWImLxferA==} + engines: {node: '>=18.12.0'} + hasBin: true + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readable-stream@4.5.2: + resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readable-stream@4.7.0: + resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + stream-chain@2.2.5: + resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==} + + stream-json@1.8.0: + resolution: {integrity: sha512-HZfXngYHUAr1exT4fxlbc1IOce1RYxp2ldeaf97LYCOPSoOqY/1Psp7iGvpb+6JIOgkra9zDYnPX01hGAHzEPw==} + + string-to-stream@3.0.1: + resolution: {integrity: sha512-Hl092MV3USJuUCC6mfl9sPzGloA3K5VwdIeJjYIkXY/8K+mUvaeEabWJgArp+xXrsWxCajeT2pc4axbVhIZJyg==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + table-layout@4.1.1: + resolution: {integrity: sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==} + engines: {node: '>=12.17'} + + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + typescript@4.9.4: + resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==} + engines: {node: '>=4.2.0'} + hasBin: true + + typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + typical@4.0.0: + resolution: {integrity: sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==} + engines: {node: '>=8'} + + typical@7.3.0: + resolution: {integrity: sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==} + engines: {node: '>=12.17'} + + unicode-properties@1.4.1: + resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==} + + unicode-trie@2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + + urijs@1.19.11: + resolution: {integrity: sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + wordwrapjs@5.1.1: + resolution: {integrity: sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==} + engines: {node: '>=12.17'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + +snapshots: + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@glideapps/ts-necessities@2.2.3': {} + + '@glideapps/ts-necessities@2.4.0': {} + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@mark.probst/typescript-json-schema@0.55.0': + dependencies: + '@types/json-schema': 7.0.15 + '@types/node': 16.18.126 + glob: 7.2.3 + path-equal: 1.2.5 + safe-stable-stringify: 2.5.0 + ts-node: 10.9.2(@types/node@16.18.126)(typescript@4.9.4) + typescript: 4.9.4 + yargs: 17.7.2 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + + '@tsconfig/node10@1.0.12': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@16.18.126': {} + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + acorn-walk@8.3.5: + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + arg@4.1.3: {} + + array-back@3.1.0: {} + + array-back@6.2.3: {} + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + brace-expansion@1.1.14: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + browser-or-node@3.0.0: {} + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + chalk-template@0.4.0: + dependencies: + chalk: 4.1.2 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + collection-utils@1.0.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + command-line-args@5.2.1: + dependencies: + array-back: 3.1.0 + find-replace: 3.0.0 + lodash.camelcase: 4.3.0 + typical: 4.0.0 + + command-line-usage@7.0.4: + dependencies: + array-back: 6.2.3 + chalk-template: 0.4.0 + table-layout: 4.1.1 + typical: 7.3.0 + + concat-map@0.0.1: {} + + create-require@1.1.1: {} + + cross-fetch@4.1.0: + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + diff@4.0.4: {} + + emoji-regex@8.0.0: {} + + escalade@3.2.0: {} + + event-target-shim@5.0.1: {} + + events@3.3.0: {} + + find-replace@3.0.0: + dependencies: + array-back: 3.1.0 + + fs.realpath@1.0.0: {} + + get-caller-file@2.0.5: {} + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + graphql@0.11.7: + dependencies: + iterall: 1.1.3 + + has-flag@4.0.0: {} + + ieee754@1.2.1: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + is-fullwidth-code-point@3.0.0: {} + + is-url@1.2.4: {} + + iterall@1.1.3: {} + + js-base64@3.7.8: {} + + lodash.camelcase@4.3.0: {} + + lodash@4.18.1: {} + + make-error@1.3.6: {} + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.14 + + moment@2.30.1: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + pako@0.2.9: {} + + pako@1.0.11: {} + + path-equal@1.2.5: {} + + path-is-absolute@1.0.1: {} + + pluralize@8.0.0: {} + + process@0.11.10: {} + + quicktype-core@23.2.6: + dependencies: + '@glideapps/ts-necessities': 2.2.3 + browser-or-node: 3.0.0 + collection-utils: 1.0.1 + cross-fetch: 4.1.0 + is-url: 1.2.4 + js-base64: 3.7.8 + lodash: 4.18.1 + pako: 1.0.11 + pluralize: 8.0.0 + readable-stream: 4.5.2 + unicode-properties: 1.4.1 + urijs: 1.19.11 + wordwrap: 1.0.0 + yaml: 2.9.0 + transitivePeerDependencies: + - encoding + + quicktype-graphql-input@23.2.6: + dependencies: + collection-utils: 1.0.1 + graphql: 0.11.7 + quicktype-core: 23.2.6 + transitivePeerDependencies: + - encoding + + quicktype-typescript-input@23.2.6: + dependencies: + '@mark.probst/typescript-json-schema': 0.55.0 + quicktype-core: 23.2.6 + typescript: 4.9.5 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - encoding + + quicktype@23.2.6: + dependencies: + '@glideapps/ts-necessities': 2.4.0 + chalk: 4.1.2 + collection-utils: 1.0.1 + command-line-args: 5.2.1 + command-line-usage: 7.0.4 + cross-fetch: 4.1.0 + graphql: 0.11.7 + lodash: 4.18.1 + moment: 2.30.1 + quicktype-core: 23.2.6 + quicktype-graphql-input: 23.2.6 + quicktype-typescript-input: 23.2.6 + readable-stream: 4.7.0 + stream-json: 1.8.0 + string-to-stream: 3.0.1 + typescript: 5.8.3 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - encoding + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readable-stream@4.5.2: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readable-stream@4.7.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + require-directory@2.1.1: {} + + safe-buffer@5.2.1: {} + + safe-stable-stringify@2.5.0: {} + + stream-chain@2.2.5: {} + + stream-json@1.8.0: + dependencies: + stream-chain: 2.2.5 + + string-to-stream@3.0.1: + dependencies: + readable-stream: 3.6.2 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + table-layout@4.1.1: + dependencies: + array-back: 6.2.3 + wordwrapjs: 5.1.1 + + tiny-inflate@1.0.3: {} + + tr46@0.0.3: {} + + ts-node@10.9.2(@types/node@16.18.126)(typescript@4.9.4): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 16.18.126 + acorn: 8.16.0 + acorn-walk: 8.3.5 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.4 + make-error: 1.3.6 + typescript: 4.9.4 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + typescript@4.9.4: {} + + typescript@4.9.5: {} + + typescript@5.8.3: {} + + typical@4.0.0: {} + + typical@7.3.0: {} + + unicode-properties@1.4.1: + dependencies: + base64-js: 1.5.1 + unicode-trie: 2.0.0 + + unicode-trie@2.0.0: + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + + urijs@1.19.11: {} + + util-deprecate@1.0.2: {} + + v8-compile-cache-lib@3.0.1: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + wordwrap@1.0.0: {} + + wordwrapjs@5.1.1: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + y18n@5.0.8: {} + + yaml@2.9.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yn@3.1.1: {} diff --git a/protocol/scripts/check_codegen_tools.mjs b/protocol/scripts/check_codegen_tools.mjs new file mode 100755 index 00000000..87567a3e --- /dev/null +++ b/protocol/scripts/check_codegen_tools.mjs @@ -0,0 +1,72 @@ +#!/usr/bin/env node +/* + * MIT License + * + * Copyright 2023-present, Shopify Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import {requireQuicktype} from "./codegen_tools.mjs"; + +function usage() { + console.error(`Usage: check_codegen_tools.sh [--quiet] + +Verifies that protocol codegen tools are installed from the repo-local pnpm +dependencies and match the exact versions in protocol/package.json.`); +} + +function parseArgs(argv) { + let quiet = false; + + for (let index = 0; index < argv.length;) { + const arg = argv[index]; + switch (arg) { + case "--quiet": + quiet = true; + index += 1; + break; + case "-h": + case "--help": + usage(); + process.exit(0); + break; + default: + console.error(`Unknown argument: ${arg}`); + usage(); + process.exit(2); + } + } + + return {quiet}; +} + +async function main() { + const {quiet} = parseArgs(process.argv.slice(2)); + + await requireQuicktype(); + + if (!quiet) { + console.log("Protocol codegen tools are installed and match protocol/package.json."); + } +} + +main().catch((error) => { + console.error(error.message); + process.exit(1); +}); diff --git a/protocol/scripts/check_codegen_tools.sh b/protocol/scripts/check_codegen_tools.sh new file mode 100755 index 00000000..b44e090b --- /dev/null +++ b/protocol/scripts/check_codegen_tools.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec node "${SCRIPT_DIR}/check_codegen_tools.mjs" "$@" diff --git a/protocol/scripts/codegen_tools.mjs b/protocol/scripts/codegen_tools.mjs new file mode 100644 index 00000000..7be4d804 --- /dev/null +++ b/protocol/scripts/codegen_tools.mjs @@ -0,0 +1,108 @@ +/* + * MIT License + * + * Copyright 2023-present, Shopify Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import {spawn} from "node:child_process"; +import {constants as fsConstants} from "node:fs"; +import fs from "node:fs/promises"; +import path from "node:path"; +import {fileURLToPath} from "node:url"; + +export const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url)); +export const PROTOCOL_DIR = path.resolve(SCRIPT_DIR, ".."); +export const REPO_ROOT = path.resolve(PROTOCOL_DIR, ".."); +export const PACKAGE_JSON = path.join(PROTOCOL_DIR, "package.json"); +export const QUICKTYPE_BIN = path.join(PROTOCOL_DIR, "node_modules", ".bin", "quicktype"); + +export function run(command, args, options = {}) { + return new Promise((resolve, reject) => { + const child = spawn(command, args, { + cwd: options.cwd ?? REPO_ROOT, + stdio: options.capture ? ["ignore", "pipe", "pipe"] : "inherit", + }); + + let stdout = ""; + let stderr = ""; + if (options.capture) { + child.stdout.setEncoding("utf8"); + child.stderr.setEncoding("utf8"); + child.stdout.on("data", (chunk) => { + stdout += chunk; + }); + child.stderr.on("data", (chunk) => { + stderr += chunk; + }); + } + + child.on("error", reject); + child.on("close", (code) => { + if (code === 0) { + resolve({stdout, stderr}); + } else { + const error = new Error(`${command} ${args.join(" ")} exited with ${code}`); + error.code = code; + error.stdout = stdout; + error.stderr = stderr; + reject(error); + } + }); + }); +} + +export async function readJson(file) { + return JSON.parse(await fs.readFile(file, "utf8")); +} + +async function fileIsExecutable(file) { + try { + await fs.access(file, fsConstants.X_OK); + return true; + } catch { + return false; + } +} + +export async function requireQuicktype() { + if (!(await fileIsExecutable(QUICKTYPE_BIN))) { + throw new Error(`quicktype is required at ${QUICKTYPE_BIN}. Run dev up or (cd protocol && pnpm install).`); + } + + const packageJson = await readJson(PACKAGE_JSON); + const expected = packageJson.devDependencies?.quicktype ?? ""; + if (expected === "") { + throw new Error(`Missing quicktype version in ${PACKAGE_JSON}`); + } + + if (!/^[0-9]+[.][0-9]+[.][0-9]+(-[0-9A-Za-z.-]+)?$/.test(expected)) { + throw new Error(`quicktype must use an exact version in ${PACKAGE_JSON}; found ${expected}.`); + } + + const {stdout} = await run(QUICKTYPE_BIN, ["--version"], {capture: true}); + const actual = stdout.match(/^quicktype version (\S+)/m)?.[1] ?? ""; + if (actual === "") { + throw new Error(`Unable to determine quicktype version from ${QUICKTYPE_BIN}`); + } + + if (actual !== expected) { + throw new Error(`Unsupported quicktype version: ${actual}. Expected ${expected} from ${PACKAGE_JSON}.`); + } +} diff --git a/protocol/scripts/generate_models.mjs b/protocol/scripts/generate_models.mjs new file mode 100755 index 00000000..cf90f861 --- /dev/null +++ b/protocol/scripts/generate_models.mjs @@ -0,0 +1,451 @@ +#!/usr/bin/env node +/* + * MIT License + * + * Copyright 2023-present, Shopify Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; + +import { + PROTOCOL_DIR, + QUICKTYPE_BIN, + REPO_ROOT, + readJson, + requireQuicktype, + run, +} from "./codegen_tools.mjs"; + +const SCHEMA_SOURCE_DIR = path.join(PROTOCOL_DIR, "schemas"); +const SERVICES_DIR = path.join(PROTOCOL_DIR, "services", "shopping"); + +function usage() { + console.error("Usage: generate_models.sh --lang [--output ]"); +} + +function parseArgs(argv) { + let lang = ""; + let output = ""; + + for (let index = 0; index < argv.length;) { + const arg = argv[index]; + switch (arg) { + case "--lang": + if (index + 1 >= argv.length) { + throw new Error("Missing value for --lang"); + } + lang = normalizeLang(argv[index + 1]); + index += 2; + break; + case "--output": + if (index + 1 >= argv.length) { + throw new Error("Missing value for --output"); + } + output = argv[index + 1]; + index += 2; + break; + default: + throw new Error(`Unknown argument: ${arg}`); + } + } + + if (lang === "") { + usage(); + process.exit(1); + } + + return {lang, output}; +} + +function normalizeLang(lang) { + switch (lang) { + case "kotlin": + case "swift": + case "typescript": + return lang; + case "ts": + return "typescript"; + default: + throw new Error(`Unsupported language: ${lang}. Use kotlin, swift, or typescript.`); + } +} + +async function writeJson(file, value) { + await fs.mkdir(path.dirname(file), {recursive: true}); + await fs.writeFile(file, `${JSON.stringify(value, null, 2)}\n`); +} + +function rewriteRefs(value) { + if (Array.isArray(value)) { + for (const item of value) { + rewriteRefs(item); + } + return; + } + + if (value === null || typeof value !== "object") { + return; + } + + if (typeof value.$ref === "string") { + value.$ref = value.$ref.replaceAll("../../schemas/shopping/", ""); + } + + for (const item of Object.values(value)) { + rewriteRefs(item); + } +} + +async function prepareCodegenSchemas(tempDir) { + const schemaDir = path.join(tempDir, "schemas"); + await fs.cp(SCHEMA_SOURCE_DIR, schemaDir, {recursive: true}); + + const specDir = path.join(schemaDir, "shopping"); + + // Build checkout models from the base checkout schema plus checkout extension + // fields that checkout-web emits in the primary checkout snapshot/change + // notifications. The imported public schemas stay unchanged; this temp schema + // only gives quicktype the full checkout shape we expose through the SDKs. + const checkout = await readJson(path.join(specDir, "checkout.json")); + checkout.properties.fulfillment = { + title: "CheckoutFulfillment", + allOf: [ + {$ref: "fulfillment.json#/$defs/fulfillment"}, + ], + description: "Fulfillment details.", + ucp_request: { + create: "optional", + update: "optional", + complete: "omit", + }, + }; + checkout.properties.discounts = { + $ref: "discount.json#/$defs/discounts_object", + ucp_request: { + create: "optional", + update: "optional", + complete: "omit", + }, + }; + checkout.properties.payment.title = "Payment"; + await writeJson(path.join(specDir, "checkout.json"), checkout); + + const order = await readJson(path.join(specDir, "order.json")); + order.properties.fulfillment.title = "Fulfillment"; + await writeJson(path.join(specDir, "order.json"), order); + + // The public schemas are imported inputs, so keep naming hints local to codegen. + // quicktype otherwise collapses order_line_item.quantity to a generic Quantity. + const orderLineItem = await readJson(path.join(specDir, "types", "order_line_item.json")); + orderLineItem.properties.quantity.title = "LineItemQuantity"; + await writeJson(path.join(specDir, "types", "order_line_item.json"), orderLineItem); + + // Preserve the existing shared line-item total model name after checkout + // fulfillment adds another reference from fulfillment option totals. + const total = await readJson(path.join(specDir, "types", "total.json")); + total.title = "LineItemTotal"; + await writeJson(path.join(specDir, "types", "total.json"), total); + + const totals = await readJson(path.join(specDir, "types", "totals.json")); + totals.items.title = "CheckoutTotal"; + await writeJson(path.join(specDir, "types", "totals.json"), totals); + + const embeddedConfig = await readJson(path.join(schemaDir, "transports", "embedded_config.json")); + embeddedConfig.properties.color_scheme.items.title = "EmbeddedColorScheme"; + await writeJson(path.join(schemaDir, "transports", "embedded_config.json"), embeddedConfig); + + // Message discriminators are defined across the message variant schemas. Give + // each variant the same local title so quicktype emits a single MessageType symbol. + for (const messageSchema of ["message_error", "message_warning", "message_info"]) { + const schema = await readJson(path.join(specDir, "types", `${messageSchema}.json`)); + schema.properties.type.title = "MessageType"; + await writeJson(path.join(specDir, "types", `${messageSchema}.json`), schema); + } + + // Extension schemas bring in repeated generic property names like `type` and + // `method`; add local titles so generated model symbols stay domain-specific. + const discount = await readJson(path.join(specDir, "discount.json")); + discount.$defs.discounts_object.title = "CheckoutDiscounts"; + discount.$defs.applied_discount.title = "AppliedDiscount"; + discount.$defs.applied_discount.properties.method.title = "DiscountMethod"; + discount.$defs.allocation.title = "DiscountAllocation"; + await writeJson(path.join(specDir, "discount.json"), discount); + + for (const fulfillmentSchema of ["fulfillment_available_method", "fulfillment_method"]) { + const schema = await readJson(path.join(specDir, "types", `${fulfillmentSchema}.json`)); + schema.properties.type.title = "FulfillmentMethodType"; + await writeJson(path.join(specDir, "types", `${fulfillmentSchema}.json`), schema); + } + + const fulfillment = await readJson(path.join(specDir, "types", "fulfillment.json")); + fulfillment.title = "CheckoutFulfillment"; + await writeJson(path.join(specDir, "types", "fulfillment.json"), fulfillment); + + return specDir; +} + +async function extractResultSchema(specDir, methodName, outputFile, rootTitle, checkoutTitle, paymentSchema) { + const service = await readJson(path.join(SERVICES_DIR, "embedded.openrpc.json")); + const method = service.methods.find((candidate) => candidate.name === methodName); + if (method === undefined) { + throw new Error(`Missing OpenRPC method ${methodName}`); + } + + const schema = structuredClone(method.result.schema); + schema.title = rootTitle; + rewriteRefs(schema); + + for (const variant of schema.oneOf ?? []) { + if (variant?.properties?.checkout !== undefined) { + variant.properties.checkout.title = checkoutTitle; + variant.properties.checkout.properties.payment = structuredClone(paymentSchema); + } + } + + schema.components = service.components; + + await writeJson(path.join(specDir, outputFile), schema); +} + +async function runQuicktype(args) { + await run(QUICKTYPE_BIN, args); +} + +async function replaceInFile(file, transform) { + const source = await fs.readFile(file, "utf8"); + await fs.writeFile(file, transform(source)); +} + +function normalizeQuicktypeFallbacks(source) { + return source + .replace(/\bPurpleStatus\b/g, "StatusEnum") + .replace(/\bPurpleService\b/g, "InstrumentsChangeService"); +} + +function assertNoQuicktypeFallbacks(source, output) { + const matches = Array.from( + source.matchAll(/\b(?:Purple|Fluffy|Tentacled|Sticky|Indigo|Magenta)\w+/g), + (match) => match[0], + ); + const unique = [...new Set(matches)].sort(); + + if (unique.length > 0) { + throw new Error(`Unexpected quicktype color-name fallback detected in ${output}:\n${unique.join("\n")}`); + } +} + +async function normalizeGeneratedFile(output, transform = (source) => source) { + await replaceInFile(output, (source) => { + const result = normalizeQuicktypeFallbacks(transform(source)); + assertNoQuicktypeFallbacks(result, output); + return result; + }); +} + +function commonSchemaSources(specDir) { + return [ + "--src", + path.join(specDir, "checkout.json"), + "--src", + path.join(specDir, "order.json"), + "--src", + path.join(specDir, "types", "error_response.json"), + "--src", + path.join(specDir, "instruments_change_result.json"), + "--src", + path.join(specDir, "credential_result.json"), + ]; +} + +async function generateKotlin(specDir, output) { + await fs.mkdir(path.dirname(output), {recursive: true}); + await runQuicktype([ + "--lang", + "kotlin", + "--src-lang", + "schema", + "--framework", + "kotlinx", + ...commonSchemaSources(specDir), + "--package", + "com.shopify.checkoutkit", + "-o", + output, + ]); + + await normalizeGeneratedFile(output, (source) => { + const result = source + .replace(/^.*?(?=^package )/ms, "") + .replace(/^data class /gm, "public data class ") + .replace(/^sealed class /gm, "public sealed class ") + .replace(/^enum class /gm, "public enum class ") + .replace(/^typealias /gm, "public typealias ") + .replace(/^ class /gm, " public class ") + .replace(/^ val /gm, " public val ") + .replace(/\(val value: /g, "(public val value: "); + + const withSerializer = result.replace( + /@Serializable(\s+public sealed class Extends\b)/, + "@Serializable(with = ExtendsSerializer::class)$1", + ); + + if (withSerializer === result) { + throw new Error("ExtendsSerializer injection failed; quicktype Extends output may have changed"); + } + + return withSerializer; + }); +} + +async function generateSwift(specDir, output) { + await fs.mkdir(path.dirname(output), {recursive: true}); + await runQuicktype([ + "--lang", + "swift", + "--swift-5-support", + "--access-level", + "public", + "--sendable", + "--src-lang", + "schema", + ...commonSchemaSources(specDir), + "-o", + output, + ]); + + await normalizeGeneratedFile(output); +} + +async function generateTypescript(specDir, output) { + await fs.mkdir(path.dirname(output), {recursive: true}); + await runQuicktype([ + "--lang", + "ts", + "--src-lang", + "schema", + "--prefer-unions", + "--nice-property-names", + "--acronym-style", + "camel", + "--no-date-times", + ...commonSchemaSources(specDir), + "-o", + output, + ]); + + await normalizeGeneratedFile(output, (source) => source.replace(/^type /gm, "export type ")); + + await run("node", [path.join(PROTOCOL_DIR, "scripts", "generate_typescript_notifications.mjs")]); + + const declarationOutput = path.join(PROTOCOL_DIR, "languages", "typescript", "src", "index.d.ts"); + const tscBin = path.join(REPO_ROOT, "platforms", "react-native", "node_modules", "typescript", "bin", "tsc"); + const indexOutput = path.join(PROTOCOL_DIR, "languages", "typescript", "src", "index.ts"); + + await run("node", [ + tscBin, + "--declaration", + "--emitDeclarationOnly", + "--noEmit", + "false", + "--rootDir", + path.join(PROTOCOL_DIR, "languages", "typescript", "src"), + "--declarationDir", + path.join(PROTOCOL_DIR, "languages", "typescript", "src"), + "--pretty", + "false", + indexOutput, + ]); + + return declarationOutput; +} + +async function main() { + const {lang, output} = parseArgs(process.argv.slice(2)); + await requireQuicktype(); + + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "checkout-kit-protocol-codegen-")); + try { + const specDir = await prepareCodegenSchemas(tempDir); + + await extractResultSchema( + specDir, + "ec.payment.instruments_change_request", + "instruments_change_result.json", + "InstrumentsChangeResult", + "InstrumentsChangeCheckout", + { + title: "InstrumentsChangePayment", + description: "Payment instruments with selected instrument ID.", + allOf: [ + {$ref: "checkout.json#/properties/payment"}, + { + type: "object", + properties: { + selected_instrument_id: { + type: "string", + description: "ID of the selected payment instrument.", + }, + }, + }, + ], + }, + ); + await extractResultSchema( + specDir, + "ec.payment.credential_request", + "credential_result.json", + "CredentialResult", + "CredentialCheckout", + {$ref: "checkout.json#/properties/payment"}, + ); + + switch (lang) { + case "kotlin": { + const target = output || path.join(REPO_ROOT, "platforms", "android", "lib", "src", "main", "java", "com", "shopify", "checkoutkit", "Models.kt"); + await generateKotlin(specDir, target); + console.log(`Generated ${target}`); + break; + } + case "swift": { + const target = output || path.join(PROTOCOL_DIR, "languages", "swift", "Sources", "ShopifyCheckoutProtocol", "Generated", "Models.swift"); + await generateSwift(specDir, target); + console.log(`Generated ${target}`); + break; + } + case "typescript": { + const target = output || path.join(PROTOCOL_DIR, "languages", "typescript", "src", "generated", "Models.ts"); + const declarationOutput = await generateTypescript(specDir, target); + console.log(`Generated ${target}, TypeScript protocol notifications, and ${declarationOutput}`); + break; + } + default: + throw new Error(`Unsupported language: ${lang}. Use kotlin, swift, or typescript.`); + } + } finally { + await fs.rm(tempDir, {recursive: true, force: true}); + } +} + +main().catch((error) => { + console.error(error.message); + process.exit(1); +}); diff --git a/protocol/scripts/generate_models.sh b/protocol/scripts/generate_models.sh index 216212c5..7dbcb0af 100755 --- a/protocol/scripts/generate_models.sh +++ b/protocol/scripts/generate_models.sh @@ -1,312 +1,5 @@ #!/bin/bash set -euo pipefail -REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" -SCHEMA_WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/checkout-kit-schemas.XXXXXX")" -SCHEMA_ROOT="${SCHEMA_WORK_DIR}/schemas" -SPEC_DIR="${SCHEMA_ROOT}/shopping" -SERVICES_DIR="${REPO_ROOT}/protocol/services/shopping" - -LANG="" -while [[ $# -gt 0 ]]; do - case "$1" in - --lang) LANG="$2"; shift 2 ;; - *) echo "Unknown argument: $1"; exit 1 ;; - esac -done - -if [[ -z "$LANG" ]]; then - echo "Usage: $0 --lang " - exit 1 -fi - -cleanup() { - rm -rf "${SCHEMA_WORK_DIR}" -} -trap cleanup EXIT - -cp -R "${REPO_ROOT}/protocol/schemas" "${SCHEMA_ROOT}" - -# Build checkout models from the base checkout schema plus checkout extension -# fields that checkout-web emits in the primary checkout snapshot/change -# notifications. The imported public schemas stay unchanged; this temp schema -# only gives quicktype the full checkout shape we expose through the SDKs. -jq ' - .properties.fulfillment = { - "title": "CheckoutFulfillment", - "allOf": [ - { "$ref": "fulfillment.json#/$defs/fulfillment" } - ], - "description": "Fulfillment details.", - "ucp_request": { - "create": "optional", - "update": "optional", - "complete": "omit" - } - } | - .properties.discounts = { - "$ref": "discount.json#/$defs/discounts_object", - "ucp_request": { - "create": "optional", - "update": "optional", - "complete": "omit" - } - } | - .properties.payment.title = "Payment" -' \ - "${SPEC_DIR}/checkout.json" > "${SCHEMA_WORK_DIR}/checkout.json" -mv "${SCHEMA_WORK_DIR}/checkout.json" "${SPEC_DIR}/checkout.json" - -jq '.properties.fulfillment.title = "Fulfillment"' \ - "${SPEC_DIR}/order.json" > "${SCHEMA_WORK_DIR}/order.json" -mv "${SCHEMA_WORK_DIR}/order.json" "${SPEC_DIR}/order.json" - -# The public schemas are imported inputs, so keep naming hints local to codegen. -# quicktype otherwise collapses order_line_item.quantity to a generic Quantity. -jq '.properties.quantity.title = "LineItemQuantity"' \ - "${SPEC_DIR}/types/order_line_item.json" > "${SCHEMA_WORK_DIR}/order_line_item.json" -mv "${SCHEMA_WORK_DIR}/order_line_item.json" "${SPEC_DIR}/types/order_line_item.json" - -# Preserve the existing shared line-item total model name after checkout -# fulfillment adds another reference from fulfillment option totals. -jq '.title = "LineItemTotal"' \ - "${SPEC_DIR}/types/total.json" > "${SCHEMA_WORK_DIR}/total.json" -mv "${SCHEMA_WORK_DIR}/total.json" "${SPEC_DIR}/types/total.json" - -jq '.items.title = "CheckoutTotal"' \ - "${SPEC_DIR}/types/totals.json" > "${SCHEMA_WORK_DIR}/totals.json" -mv "${SCHEMA_WORK_DIR}/totals.json" "${SPEC_DIR}/types/totals.json" - -jq '.properties.color_scheme.items.title = "EmbeddedColorScheme"' \ - "${SCHEMA_ROOT}/transports/embedded_config.json" > "${SCHEMA_WORK_DIR}/embedded_config.json" -mv "${SCHEMA_WORK_DIR}/embedded_config.json" "${SCHEMA_ROOT}/transports/embedded_config.json" - -# Message discriminators are defined across the message variant schemas. Give each -# variant the same local title so quicktype emits a single MessageType symbol. -for message_schema in message_error message_warning message_info; do - jq '.properties.type.title = "MessageType"' \ - "${SPEC_DIR}/types/${message_schema}.json" > "${SCHEMA_WORK_DIR}/${message_schema}.json" - mv "${SCHEMA_WORK_DIR}/${message_schema}.json" "${SPEC_DIR}/types/${message_schema}.json" -done - -# Extension schemas bring in repeated generic property names like `type` and -# `method`; add local titles so generated model symbols stay domain-specific. -jq ' - ."$defs".discounts_object.title = "CheckoutDiscounts" | - ."$defs".applied_discount.title = "AppliedDiscount" | - ."$defs".applied_discount.properties.method.title = "DiscountMethod" | - ."$defs".allocation.title = "DiscountAllocation" -' \ - "${SPEC_DIR}/discount.json" > "${SCHEMA_WORK_DIR}/discount.json" -mv "${SCHEMA_WORK_DIR}/discount.json" "${SPEC_DIR}/discount.json" - -for fulfillment_schema in fulfillment_available_method fulfillment_method; do - jq '.properties.type.title = "FulfillmentMethodType"' \ - "${SPEC_DIR}/types/${fulfillment_schema}.json" > "${SCHEMA_WORK_DIR}/${fulfillment_schema}.json" - mv "${SCHEMA_WORK_DIR}/${fulfillment_schema}.json" "${SPEC_DIR}/types/${fulfillment_schema}.json" -done - -jq '.title = "CheckoutFulfillment"' \ - "${SPEC_DIR}/types/fulfillment.json" > "${SCHEMA_WORK_DIR}/fulfillment.json" -mv "${SCHEMA_WORK_DIR}/fulfillment.json" "${SPEC_DIR}/types/fulfillment.json" - -# We also rewrite $ref paths from "../../schemas/shopping/" to "" so that refs resolve -# correctly when the temp file is placed alongside the main schemas in SPEC_DIR. The -# openrpc doc's `components` section is copied into the temp file so internal -# `#/components/schemas/X` refs in the extracted result schema resolve locally. -extract_result_schema() { - local method_name="$1" - local output_file="$2" - local root_title="$3" - local checkout_title="$4" - local payment_schema="$5" - jq --arg method "$method_name" \ - --arg root_title "$root_title" \ - --arg checkout_title "$checkout_title" \ - --argjson payment_schema "$payment_schema" \ - ' - . as $root - | .methods[] | select(.name == $method) | .result.schema - | .title = $root_title - | walk(if type == "object" and has("$ref") then - .["$ref"] |= gsub("../../schemas/shopping/"; "") - else . end) - | (.oneOf[] | select(.properties.checkout? != null).properties.checkout) |= ( - .title = $checkout_title - | .properties.payment = $payment_schema - ) - | . + { components: $root.components } - ' \ - "${SERVICES_DIR}/embedded.openrpc.json" > "$output_file" -} - -extract_result_schema "ec.payment.instruments_change_request" \ - "${SPEC_DIR}/instruments_change_result.json" \ - "InstrumentsChangeResult" \ - "InstrumentsChangeCheckout" \ - '{ - "title": "InstrumentsChangePayment", - "description": "Payment instruments with selected instrument ID.", - "allOf": [ - { "$ref": "checkout.json#/properties/payment" }, - { - "type": "object", - "properties": { - "selected_instrument_id": { - "type": "string", - "description": "ID of the selected payment instrument." - } - } - } - ] - }' - -extract_result_schema "ec.payment.credential_request" \ - "${SPEC_DIR}/credential_result.json" \ - "CredentialResult" \ - "CredentialCheckout" \ - '{ "$ref": "checkout.json#/properties/payment" }' - -normalize_quicktype_fallbacks() { - # These two names resist schema title hints because they arise from inline - # object collisions inside result schema oneOf branches. Add new entries here - # if quicktype emits color-name fallbacks after a schema change. - perl -0pi -e 's/\bPurpleStatus\b/StatusEnum/g; s/\bPurpleService\b/InstrumentsChangeService/g' "$1" -} - -assert_no_quicktype_fallbacks() { - local output="$1" - local matches - matches="$(perl -ne 'while (/\b(?:Purple|Fluffy|Tentacled|Sticky|Indigo|Magenta)\w+/g) { print "$&\n" }' "${output}" | sort -u)" - - if [[ -n "${matches}" ]]; then - echo "ERROR: Unexpected quicktype color-name fallback detected in ${output}" >&2 - printf '%s\n' "${matches}" >&2 - exit 1 - fi -} - -case "$LANG" in - kotlin) - OUTPUT="${REPO_ROOT}/platforms/android/lib/src/main/java/com/shopify/checkoutkit/Models.kt" - quicktype \ - --lang kotlin \ - --src-lang schema \ - --framework kotlinx \ - --src "${SPEC_DIR}/checkout.json" \ - --src "${SPEC_DIR}/order.json" \ - --src "${SPEC_DIR}/types/error_response.json" \ - --src "${SPEC_DIR}/instruments_change_result.json" \ - --src "${SPEC_DIR}/credential_result.json" \ - --package "com.shopify.checkoutkit" \ - -o "${OUTPUT}" - - # Remove quicktype's usage preamble so generated Android sources start at - # the package declaration, matching the rest of the library source layout. - perl -0pi -e 's/\A.*?(?=^package )//ms' "${OUTPUT}" - - # Post-process for -Xexplicit-api=strict: every top-level and member declaration that - # is part of the public API surface needs an explicit 'public' modifier. - # - # quicktype Kotlin does not support --access-level, so we add 'public' - # after generation. - # Patterns: top-level classes/aliases, 4-space-indented constructor properties, - # inner classes inside sealed classes, and inline constructor params in enum/sealed. - perl -0pi -e ' - s/^data class /public data class /mg; - s/^sealed class /public sealed class /mg; - s/^enum class /public enum class /mg; - s/^typealias /public typealias /mg; - s/^ class / public class /mg; - s/^ val / public val /mg; - s/\(val value: /\(public val value: /g; - ' "${OUTPUT}" - - # Normalize remaining exact quicktype fallback names. - normalize_quicktype_fallbacks "${OUTPUT}" - assert_no_quicktype_fallbacks "${OUTPUT}" - - # quicktype renders Extends as a sealed class, but the wire shape is either - # a string or an array of strings. Use the hand-written serializer in - # ExtendsSerializer.kt without depending on quicktype's sealed-class body. - perl -0pi -e 's/@Serializable(\s+public sealed class Extends\b)/@Serializable(with = ExtendsSerializer::class)$1/' "${OUTPUT}" - - if ! grep -q '@Serializable(with = ExtendsSerializer::class)' "${OUTPUT}"; then - echo "ERROR: ExtendsSerializer injection failed; quicktype Extends output may have changed" >&2 - exit 1 - fi - - - echo "Generated ${OUTPUT}" - ;; - - swift) - OUTPUT="${REPO_ROOT}/protocol/languages/swift/Sources/ShopifyCheckoutProtocol/Generated/Models.swift" - quicktype \ - --lang swift \ - --swift-5-support \ - --access-level public \ - --sendable \ - --src-lang schema \ - --src "${SPEC_DIR}/checkout.json" \ - --src "${SPEC_DIR}/order.json" \ - --src "${SPEC_DIR}/types/error_response.json" \ - --src "${SPEC_DIR}/instruments_change_result.json" \ - --src "${SPEC_DIR}/credential_result.json" \ - -o "${OUTPUT}" - - # Normalize remaining exact quicktype fallback names. - normalize_quicktype_fallbacks "${OUTPUT}" - assert_no_quicktype_fallbacks "${OUTPUT}" - - echo "Generated ${OUTPUT}" - ;; - - typescript|ts) - OUTPUT="${REPO_ROOT}/protocol/languages/typescript/src/generated/Models.ts" - mkdir -p "$(dirname "${OUTPUT}")" - quicktype \ - --lang ts \ - --src-lang schema \ - --prefer-unions \ - --nice-property-names \ - --acronym-style camel \ - --no-date-times \ - --src "${SPEC_DIR}/checkout.json" \ - --src "${SPEC_DIR}/order.json" \ - --src "${SPEC_DIR}/types/error_response.json" \ - --src "${SPEC_DIR}/instruments_change_result.json" \ - --src "${SPEC_DIR}/credential_result.json" \ - -o "${OUTPUT}" - - # Keep all schema-derived aliases available to package consumers, and apply - # the cross-platform generated model renames used by Swift and Kotlin. - perl -0pi -e 's/^type /export type /mg' "${OUTPUT}" - normalize_quicktype_fallbacks "${OUTPUT}" - assert_no_quicktype_fallbacks "${OUTPUT}" - - node "${REPO_ROOT}/protocol/scripts/generate_typescript_notifications.mjs" - - # API Extractor consumers require dependency entry points to resolve to - # declaration files. Runtime converter output is not valid declaration syntax, - # so emit declarations from the TypeScript package entry point. - DECLARATION_OUTPUT="${REPO_ROOT}/protocol/languages/typescript/src/index.d.ts" - TSC_BIN="${REPO_ROOT}/platforms/react-native/node_modules/typescript/bin/tsc" - INDEX_OUTPUT="${REPO_ROOT}/protocol/languages/typescript/src/index.ts" - node "${TSC_BIN}" \ - --declaration \ - --emitDeclarationOnly \ - --noEmit false \ - --rootDir "${REPO_ROOT}/protocol/languages/typescript/src" \ - --declarationDir "${REPO_ROOT}/protocol/languages/typescript/src" \ - --pretty false \ - "${INDEX_OUTPUT}" - - echo "Generated ${OUTPUT}, TypeScript protocol notifications, and ${DECLARATION_OUTPUT}" - ;; - - *) - echo "Unsupported language: $LANG. Use kotlin, swift, or typescript." - exit 1 - ;; -esac +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec node "${SCRIPT_DIR}/generate_models.mjs" "$@"