From 5173cbf9d3105a1eb68703513ff30a076fd7c9cb Mon Sep 17 00:00:00 2001 From: sunny Date: Tue, 4 Mar 2025 16:38:15 +0800 Subject: [PATCH 1/7] chore: Update deploy script --- .env.example | 10 +- .gitignore | 6 +- package.json | 8 +- pnpm-lock.yaml | 199 +++++++++++++++- scripts/deploy/cloudflare.ts | 112 +++++++++ scripts/deploy/index.ts | 422 ++++++++++++++++++++++++++++++++++ wrangler.cleanup.example.json | 18 ++ wrangler.cleanup.example.toml | 14 -- wrangler.email.example.json | 15 ++ wrangler.email.example.toml | 11 - wrangler.example.json | 21 ++ wrangler.example.toml | 14 -- 12 files changed, 805 insertions(+), 45 deletions(-) create mode 100644 scripts/deploy/cloudflare.ts create mode 100644 scripts/deploy/index.ts create mode 100644 wrangler.cleanup.example.json delete mode 100644 wrangler.cleanup.example.toml create mode 100644 wrangler.email.example.json delete mode 100644 wrangler.email.example.toml create mode 100644 wrangler.example.json delete mode 100644 wrangler.example.toml diff --git a/.env.example b/.env.example index 5191c3c..1e366ec 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,11 @@ AUTH_GITHUB_ID = "" AUTH_GITHUB_SECRET = "" -AUTH_SECRET = "" \ No newline at end of file +AUTH_SECRET = "" + +CLOUDFLARE_API_TOKEN = "" +CLOUDFLARE_ACCOUNT_ID = "" +DATABASE_NAME = "" +DATABASE_ID = "" +KV_NAMESPACE_ID = "" + +PROJECT_URL = "" \ No newline at end of file diff --git a/.gitignore b/.gitignore index f19465b..93d59c3 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,8 @@ wrangler.email.toml wrangler.cleanup.toml public/workbox-*.js -public/sw.js \ No newline at end of file +public/sw.js + +wrangler.json +wrangler.cleanup.json +wrangler.email.json \ No newline at end of file diff --git a/package.json b/package.json index 52c76b4..d643ea9 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,10 @@ "db:migrate-remote": "bun run scripts/migrate.ts remote", "webhook-test-server": "bun run scripts/webhook-test-server.ts", "generate-test-data": "wrangler dev scripts/generate-test-data.ts", - "dev:cleanup": "wrangler dev --config wrangler.cleanup.toml --test-scheduled", + "dev:cleanup": "wrangler dev --config wrangler.cleanup.json --test-scheduled", "test:cleanup": "curl http://localhost:8787/__scheduled", - "deploy:email": "wrangler deploy --config wrangler.email.toml", - "deploy:cleanup": "wrangler deploy --config wrangler.cleanup.toml", + "deploy:email": "wrangler deploy --config wrangler.email.json", + "deploy:cleanup": "wrangler deploy --config wrangler.cleanup.json", "deploy:pages": "npm run build:pages && wrangler pages deploy .vercel/output/static --branch main" }, "type": "module", @@ -60,6 +60,8 @@ "@types/react": "^18", "@types/react-dom": "^18", "bun": "^1.1.39", + "cloudflare": "^4.1.0", + "dotenv": "^16.4.7", "drizzle-kit": "^0.28.1", "eslint": "^8", "eslint-config-next": "15.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 95247d2..6723a33 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -123,6 +123,12 @@ importers: bun: specifier: ^1.1.39 version: 1.1.39 + cloudflare: + specifier: ^4.1.0 + version: 4.1.0 + dotenv: + specifier: ^16.4.7 + version: 16.4.7 drizzle-kit: specifier: ^0.28.1 version: 0.28.1 @@ -2226,12 +2232,18 @@ packages: '@types/next-pwa@5.6.9': resolution: {integrity: sha512-KcymH+MtFYB5KVKIOH1DMqd0wUb8VLCxzHtsaRQQ7S8sGOaTH24Lo2vGZf6/0Ok9e+xWCKhqsSt6cgDJTk91Iw==} + '@types/node-fetch@2.6.12': + resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==} + '@types/node-forge@1.3.11': resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} '@types/node@16.18.11': resolution: {integrity: sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==} + '@types/node@18.19.78': + resolution: {integrity: sha512-m1ilZCTwKLkk9rruBJXFeYN0Bc5SbjirwYX/Td3MqPfioYbgun3IvK/m8dQxMCnrPGZPg1kvXjp3SIekCN/ynw==} + '@types/node@20.12.14': resolution: {integrity: sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==} @@ -2429,6 +2441,10 @@ packages: abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -2452,6 +2468,10 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + ajv-keywords@3.5.2: resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} peerDependencies: @@ -2587,6 +2607,9 @@ packages: async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} @@ -2686,6 +2709,10 @@ packages: resolution: {integrity: sha512-CCKAP2tkPau7D3GE8+V8R6sQubA9R5foIzGp+85EXCVSCivuxBNAWqcpn72PKYiIcqoViv/kcUDpaEIMBVi1lQ==} engines: {node: '>= 0.4'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + call-bind@1.0.8: resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} engines: {node: '>= 0.4'} @@ -2750,6 +2777,9 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cloudflare@4.1.0: + resolution: {integrity: sha512-TySwSEGGQhuVHFVjKRUHkzZum0MSGJkgfVjep+KBJxuxScEvjoTckQFbxlYThPp5kLm8IUi4C7oJeVr5e9etVw==} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -2775,6 +2805,10 @@ packages: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} @@ -2913,6 +2947,10 @@ packages: resolution: {integrity: sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==} engines: {node: '>=6'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} @@ -2949,6 +2987,10 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} + drizzle-kit@0.28.1: resolution: {integrity: sha512-JimOV+ystXTWMgZkLHYHf2w3oS28hxiH1FR0dkmJLc7GHzdGJoJAQtQS5DRppnabsRZwE2U1F6CuezVBgmsBBQ==} hasBin: true @@ -3049,6 +3091,10 @@ packages: resolution: {integrity: sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==} engines: {node: '>= 0.4'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -3111,10 +3157,18 @@ packages: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + es-set-tostringtag@2.0.3: resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} engines: {node: '>= 0.4'} + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + es-shim-unscopables@1.0.2: resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} @@ -3532,6 +3586,10 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + events-intercept@2.0.0: resolution: {integrity: sha512-blk1va0zol9QOrdZt0rFXo5KMkNPVSp92Eju/Qz8THwKWKRKeE0T8Br/1aW6+Edkyq9xHYgYxn2QtOnUKPUp+Q==} @@ -3613,6 +3671,17 @@ packages: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} + form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + + form-data@4.0.2: + resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + engines: {node: '>= 6'} + + formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + fs-extra@11.1.0: resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==} engines: {node: '>=14.14'} @@ -3666,6 +3735,10 @@ packages: resolution: {integrity: sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg==} engines: {node: '>= 0.4'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} @@ -3673,6 +3746,10 @@ packages: get-own-enumerable-property-symbols@3.0.2: resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-source@2.0.12: resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==} @@ -3780,6 +3857,9 @@ packages: resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} engines: {node: '>=8.12.0'} + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -4142,6 +4222,10 @@ packages: make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -4322,6 +4406,10 @@ packages: sass: optional: true + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + node-fetch@2.6.7: resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} engines: {node: 4.x || >=6.0.0} @@ -5394,6 +5482,10 @@ packages: resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} engines: {node: '>=10.13.0'} + web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + web-vitals@0.2.4: resolution: {integrity: sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg==} @@ -7566,12 +7658,21 @@ snapshots: - sass - supports-color + '@types/node-fetch@2.6.12': + dependencies: + '@types/node': 20.17.9 + form-data: 4.0.2 + '@types/node-forge@1.3.11': dependencies: '@types/node': 20.17.9 '@types/node@16.18.11': {} + '@types/node@18.19.78': + dependencies: + undici-types: 5.26.5 + '@types/node@20.12.14': dependencies: undici-types: 5.26.5 @@ -7914,6 +8015,10 @@ snapshots: abbrev@1.1.1: {} + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + acorn-import-attributes@1.9.5(acorn@8.14.0): dependencies: acorn: 8.14.0 @@ -7934,6 +8039,10 @@ snapshots: transitivePeerDependencies: - supports-color + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + ajv-keywords@3.5.2(ajv@6.12.6): dependencies: ajv: 6.12.6 @@ -8090,6 +8199,8 @@ snapshots: async@3.2.6: {} + asynckit@0.4.0: {} + at-least-node@1.0.0: {} available-typed-arrays@1.0.7: @@ -8201,6 +8312,11 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + call-bind@1.0.8: dependencies: call-bind-apply-helpers: 1.0.0 @@ -8267,6 +8383,18 @@ snapshots: client-only@0.0.1: {} + cloudflare@4.1.0: + dependencies: + '@types/node': 18.19.78 + '@types/node-fetch': 2.6.12 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + clsx@2.1.1: {} code-block-writer@10.1.1: {} @@ -8291,6 +8419,10 @@ snapshots: color-string: 1.9.1 optional: true + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@11.1.0: {} commander@2.20.3: {} @@ -8365,7 +8497,7 @@ snapshots: debug@4.1.1: dependencies: - ms: 2.1.1 + ms: 2.1.3 debug@4.4.0: dependencies: @@ -8399,6 +8531,8 @@ snapshots: pify: 4.0.1 rimraf: 2.7.1 + delayed-stream@1.0.0: {} + delegates@1.0.0: {} depd@1.1.2: {} @@ -8425,6 +8559,8 @@ snapshots: dependencies: esutils: 2.0.3 + dotenv@16.4.7: {} + drizzle-kit@0.28.1: dependencies: '@drizzle-team/brocli': 0.10.2 @@ -8447,6 +8583,12 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + eastasianwidth@0.2.0: {} edge-runtime@2.5.9: @@ -8565,12 +8707,23 @@ snapshots: dependencies: es-errors: 1.3.0 + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + es-set-tostringtag@2.0.3: dependencies: get-intrinsic: 1.2.5 has-tostringtag: 1.0.2 hasown: 2.0.2 + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + es-shim-unscopables@1.0.2: dependencies: hasown: 2.0.2 @@ -9050,6 +9203,8 @@ snapshots: etag@1.8.1: {} + event-target-shim@5.0.1: {} + events-intercept@2.0.0: {} events@3.3.0: {} @@ -9148,6 +9303,20 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + form-data-encoder@1.7.2: {} + + form-data@4.0.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + mime-types: 2.1.35 + + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + fs-extra@11.1.0: dependencies: graceful-fs: 4.2.11 @@ -9218,10 +9387,28 @@ snapshots: has-symbols: 1.1.0 hasown: 2.0.2 + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} get-own-enumerable-property-symbols@3.0.2: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-source@2.0.12: dependencies: data-uri-to-buffer: 2.0.2 @@ -9349,6 +9536,10 @@ snapshots: human-signals@1.1.1: {} + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -9684,6 +9875,8 @@ snapshots: make-error@1.3.6: {} + math-intrinsics@1.1.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -9872,6 +10065,8 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-domexception@1.0.0: {} + node-fetch@2.6.7: dependencies: whatwg-url: 5.0.0 @@ -10982,6 +11177,8 @@ snapshots: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 + web-streams-polyfill@4.0.0-beta.3: {} + web-vitals@0.2.4: {} webidl-conversions@3.0.1: {} diff --git a/scripts/deploy/cloudflare.ts b/scripts/deploy/cloudflare.ts new file mode 100644 index 0000000..c5ca720 --- /dev/null +++ b/scripts/deploy/cloudflare.ts @@ -0,0 +1,112 @@ +import Cloudflare from "cloudflare"; +import "dotenv/config"; + +const CF_ACCOUNT_ID = process.env.CLOUDFLARE_ACCOUNT_ID!; +const CF_API_TOKEN = process.env.CLOUDFLARE_API_TOKEN; +const PROJECT_URL = process.env.PROJECT_URL; +const PROJECT_NAME = process.env.PROJECT_NAME || "moemail"; +const DB_NAME = process.env.DATABASE_NAME || "moemail-db"; +const KV_NAMESPACE_NAME = process.env.KV_NAME || "moemail-kv"; + +const client = new Cloudflare({ + apiKey: CF_API_TOKEN, +}); + +export const getPages = async () => { + try { + const projectInfo = await client.pages.projects.get(PROJECT_NAME, { + account_id: CF_ACCOUNT_ID, + }); + + return projectInfo; + } catch (error) { + throw error; + } +}; + +export const createPages = async () => { + try { + console.log(`🆕 Creating new Cloudflare Pages project: "${PROJECT_NAME}"`); + + const project = await client.pages.projects.create({ + account_id: CF_ACCOUNT_ID, + name: PROJECT_NAME, + production_branch: "main", + }); + + if (PROJECT_URL) { + console.log("🔗 Setting pages domain..."); + + await client.pages.projects.domains.create(PROJECT_NAME, { + account_id: CF_ACCOUNT_ID, + name: PROJECT_URL?.split("://")[1], + }); + + console.log("✅ Pages domain set successfully"); + } + + console.log("✅ Project created successfully"); + + return project; + } catch (error) { + throw error; + } +}; + +export const getDatabase = async () => { + try { + const database = await client.d1.database.get(DB_NAME, { + account_id: CF_ACCOUNT_ID, + }); + + return database; + } catch (error) { + throw error; + } +}; + +export const createDatabase = async () => { + try { + console.log(`🆕 Creating new D1 database: "${DB_NAME}"`); + const database = await client.d1.database.create({ + account_id: CF_ACCOUNT_ID, + name: DB_NAME, + }); + console.log("✅ Database created successfully"); + + return database; + } catch (error) { + throw error; + } +}; + +export const getKVNamespace = async (namespaceId: string) => { + if (!namespaceId) { + throw new Error("KV namespace ID is required"); + } + + try { + const kvNamespace = await client.kv.namespaces.get(namespaceId, { + account_id: CF_ACCOUNT_ID, + }); + + return kvNamespace; + } catch (error) { + throw error; + } +}; + +export const createKVNamespace = async () => { + try { + console.log(`🆕 Creating new KV namespace: "${KV_NAMESPACE_NAME}"`); + const kvNamespace = await client.kv.namespaces.create({ + account_id: CF_ACCOUNT_ID, + title: KV_NAMESPACE_NAME, + }); + console.log("✅ KV namespace created successfully"); + + return kvNamespace; + } catch (error) { + throw error; + } +}; \ No newline at end of file diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts new file mode 100644 index 0000000..4581500 --- /dev/null +++ b/scripts/deploy/index.ts @@ -0,0 +1,422 @@ +import { NotFoundError } from "cloudflare"; +import "dotenv/config"; +import { execSync } from "node:child_process"; +import { readFileSync, writeFileSync, existsSync } from "node:fs"; +import { resolve } from "node:path"; +import { + createDatabase, + createKVNamespace, + createPages, + getDatabase, + getKVNamespace, + getPages, +} from "./cloudflare"; + +const PROJECT_NAME = process.env.PROJECT_NAME || "moemail"; +const DATABASE_NAME = process.env.DATABASE_NAME || "moemail-db"; +const KV_NAMESPACE_NAME = process.env.KV_NAME || "moemail-kv"; +const PROJECT_URL = process.env.PROJECT_URL; +const DATABASE_ID = process.env.DATABASE_ID || ""; +const KV_NAMESPACE_ID = process.env.KV_NAMESPACE_ID || ""; + +/** + * 验证必要的环境变量 + */ +const validateEnvironment = () => { + const requiredEnvVars = ["CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_TOKEN"]; + const missing = requiredEnvVars.filter((varName) => !process.env[varName]); + + if (missing.length > 0) { + throw new Error( + `Missing required environment variables: ${missing.join(", ")}` + ); + } +}; + +/** + * 处理JSON配置文件 + */ +const setupConfigFile = (examplePath: string, targetPath: string) => { + try { + // 如果目标文件已存在,则跳过 + if (existsSync(targetPath)) { + console.log(`✨ Configuration ${targetPath} already exists.`); + return; + } + + if (!existsSync(examplePath)) { + console.log(`⚠️ Example file ${examplePath} does not exist, skipping...`); + return; + } + + const configContent = readFileSync(examplePath, "utf-8"); + const json = JSON.parse(configContent); + + // 处理数据库配置 + if (json.d1_databases && json.d1_databases.length > 0) { + json.d1_databases[0].database_name = DATABASE_NAME; + if (DATABASE_ID) { + json.d1_databases[0].database_id = DATABASE_ID; + } + } + + // 处理KV配置 + if (json.kv_namespaces && json.kv_namespaces.length > 0 && KV_NAMESPACE_ID) { + json.kv_namespaces[0].id = KV_NAMESPACE_ID; + } + + // 写入配置文件 + writeFileSync(targetPath, JSON.stringify(json, null, 2)); + console.log(`✅ Configuration ${targetPath} setup successfully.`); + } catch (error) { + console.error(`❌ Failed to setup ${targetPath}:`, error); + throw error; + } +}; + +/** + * 设置所有Wrangler配置文件 + */ +const setupWranglerConfigs = () => { + console.log("🔧 Setting up Wrangler configuration files..."); + + const configs = [ + { example: "wrangler.example.json", target: "wrangler.json" }, + { example: "wrangler.email.example.json", target: "wrangler.email.json" }, + { example: "wrangler.cleanup.example.json", target: "wrangler.cleanup.json" }, + ]; + + // 处理每个配置文件 + for (const config of configs) { + setupConfigFile( + resolve(config.example), + resolve(config.target) + ); + } +}; + +/** + * 更新数据库ID到所有配置文件 + */ +const updateDatabaseConfig = (dbId: string) => { + console.log(`📝 Updating database ID (${dbId}) in configurations...`); + + // 更新环境变量 + updateEnvVar("DATABASE_ID", dbId); + + // 更新所有配置文件 + const configFiles = [ + "wrangler.json", + "wrangler.email.json", + "wrangler.cleanup.json", + ]; + + for (const filename of configFiles) { + const configPath = resolve(filename); + if (!existsSync(configPath)) continue; + + try { + const json = JSON.parse(readFileSync(configPath, "utf-8")); + if (json.d1_databases && json.d1_databases.length > 0) { + json.d1_databases[0].database_id = dbId; + } + writeFileSync(configPath, JSON.stringify(json, null, 2)); + console.log(`✅ Updated database ID in ${filename}`); + } catch (error) { + console.error(`❌ Failed to update ${filename}:`, error); + } + } +}; + +/** + * 更新KV命名空间ID到所有配置文件 + */ +const updateKVConfig = (namespaceId: string) => { + console.log(`📝 Updating KV namespace ID (${namespaceId}) in configurations...`); + + // 更新环境变量 + updateEnvVar("KV_NAMESPACE_ID", namespaceId); + + // KV命名空间只在主wrangler.json中使用 + const wranglerPath = resolve("wrangler.json"); + if (existsSync(wranglerPath)) { + try { + const json = JSON.parse(readFileSync(wranglerPath, "utf-8")); + if (json.kv_namespaces && json.kv_namespaces.length > 0) { + json.kv_namespaces[0].id = namespaceId; + } + writeFileSync(wranglerPath, JSON.stringify(json, null, 2)); + console.log(`✅ Updated KV namespace ID in wrangler.json`); + } catch (error) { + console.error(`❌ Failed to update wrangler.json:`, error); + } + } +}; + +/** + * 检查并创建数据库 + */ +const checkAndCreateDatabase = async () => { + console.log(`🔍 Checking if database "${DATABASE_NAME}" exists...`); + + try { + const database = await getDatabase(); + + if (!database || !database.uuid) { + throw new Error('Database object is missing a valid UUID'); + } + + updateDatabaseConfig(database.uuid); + console.log(`✅ Database "${DATABASE_NAME}" already exists (ID: ${database.uuid})`); + } catch (error) { + if (error instanceof NotFoundError) { + console.log(`⚠️ Database not found, creating new database...`); + try { + const database = await createDatabase(); + + if (!database || !database.uuid) { + throw new Error('Database object is missing a valid UUID'); + } + + updateDatabaseConfig(database.uuid); + console.log(`✅ Database "${DATABASE_NAME}" created successfully (ID: ${database.uuid})`); + } catch (createError) { + console.error(`❌ Failed to create database:`, createError); + throw createError; + } + } else { + console.error(`❌ An error occurred while checking the database:`, error); + throw error; + } + } +}; + +/** + * 迁移数据库 + */ +const migrateDatabase = () => { + console.log("📝 Migrating remote database..."); + try { + execSync("pnpm run db:migrate-remote", { stdio: "inherit" }); + console.log("✅ Database migration completed successfully"); + } catch (error) { + console.error("❌ Database migration failed:", error); + throw error; + } +}; + +/** + * 检查并创建KV命名空间 + */ +const checkAndCreateKVNamespace = async () => { + console.log(`🔍 Checking if KV namespace "${KV_NAMESPACE_NAME}" exists...`); + + try { + if (!KV_NAMESPACE_ID) { + console.log("⚠️ KV_NAMESPACE_ID is not set, creating a new KV namespace..."); + const namespace = await createKVNamespace(); + updateKVConfig(namespace.id); + console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" created successfully (ID: ${namespace.id})`); + return; + } + + const namespace = await getKVNamespace(KV_NAMESPACE_ID); + console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" already exists (ID: ${namespace.id})`); + } catch (error) { + if (error instanceof NotFoundError || (error instanceof Error && error.message?.includes("required"))) { + console.log(`⚠️ KV namespace not found or invalid, creating new KV namespace...`); + try { + const namespace = await createKVNamespace(); + updateKVConfig(namespace.id); + console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" created successfully (ID: ${namespace.id})`); + } catch (createError) { + console.error(`❌ Failed to create KV namespace:`, createError); + throw createError; + } + } else { + console.error(`❌ An error occurred while checking the KV namespace:`, error); + throw error; + } + } +}; + +/** + * 检查并创建Pages项目 + */ +const checkAndCreatePages = async () => { + console.log(`🔍 Checking if project "${PROJECT_NAME}" exists...`); + + try { + await getPages(); + console.log("✅ Project already exists, proceeding with update..."); + } catch (error) { + if (error instanceof NotFoundError) { + console.log("⚠️ Project not found, creating new project..."); + const pages = await createPages(); + + if (!PROJECT_URL && pages.subdomain) { + console.log("⚠️ PROJECT_URL is empty, using pages default domain..."); + console.log("📝 Updating environment variables..."); + + // 更新环境变量为默认的Pages域名 + const appUrl = `https://${pages.subdomain}`; + updateEnvVar("PROJECT_URL", appUrl); + } + } else { + console.error(`❌ An error occurred while checking the project:`, error); + throw error; + } + } +}; + +/** + * 推送Pages密钥 + */ +const pushPagesSecret = () => { + console.log("🔐 Pushing environment secrets to Pages..."); + + try { + // 确保.env文件存在 + if (!existsSync(resolve('.env'))) { + setupEnvFile(); + } + + execSync(`pnpm dlx wrangler pages secret bulk .env`, { stdio: "inherit" }); + console.log("✅ Secrets pushed successfully"); + } catch (error) { + console.error("❌ Failed to push secrets:", error); + throw error; + } +}; + +/** + * 部署Pages应用 + */ +const deployPages = () => { + console.log("🚧 Deploying to Cloudflare Pages..."); + try { + execSync("pnpm run build:pages && pnpm dlx wrangler pages deploy .vercel/output/static --branch main", { stdio: "inherit" }); + console.log("✅ Pages deployment completed successfully"); + } catch (error) { + console.error("❌ Pages deployment failed:", error); + throw error; + } +}; + +/** + * 部署Email Worker + */ +const deployEmailWorker = () => { + console.log("🚧 Deploying Email Worker..."); + try { + execSync("pnpm dlx wrangler deploy --config wrangler.email.json", { stdio: "inherit" }); + console.log("✅ Email Worker deployed successfully"); + } catch (error) { + console.error("❌ Email Worker deployment failed:", error); + // 继续执行而不中断 + } +}; + +/** + * 部署Cleanup Worker + */ +const deployCleanupWorker = () => { + console.log("🚧 Deploying Cleanup Worker..."); + try { + execSync("pnpm dlx wrangler deploy --config wrangler.cleanup.json", { stdio: "inherit" }); + console.log("✅ Cleanup Worker deployed successfully"); + } catch (error) { + console.error("❌ Cleanup Worker deployment failed:", error); + // 继续执行而不中断 + } +}; + +/** + * 创建或更新环境变量文件 + */ +const setupEnvFile = () => { + console.log("📄 Setting up environment file..."); + const envFilePath = resolve(".env"); + const envExamplePath = resolve(".env.example"); + + // 如果.env文件不存在,则从.env.example复制创建 + if (!existsSync(envFilePath) && existsSync(envExamplePath)) { + console.log("⚠️ .env file does not exist, creating from example..."); + + // 从示例文件复制 + let envContent = readFileSync(envExamplePath, "utf-8"); + + // 填充当前的环境变量 + const envVarMatches = envContent.match(/^([A-Z_]+)\s*=\s*".*?"/gm); + if (envVarMatches) { + for (const match of envVarMatches) { + const varName = match.split("=")[0].trim(); + if (process.env[varName]) { + const regex = new RegExp(`${varName}\\s*=\\s*".*?"`, "g"); + envContent = envContent.replace(regex, `${varName} = "${process.env[varName]}"`); + } + } + } + + writeFileSync(envFilePath, envContent); + console.log("✅ .env file created from example"); + } else if (existsSync(envFilePath)) { + console.log("✨ .env file already exists"); + } else { + console.error("❌ .env.example file not found!"); + throw new Error(".env.example file not found"); + } +}; + +/** + * 更新环境变量 + */ +const updateEnvVar = (name: string, value: string) => { + // 首先更新进程环境变量 + process.env[name] = value; + + // 然后尝试更新.env文件 + const envFilePath = resolve(".env"); + if (!existsSync(envFilePath)) { + setupEnvFile(); + } + + let envContent = readFileSync(envFilePath, "utf-8"); + const regex = new RegExp(`^${name}\\s*=\\s*".*?"`, "m"); + + if (envContent.match(regex)) { + envContent = envContent.replace(regex, `${name} = "${value}"`); + } else { + envContent += `\n${name} = "${value}"`; + } + + writeFileSync(envFilePath, envContent); + console.log(`✅ Updated ${name} in .env file`); +}; + +/** + * 主函数 + */ +const main = async () => { + try { + console.log("🚀 Starting deployment process..."); + validateEnvironment(); + setupEnvFile(); + setupWranglerConfigs(); + await checkAndCreateDatabase(); + migrateDatabase(); + await checkAndCreateKVNamespace(); + await checkAndCreatePages(); + pushPagesSecret(); + deployPages(); + deployEmailWorker(); + deployCleanupWorker(); + + console.log("🎉 Deployment completed successfully"); + } catch (error) { + console.error("❌ Deployment failed:", error); + process.exit(1); + } +}; + +main(); diff --git a/wrangler.cleanup.example.json b/wrangler.cleanup.example.json new file mode 100644 index 0000000..5ca3a68 --- /dev/null +++ b/wrangler.cleanup.example.json @@ -0,0 +1,18 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "cleanup-worker", + "main": "workers/cleanup.ts", + "compatibility_date": "2024-03-20", + "compatibility_flags": ["nodejs_compat"], + "triggers": { + "crons": ["0 * * * *"] + }, + "d1_databases": [ + { + "binding": "DB", + "migrations_dir": "drizzle", + "database_name": "${DATABASE_NAME}", + "database_id": "${DATABASE_ID}" + } + ] +} diff --git a/wrangler.cleanup.example.toml b/wrangler.cleanup.example.toml deleted file mode 100644 index 121a652..0000000 --- a/wrangler.cleanup.example.toml +++ /dev/null @@ -1,14 +0,0 @@ -name = "cleanup-worker" -main = "workers/cleanup.ts" -compatibility_date = "2024-03-20" -compatibility_flags = ["nodejs_compat"] - -# 每 1 小时运行一次 -[triggers] -crons = ["0 * * * *"] - -[[d1_databases]] -binding = "DB" -migrations_dir = "drizzle" -database_name = "" -database_id = "" diff --git a/wrangler.email.example.json b/wrangler.email.example.json new file mode 100644 index 0000000..dadd49e --- /dev/null +++ b/wrangler.email.example.json @@ -0,0 +1,15 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "email-receiver-worker", + "compatibility_date": "2024-03-20", + "compatibility_flags": ["nodejs_compat"], + "main": "workers/email-receiver.ts", + "d1_databases": [ + { + "binding": "DB", + "migrations_dir": "drizzle", + "database_name": "${DATABASE_NAME}", + "database_id": "${DATABASE_ID}" + } + ] +} diff --git a/wrangler.email.example.toml b/wrangler.email.example.toml deleted file mode 100644 index 3d0eade..0000000 --- a/wrangler.email.example.toml +++ /dev/null @@ -1,11 +0,0 @@ -name = "email-receiver-worker" -compatibility_date = "2024-03-20" -compatibility_flags = ["nodejs_compat"] -main = "workers/email-receiver.ts" - - -[[d1_databases]] -binding = "DB" -migrations_dir = "drizzle" -database_name = "" -database_id = "" diff --git a/wrangler.example.json b/wrangler.example.json new file mode 100644 index 0000000..2c6bb08 --- /dev/null +++ b/wrangler.example.json @@ -0,0 +1,21 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "moemail", + "compatibility_date": "2024-03-20", + "compatibility_flags": ["nodejs_compat"], + "pages_build_output_dir": ".vercel/output/static", + "d1_databases": [ + { + "binding": "DB", + "database_name": "${DATABASE_NAME}", + "database_id": "${DATABASE_ID}", + "migrations_dir": "drizzle" + } + ], + "kv_namespaces": [ + { + "binding": "SITE_CONFIG", + "id": "${KV_NAMESPACE_ID}" + } + ] +} diff --git a/wrangler.example.toml b/wrangler.example.toml deleted file mode 100644 index b65dd32..0000000 --- a/wrangler.example.toml +++ /dev/null @@ -1,14 +0,0 @@ -name = "moemail" -compatibility_date = "2024-03-20" -compatibility_flags = ["nodejs_compat"] -pages_build_output_dir = ".vercel/output/static" - -[[d1_databases]] -binding = "DB" -migrations_dir = "drizzle" -database_name = "" -database_id = "" - -[[kv_namespaces]] -binding = "SITE_CONFIG" -id = "" \ No newline at end of file From cd429b96d8aa56d141728194644d7990e0049c19 Mon Sep 17 00:00:00 2001 From: sunny Date: Tue, 4 Mar 2025 17:31:54 +0800 Subject: [PATCH 2/7] fix: Database migration failed --- package.json | 6 +- pnpm-lock.yaml | 281 +++++++++++++++++++++++++++++++++++++++++++-- scripts/migrate.ts | 17 ++- 3 files changed, 284 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index d643ea9..8c0e90d 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "start": "next start", "lint": "next lint", "build:pages": "npx @cloudflare/next-on-pages", - "db:migrate-local": "bun run scripts/migrate.ts local", - "db:migrate-remote": "bun run scripts/migrate.ts remote", + "db:migrate-local": "tsx scripts/migrate.ts local", + "db:migrate-remote": "tsx scripts/migrate.ts remote", "webhook-test-server": "bun run scripts/webhook-test-server.ts", "generate-test-data": "wrangler dev scripts/generate-test-data.ts", "dev:cleanup": "wrangler dev --config wrangler.cleanup.json --test-scheduled", @@ -53,7 +53,6 @@ }, "devDependencies": { "@cloudflare/workers-types": "^4.20241127.0", - "@iarna/toml": "^3.0.0", "@types/bun": "^1.1.14", "@types/next-pwa": "^5.6.9", "@types/node": "^20", @@ -67,6 +66,7 @@ "eslint-config-next": "15.0.3", "postcss": "^8", "tailwindcss": "^3.4.1", + "tsx": "^4.19.3", "typescript": "^5", "vercel": "39.1.1", "wrangler": "^3.91.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6723a33..e112e7c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -102,9 +102,6 @@ importers: '@cloudflare/workers-types': specifier: ^4.20241127.0 version: 4.20241205.0 - '@iarna/toml': - specifier: ^3.0.0 - version: 3.0.0 '@types/bun': specifier: ^1.1.14 version: 1.1.14 @@ -144,6 +141,9 @@ importers: tailwindcss: specifier: ^3.4.1 version: 3.4.16(ts-node@10.9.1(@types/node@20.17.9)(typescript@5.7.2)) + tsx: + specifier: ^4.19.3 + version: 4.19.3 typescript: specifier: ^5 version: 5.7.2 @@ -790,6 +790,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.25.0': + resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.17.19': resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} engines: {node: '>=12'} @@ -808,6 +814,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.25.0': + resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.15.18': resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} engines: {node: '>=12'} @@ -832,6 +844,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.25.0': + resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.17.19': resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} engines: {node: '>=12'} @@ -850,6 +868,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.25.0': + resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.17.19': resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} engines: {node: '>=12'} @@ -868,6 +892,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.25.0': + resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.17.19': resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} engines: {node: '>=12'} @@ -886,6 +916,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.25.0': + resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.17.19': resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} engines: {node: '>=12'} @@ -904,6 +940,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.25.0': + resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.17.19': resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} engines: {node: '>=12'} @@ -922,6 +964,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.25.0': + resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.17.19': resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} engines: {node: '>=12'} @@ -940,6 +988,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.25.0': + resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.17.19': resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} engines: {node: '>=12'} @@ -958,6 +1012,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.25.0': + resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.17.19': resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} engines: {node: '>=12'} @@ -976,6 +1036,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.25.0': + resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.15.18': resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} engines: {node: '>=12'} @@ -1000,6 +1066,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.25.0': + resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.17.19': resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} engines: {node: '>=12'} @@ -1018,6 +1090,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.25.0': + resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.17.19': resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} engines: {node: '>=12'} @@ -1036,6 +1114,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.25.0': + resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.17.19': resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} engines: {node: '>=12'} @@ -1054,6 +1138,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.25.0': + resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.17.19': resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} engines: {node: '>=12'} @@ -1072,6 +1162,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.25.0': + resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.17.19': resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} engines: {node: '>=12'} @@ -1090,6 +1186,18 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.25.0': + resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.0': + resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.17.19': resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} engines: {node: '>=12'} @@ -1108,6 +1216,18 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.0': + resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.0': + resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.17.19': resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} engines: {node: '>=12'} @@ -1126,6 +1246,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.25.0': + resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.17.19': resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} engines: {node: '>=12'} @@ -1144,6 +1270,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.25.0': + resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.17.19': resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} engines: {node: '>=12'} @@ -1162,6 +1294,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.25.0': + resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.17.19': resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} engines: {node: '>=12'} @@ -1180,6 +1318,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.25.0': + resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.17.19': resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} engines: {node: '>=12'} @@ -1198,6 +1342,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.25.0': + resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.1': resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1247,9 +1397,6 @@ packages: '@humanwhocodes/object-schema@2.0.3': resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - '@iarna/toml@3.0.0': - resolution: {integrity: sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==} - '@img/sharp-darwin-arm64@0.33.5': resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -3446,6 +3593,11 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.25.0: + resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -5323,6 +5475,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.19.3: + resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==} + engines: {node: '>=18.0.0'} + hasBin: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -6482,6 +6639,9 @@ snapshots: '@esbuild/aix-ppc64@0.19.12': optional: true + '@esbuild/aix-ppc64@0.25.0': + optional: true + '@esbuild/android-arm64@0.17.19': optional: true @@ -6491,6 +6651,9 @@ snapshots: '@esbuild/android-arm64@0.19.12': optional: true + '@esbuild/android-arm64@0.25.0': + optional: true + '@esbuild/android-arm@0.15.18': optional: true @@ -6503,6 +6666,9 @@ snapshots: '@esbuild/android-arm@0.19.12': optional: true + '@esbuild/android-arm@0.25.0': + optional: true + '@esbuild/android-x64@0.17.19': optional: true @@ -6512,6 +6678,9 @@ snapshots: '@esbuild/android-x64@0.19.12': optional: true + '@esbuild/android-x64@0.25.0': + optional: true + '@esbuild/darwin-arm64@0.17.19': optional: true @@ -6521,6 +6690,9 @@ snapshots: '@esbuild/darwin-arm64@0.19.12': optional: true + '@esbuild/darwin-arm64@0.25.0': + optional: true + '@esbuild/darwin-x64@0.17.19': optional: true @@ -6530,6 +6702,9 @@ snapshots: '@esbuild/darwin-x64@0.19.12': optional: true + '@esbuild/darwin-x64@0.25.0': + optional: true + '@esbuild/freebsd-arm64@0.17.19': optional: true @@ -6539,6 +6714,9 @@ snapshots: '@esbuild/freebsd-arm64@0.19.12': optional: true + '@esbuild/freebsd-arm64@0.25.0': + optional: true + '@esbuild/freebsd-x64@0.17.19': optional: true @@ -6548,6 +6726,9 @@ snapshots: '@esbuild/freebsd-x64@0.19.12': optional: true + '@esbuild/freebsd-x64@0.25.0': + optional: true + '@esbuild/linux-arm64@0.17.19': optional: true @@ -6557,6 +6738,9 @@ snapshots: '@esbuild/linux-arm64@0.19.12': optional: true + '@esbuild/linux-arm64@0.25.0': + optional: true + '@esbuild/linux-arm@0.17.19': optional: true @@ -6566,6 +6750,9 @@ snapshots: '@esbuild/linux-arm@0.19.12': optional: true + '@esbuild/linux-arm@0.25.0': + optional: true + '@esbuild/linux-ia32@0.17.19': optional: true @@ -6575,6 +6762,9 @@ snapshots: '@esbuild/linux-ia32@0.19.12': optional: true + '@esbuild/linux-ia32@0.25.0': + optional: true + '@esbuild/linux-loong64@0.15.18': optional: true @@ -6587,6 +6777,9 @@ snapshots: '@esbuild/linux-loong64@0.19.12': optional: true + '@esbuild/linux-loong64@0.25.0': + optional: true + '@esbuild/linux-mips64el@0.17.19': optional: true @@ -6596,6 +6789,9 @@ snapshots: '@esbuild/linux-mips64el@0.19.12': optional: true + '@esbuild/linux-mips64el@0.25.0': + optional: true + '@esbuild/linux-ppc64@0.17.19': optional: true @@ -6605,6 +6801,9 @@ snapshots: '@esbuild/linux-ppc64@0.19.12': optional: true + '@esbuild/linux-ppc64@0.25.0': + optional: true + '@esbuild/linux-riscv64@0.17.19': optional: true @@ -6614,6 +6813,9 @@ snapshots: '@esbuild/linux-riscv64@0.19.12': optional: true + '@esbuild/linux-riscv64@0.25.0': + optional: true + '@esbuild/linux-s390x@0.17.19': optional: true @@ -6623,6 +6825,9 @@ snapshots: '@esbuild/linux-s390x@0.19.12': optional: true + '@esbuild/linux-s390x@0.25.0': + optional: true + '@esbuild/linux-x64@0.17.19': optional: true @@ -6632,6 +6837,12 @@ snapshots: '@esbuild/linux-x64@0.19.12': optional: true + '@esbuild/linux-x64@0.25.0': + optional: true + + '@esbuild/netbsd-arm64@0.25.0': + optional: true + '@esbuild/netbsd-x64@0.17.19': optional: true @@ -6641,6 +6852,12 @@ snapshots: '@esbuild/netbsd-x64@0.19.12': optional: true + '@esbuild/netbsd-x64@0.25.0': + optional: true + + '@esbuild/openbsd-arm64@0.25.0': + optional: true + '@esbuild/openbsd-x64@0.17.19': optional: true @@ -6650,6 +6867,9 @@ snapshots: '@esbuild/openbsd-x64@0.19.12': optional: true + '@esbuild/openbsd-x64@0.25.0': + optional: true + '@esbuild/sunos-x64@0.17.19': optional: true @@ -6659,6 +6879,9 @@ snapshots: '@esbuild/sunos-x64@0.19.12': optional: true + '@esbuild/sunos-x64@0.25.0': + optional: true + '@esbuild/win32-arm64@0.17.19': optional: true @@ -6668,6 +6891,9 @@ snapshots: '@esbuild/win32-arm64@0.19.12': optional: true + '@esbuild/win32-arm64@0.25.0': + optional: true + '@esbuild/win32-ia32@0.17.19': optional: true @@ -6677,6 +6903,9 @@ snapshots: '@esbuild/win32-ia32@0.19.12': optional: true + '@esbuild/win32-ia32@0.25.0': + optional: true + '@esbuild/win32-x64@0.17.19': optional: true @@ -6686,6 +6915,9 @@ snapshots: '@esbuild/win32-x64@0.19.12': optional: true + '@esbuild/win32-x64@0.25.0': + optional: true + '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)': dependencies: eslint: 8.57.1 @@ -6740,8 +6972,6 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} - '@iarna/toml@3.0.0': {} - '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.0.4 @@ -8985,6 +9215,34 @@ snapshots: '@esbuild/win32-ia32': 0.19.12 '@esbuild/win32-x64': 0.19.12 + esbuild@0.25.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.0 + '@esbuild/android-arm': 0.25.0 + '@esbuild/android-arm64': 0.25.0 + '@esbuild/android-x64': 0.25.0 + '@esbuild/darwin-arm64': 0.25.0 + '@esbuild/darwin-x64': 0.25.0 + '@esbuild/freebsd-arm64': 0.25.0 + '@esbuild/freebsd-x64': 0.25.0 + '@esbuild/linux-arm': 0.25.0 + '@esbuild/linux-arm64': 0.25.0 + '@esbuild/linux-ia32': 0.25.0 + '@esbuild/linux-loong64': 0.25.0 + '@esbuild/linux-mips64el': 0.25.0 + '@esbuild/linux-ppc64': 0.25.0 + '@esbuild/linux-riscv64': 0.25.0 + '@esbuild/linux-s390x': 0.25.0 + '@esbuild/linux-x64': 0.25.0 + '@esbuild/netbsd-arm64': 0.25.0 + '@esbuild/netbsd-x64': 0.25.0 + '@esbuild/openbsd-arm64': 0.25.0 + '@esbuild/openbsd-x64': 0.25.0 + '@esbuild/sunos-x64': 0.25.0 + '@esbuild/win32-arm64': 0.25.0 + '@esbuild/win32-ia32': 0.25.0 + '@esbuild/win32-x64': 0.25.0 + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -11015,6 +11273,13 @@ snapshots: tslib@2.8.1: {} + tsx@4.19.3: + dependencies: + esbuild: 0.25.0 + get-tsconfig: 4.8.1 + optionalDependencies: + fsevents: 2.3.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/scripts/migrate.ts b/scripts/migrate.ts index 2b3d586..4b34f78 100644 --- a/scripts/migrate.ts +++ b/scripts/migrate.ts @@ -1,4 +1,3 @@ -import { parse } from '@iarna/toml' import { readFileSync } from 'fs' import { exec } from 'child_process' import { promisify } from 'util' @@ -27,23 +26,23 @@ async function migrate() { process.exit(1) } - // Read wrangler.toml - const wranglerPath = join(process.cwd(), 'wrangler.toml') + // Read wrangler.json + const wranglerPath = join(process.cwd(), 'wrangler.json') let wranglerContent: string try { wranglerContent = readFileSync(wranglerPath, 'utf-8') // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { - console.error('Error: wrangler.toml not found') + console.error('Error: wrangler.json not found') process.exit(1) } - // Parse wrangler.toml - const config = parse(wranglerContent) as unknown as WranglerConfig + // Parse wrangler.json + const config = JSON.parse(wranglerContent) as WranglerConfig if (!config.d1_databases?.[0]?.database_name) { - console.error('Error: Database name not found in wrangler.toml') + console.error('Error: Database name not found in wrangler.json') process.exit(1) } @@ -53,7 +52,7 @@ async function migrate() { console.log('Generating migrations...') await execAsync('drizzle-kit generate') - // Apply migrations + // Applying migrations console.log(`Applying migrations to ${mode} database: ${dbName}`) await execAsync(`wrangler d1 migrations apply ${dbName} --${mode}`) @@ -64,4 +63,4 @@ async function migrate() { } } -migrate() \ No newline at end of file +migrate() \ No newline at end of file From ed8885a2d8d18684ac83e119f2790b0e9d5ecbaf Mon Sep 17 00:00:00 2001 From: sunny Date: Wed, 5 Mar 2025 11:03:54 +0800 Subject: [PATCH 3/7] chore: Change environment variable PROJECT_URL to CUSTOM_DOMAIN --- .env.example | 2 +- scripts/deploy/cloudflare.ts | 6 +++--- scripts/deploy/index.ts | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.env.example b/.env.example index 1e366ec..ee2c4ef 100644 --- a/.env.example +++ b/.env.example @@ -8,4 +8,4 @@ DATABASE_NAME = "" DATABASE_ID = "" KV_NAMESPACE_ID = "" -PROJECT_URL = "" \ No newline at end of file +CUSTOM_DOMAIN = "" \ No newline at end of file diff --git a/scripts/deploy/cloudflare.ts b/scripts/deploy/cloudflare.ts index c5ca720..80aa128 100644 --- a/scripts/deploy/cloudflare.ts +++ b/scripts/deploy/cloudflare.ts @@ -3,7 +3,7 @@ import "dotenv/config"; const CF_ACCOUNT_ID = process.env.CLOUDFLARE_ACCOUNT_ID!; const CF_API_TOKEN = process.env.CLOUDFLARE_API_TOKEN; -const PROJECT_URL = process.env.PROJECT_URL; +const CUSTOM_DOMAIN = process.env.CUSTOM_DOMAIN; const PROJECT_NAME = process.env.PROJECT_NAME || "moemail"; const DB_NAME = process.env.DATABASE_NAME || "moemail-db"; const KV_NAMESPACE_NAME = process.env.KV_NAME || "moemail-kv"; @@ -34,12 +34,12 @@ export const createPages = async () => { production_branch: "main", }); - if (PROJECT_URL) { + if (CUSTOM_DOMAIN) { console.log("🔗 Setting pages domain..."); await client.pages.projects.domains.create(PROJECT_NAME, { account_id: CF_ACCOUNT_ID, - name: PROJECT_URL?.split("://")[1], + name: CUSTOM_DOMAIN?.split("://")[1], }); console.log("✅ Pages domain set successfully"); diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts index 4581500..44b7ea1 100644 --- a/scripts/deploy/index.ts +++ b/scripts/deploy/index.ts @@ -15,7 +15,7 @@ import { const PROJECT_NAME = process.env.PROJECT_NAME || "moemail"; const DATABASE_NAME = process.env.DATABASE_NAME || "moemail-db"; const KV_NAMESPACE_NAME = process.env.KV_NAME || "moemail-kv"; -const PROJECT_URL = process.env.PROJECT_URL; +const CUSTOM_DOMAIN = process.env.CUSTOM_DOMAIN; const DATABASE_ID = process.env.DATABASE_ID || ""; const KV_NAMESPACE_ID = process.env.KV_NAMESPACE_ID || ""; @@ -254,13 +254,13 @@ const checkAndCreatePages = async () => { console.log("⚠️ Project not found, creating new project..."); const pages = await createPages(); - if (!PROJECT_URL && pages.subdomain) { - console.log("⚠️ PROJECT_URL is empty, using pages default domain..."); + if (!CUSTOM_DOMAIN && pages.subdomain) { + console.log("⚠️ CUSTOM_DOMAIN is empty, using pages default domain..."); console.log("📝 Updating environment variables..."); // 更新环境变量为默认的Pages域名 const appUrl = `https://${pages.subdomain}`; - updateEnvVar("PROJECT_URL", appUrl); + updateEnvVar("CUSTOM_DOMAIN", appUrl); } } else { console.error(`❌ An error occurred while checking the project:`, error); From 200d82f874e780ca56b3f2dfde140a92a95dd16d Mon Sep 17 00:00:00 2001 From: sunny Date: Wed, 5 Mar 2025 11:16:00 +0800 Subject: [PATCH 4/7] chore: Extract and push only runtime-required environment variables --- scripts/deploy/index.ts | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts index 44b7ea1..a308c75 100644 --- a/scripts/deploy/index.ts +++ b/scripts/deploy/index.ts @@ -281,7 +281,40 @@ const pushPagesSecret = () => { setupEnvFile(); } - execSync(`pnpm dlx wrangler pages secret bulk .env`, { stdio: "inherit" }); + // 创建一个临时文件,只包含运行时所需的环境变量 + const envContent = readFileSync(resolve('.env'), 'utf-8'); + const runtimeEnvFile = resolve('.env.runtime'); + + // 定义运行时所需的环境变量列表 + const runtimeEnvVars = ['AUTH_GITHUB_ID', 'AUTH_GITHUB_SECRET', 'AUTH_SECRET']; + + // 从.env文件中提取运行时变量 + const runtimeEnvContent = envContent + .split('\n') + .filter(line => { + const trimmedLine = line.trim(); + // 跳过注释和空行 + if (!trimmedLine || trimmedLine.startsWith('#')) return false; + + // 检查是否为运行时所需的环境变量 + for (const varName of runtimeEnvVars) { + if (line.startsWith(`${varName} =`) || line.startsWith(`${varName}=`)) { + return true; + } + } + return false; + }) + .join('\n'); + + // 写入临时文件 + writeFileSync(runtimeEnvFile, runtimeEnvContent); + + // 使用临时文件推送secrets + execSync(`pnpm dlx wrangler pages secret bulk ${runtimeEnvFile}`, { stdio: "inherit" }); + + // 清理临时文件 + execSync(`rm ${runtimeEnvFile}`, { stdio: "inherit" }); + console.log("✅ Secrets pushed successfully"); } catch (error) { console.error("❌ Failed to push secrets:", error); From da979d2a51ebbf99cdc85c8a1ed2d5c40d41b74a Mon Sep 17 00:00:00 2001 From: sunny Date: Wed, 5 Mar 2025 11:58:15 +0800 Subject: [PATCH 5/7] chore: Update and simplify deployment workflow --- .github/workflows/deploy.yml | 143 +++++------------------------------ 1 file changed, 19 insertions(+), 124 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4108fc2..fe548f1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -5,22 +5,6 @@ on: tags: - 'v*' workflow_dispatch: - inputs: - run_migrations: - description: 'Run database migrations' - type: boolean - required: true - default: false - deploy_email_worker: - description: 'Deploy email Worker' - type: boolean - required: true - default: false - deploy_cleanup_worker: - description: 'Deploy cleanup Worker' - type: boolean - required: true - default: false jobs: deploy: @@ -51,112 +35,23 @@ jobs: - name: Install Dependencies run: pnpm install --frozen-lockfile - # Check if database migrations have changes - - name: Check migrations changes - id: check_migrations - if: github.event_name == 'push' + - name: Run deploy script + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + PROJECT_NAME: ${{ secrets.PROJECT_NAME }} + DATABASE_NAME: ${{ secrets.DATABASE_NAME }} + KV_NAME: ${{ secrets.KV_NAME }} + CUSTOM_DOMAIN: ${{ secrets.CUSTOM_DOMAIN }} + DATABASE_ID: ${{ secrets.DATABASE_ID }} + KV_NAMESPACE_ID: ${{ secrets.KV_NAMESPACE_ID }} + AUTH_GITHUB_ID: ${{ secrets.AUTH_GITHUB_ID }} + AUTH_GITHUB_SECRET: ${{ secrets.AUTH_GITHUB_SECRET }} + AUTH_SECRET: ${{ secrets.AUTH_SECRET }} + run: pnpm dlx tsx scripts/deploy/index.ts + + # Clean up + - name: Post deployment cleanup run: | - if [ -z "${{ steps.previoustag.outputs.tag }}" ]; then - echo "migrations_changed=true" >> $GITHUB_OUTPUT - else - if git diff ${{ steps.previoustag.outputs.tag }}..HEAD --name-only | grep -q "^drizzle/"; then - echo "migrations_changed=true" >> $GITHUB_OUTPUT - else - echo "migrations_changed=false" >> $GITHUB_OUTPUT - fi - fi - - # Process configuration files - - name: Process configuration files - run: | - # Process wrangler.example.toml - if [ -f wrangler.example.toml ]; then - cp wrangler.example.toml wrangler.toml - sed -i "s/database_name = \".*\"/database_name = \"${{ secrets.DATABASE_NAME }}\"/" wrangler.toml - sed -i "s/database_id = \".*\"/database_id = \"${{ secrets.DATABASE_ID }}\"/" wrangler.toml - sed -i "s/id = \"\"/id = \"${{ secrets.KV_NAMESPACE_ID }}\"/" wrangler.toml - fi - - # Process wrangler.email.example.toml - if [ -f wrangler.email.example.toml ]; then - cp wrangler.email.example.toml wrangler.email.toml - sed -i "s/database_name = \".*\"/database_name = \"${{ secrets.DATABASE_NAME }}\"/" wrangler.email.toml - sed -i "s/database_id = \".*\"/database_id = \"${{ secrets.DATABASE_ID }}\"/" wrangler.email.toml - fi - - # Process wrangler.cleanup.example.toml - if [ -f wrangler.cleanup.example.toml ]; then - cp wrangler.cleanup.example.toml wrangler.cleanup.toml - sed -i "s/database_name = \".*\"/database_name = \"${{ secrets.DATABASE_NAME }}\"/" wrangler.cleanup.toml - sed -i "s/database_id = \".*\"/database_id = \"${{ secrets.DATABASE_ID }}\"/" wrangler.cleanup.toml - fi - - # Run database migrations if needed - - name: Run database migrations - if: | - github.event_name == 'push' && steps.check_migrations.outputs.migrations_changed == 'true' || - github.event_name == 'workflow_dispatch' && github.event.inputs.run_migrations == 'true' - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - run: pnpm db:migrate-remote - - # Check if workers have changes - - name: Check workers changes - id: check_changes - if: github.event_name == 'push' - run: | - if [ -z "${{ steps.previoustag.outputs.tag }}" ]; then - # Check email worker and its dependencies - if git ls-files | grep -q -E "workers/email-receiver.ts|app/lib/schema.ts|app/lib/webhook.ts|app/config/webhook.ts"; then - echo "email_worker_changed=true" >> $GITHUB_OUTPUT - else - echo "email_worker_changed=false" >> $GITHUB_OUTPUT - fi - # Check cleanup worker - if git ls-files | grep -q "workers/cleanup.ts"; then - echo "cleanup_worker_changed=true" >> $GITHUB_OUTPUT - else - echo "cleanup_worker_changed=false" >> $GITHUB_OUTPUT - fi - else - # Check email worker and its dependencies changes - if git diff ${{ steps.previoustag.outputs.tag }}..HEAD --name-only | grep -q -E "workers/email-receiver.ts|app/lib/schema.ts|app/lib/webhook.ts|app/config/webhook.ts"; then - echo "email_worker_changed=true" >> $GITHUB_OUTPUT - else - echo "email_worker_changed=false" >> $GITHUB_OUTPUT - fi - # Check cleanup worker changes - if git diff ${{ steps.previoustag.outputs.tag }}..HEAD --name-only | grep -q "workers/cleanup.ts"; then - echo "cleanup_worker_changed=true" >> $GITHUB_OUTPUT - else - echo "cleanup_worker_changed=false" >> $GITHUB_OUTPUT - fi - fi - - # Deploy Pages application - - name: Deploy Pages - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - run: pnpm run deploy:pages - - # Deploy email worker if changed or manually triggered - - name: Deploy Email Worker - if: | - github.event_name == 'push' && steps.check_changes.outputs.email_worker_changed == 'true' || - github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_email_worker == 'true' - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - run: pnpm run deploy:email - - # Deploy cleanup worker if changed or manually triggered - - name: Deploy Cleanup Worker - if: | - github.event_name == 'push' && steps.check_changes.outputs.cleanup_worker_changed == 'true' || - github.event_name == 'workflow_dispatch' && github.event.inputs.deploy_cleanup_worker == 'true' - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - run: pnpm run deploy:cleanup \ No newline at end of file + rm -f .env*.* + rm -f wrangler*.json \ No newline at end of file From b75d9ada433b1cea07cdbab572cc7af6fd3c9337 Mon Sep 17 00:00:00 2001 From: sunny Date: Wed, 5 Mar 2025 13:28:45 +0800 Subject: [PATCH 6/7] chore: Update checking kv namespace function to avoid failed to create namespace --- scripts/deploy/cloudflare.ts | 12 +++++++++++ scripts/deploy/index.ts | 40 ++++++++++++++++++------------------ 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/scripts/deploy/cloudflare.ts b/scripts/deploy/cloudflare.ts index 80aa128..8a9b375 100644 --- a/scripts/deploy/cloudflare.ts +++ b/scripts/deploy/cloudflare.ts @@ -96,6 +96,18 @@ export const getKVNamespace = async (namespaceId: string) => { } }; +export const getKVNamespaceList = async () => { + try { + const kvNamespaces = await client.kv.namespaces.list({ + account_id: CF_ACCOUNT_ID, + }); + + return kvNamespaces; + } catch (error) { + throw error; + } +} + export const createKVNamespace = async () => { try { console.log(`🆕 Creating new KV namespace: "${KV_NAMESPACE_NAME}"`); diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts index a308c75..f4cd02a 100644 --- a/scripts/deploy/index.ts +++ b/scripts/deploy/index.ts @@ -9,6 +9,7 @@ import { createPages, getDatabase, getKVNamespace, + getKVNamespaceList, getPages, } from "./cloudflare"; @@ -212,31 +213,30 @@ const checkAndCreateKVNamespace = async () => { console.log(`🔍 Checking if KV namespace "${KV_NAMESPACE_NAME}" exists...`); try { - if (!KV_NAMESPACE_ID) { - console.log("⚠️ KV_NAMESPACE_ID is not set, creating a new KV namespace..."); - const namespace = await createKVNamespace(); - updateKVConfig(namespace.id); - console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" created successfully (ID: ${namespace.id})`); - return; - } + let namespace; - const namespace = await getKVNamespace(KV_NAMESPACE_ID); - console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" already exists (ID: ${namespace.id})`); - } catch (error) { - if (error instanceof NotFoundError || (error instanceof Error && error.message?.includes("required"))) { - console.log(`⚠️ KV namespace not found or invalid, creating new KV namespace...`); - try { - const namespace = await createKVNamespace(); + if (KV_NAMESPACE_ID) { + namespace = await getKVNamespace(KV_NAMESPACE_ID); + console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" already exists (ID: ${namespace.id})`); + } else { + console.log("⚠️ KV_NAMESPACE_ID is not set, checking by name..."); + + const namespaceList = await getKVNamespaceList(); + namespace = namespaceList.result.find(ns => ns.title === KV_NAMESPACE_NAME); + + if (namespace && namespace.id) { + updateKVConfig(namespace.id); + console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" found by name (ID: ${namespace.id})`); + } else { + console.log("⚠️ KV namespace not found by name, creating new KV namespace..."); + namespace = await createKVNamespace(); updateKVConfig(namespace.id); console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" created successfully (ID: ${namespace.id})`); - } catch (createError) { - console.error(`❌ Failed to create KV namespace:`, createError); - throw createError; } - } else { - console.error(`❌ An error occurred while checking the KV namespace:`, error); - throw error; } + } catch (error) { + console.error(`❌ An error occurred while checking the KV namespace:`, error); + throw error; } }; From 16bc357973010847ec8124e43bd2ace0e963912c Mon Sep 17 00:00:00 2001 From: sunny Date: Thu, 6 Mar 2025 14:26:10 +0800 Subject: [PATCH 7/7] chore: Remove environment variable DATABASE_ID and KV_NAMESPACE_ID, auto-pagination kvNamespaces --- .env.example | 3 +- .github/workflows/deploy.yml | 4 +- scripts/deploy/cloudflare.ts | 136 ++++++++++++++--------------------- scripts/deploy/index.ts | 47 ++++-------- 4 files changed, 66 insertions(+), 124 deletions(-) diff --git a/.env.example b/.env.example index ee2c4ef..47684bd 100644 --- a/.env.example +++ b/.env.example @@ -5,7 +5,6 @@ AUTH_SECRET = "" CLOUDFLARE_API_TOKEN = "" CLOUDFLARE_ACCOUNT_ID = "" DATABASE_NAME = "" -DATABASE_ID = "" -KV_NAMESPACE_ID = "" +KV_NAMESPACE_NAME = "" CUSTOM_DOMAIN = "" \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index fe548f1..412cb0d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -41,10 +41,8 @@ jobs: CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} PROJECT_NAME: ${{ secrets.PROJECT_NAME }} DATABASE_NAME: ${{ secrets.DATABASE_NAME }} - KV_NAME: ${{ secrets.KV_NAME }} + KV_NAMESPACE_NAME: ${{ secrets.KV_NAMESPACE_NAME }} CUSTOM_DOMAIN: ${{ secrets.CUSTOM_DOMAIN }} - DATABASE_ID: ${{ secrets.DATABASE_ID }} - KV_NAMESPACE_ID: ${{ secrets.KV_NAMESPACE_ID }} AUTH_GITHUB_ID: ${{ secrets.AUTH_GITHUB_ID }} AUTH_GITHUB_SECRET: ${{ secrets.AUTH_GITHUB_SECRET }} AUTH_SECRET: ${{ secrets.AUTH_SECRET }} diff --git a/scripts/deploy/cloudflare.ts b/scripts/deploy/cloudflare.ts index 8a9b375..2d5e6bb 100644 --- a/scripts/deploy/cloudflare.ts +++ b/scripts/deploy/cloudflare.ts @@ -5,120 +5,88 @@ const CF_ACCOUNT_ID = process.env.CLOUDFLARE_ACCOUNT_ID!; const CF_API_TOKEN = process.env.CLOUDFLARE_API_TOKEN; const CUSTOM_DOMAIN = process.env.CUSTOM_DOMAIN; const PROJECT_NAME = process.env.PROJECT_NAME || "moemail"; -const DB_NAME = process.env.DATABASE_NAME || "moemail-db"; -const KV_NAMESPACE_NAME = process.env.KV_NAME || "moemail-kv"; +const DATABASE_NAME = process.env.DATABASE_NAME || "moemail-db"; +const KV_NAMESPACE_NAME = process.env.KV_NAMESPACE_NAME || "moemail-kv"; const client = new Cloudflare({ apiKey: CF_API_TOKEN, }); export const getPages = async () => { - try { - const projectInfo = await client.pages.projects.get(PROJECT_NAME, { - account_id: CF_ACCOUNT_ID, - }); + const projectInfo = await client.pages.projects.get(PROJECT_NAME, { + account_id: CF_ACCOUNT_ID, + }); - return projectInfo; - } catch (error) { - throw error; - } + return projectInfo; }; export const createPages = async () => { - try { - console.log(`🆕 Creating new Cloudflare Pages project: "${PROJECT_NAME}"`); + console.log(`🆕 Creating new Cloudflare Pages project: "${PROJECT_NAME}"`); - const project = await client.pages.projects.create({ + const project = await client.pages.projects.create({ + account_id: CF_ACCOUNT_ID, + name: PROJECT_NAME, + production_branch: "main", + }); + + if (CUSTOM_DOMAIN) { + console.log("🔗 Setting pages domain..."); + + await client.pages.projects.domains.create(PROJECT_NAME, { account_id: CF_ACCOUNT_ID, - name: PROJECT_NAME, - production_branch: "main", + name: CUSTOM_DOMAIN?.split("://")[1], }); - if (CUSTOM_DOMAIN) { - console.log("🔗 Setting pages domain..."); - - await client.pages.projects.domains.create(PROJECT_NAME, { - account_id: CF_ACCOUNT_ID, - name: CUSTOM_DOMAIN?.split("://")[1], - }); - - console.log("✅ Pages domain set successfully"); - } - - console.log("✅ Project created successfully"); - - return project; - } catch (error) { - throw error; + console.log("✅ Pages domain set successfully"); } + + console.log("✅ Project created successfully"); + + return project; }; export const getDatabase = async () => { - try { - const database = await client.d1.database.get(DB_NAME, { - account_id: CF_ACCOUNT_ID, - }); + const database = await client.d1.database.get(DATABASE_NAME, { + account_id: CF_ACCOUNT_ID, + }); - return database; - } catch (error) { - throw error; - } + return database; }; export const createDatabase = async () => { - try { - console.log(`🆕 Creating new D1 database: "${DB_NAME}"`); - const database = await client.d1.database.create({ - account_id: CF_ACCOUNT_ID, - name: DB_NAME, - }); - console.log("✅ Database created successfully"); + console.log(`🆕 Creating new D1 database: "${DATABASE_NAME}"`); - return database; - } catch (error) { - throw error; - } -}; + const database = await client.d1.database.create({ + account_id: CF_ACCOUNT_ID, + name: DATABASE_NAME, + }); -export const getKVNamespace = async (namespaceId: string) => { - if (!namespaceId) { - throw new Error("KV namespace ID is required"); - } - - try { - const kvNamespace = await client.kv.namespaces.get(namespaceId, { - account_id: CF_ACCOUNT_ID, - }); + console.log("✅ Database created successfully"); - return kvNamespace; - } catch (error) { - throw error; - } + return database; }; export const getKVNamespaceList = async () => { - try { - const kvNamespaces = await client.kv.namespaces.list({ - account_id: CF_ACCOUNT_ID, - }); + const kvNamespaces = []; - return kvNamespaces; - } catch (error) { - throw error; + for await (const namespace of client.kv.namespaces.list({ + account_id: CF_ACCOUNT_ID, + })) { + kvNamespaces.push(namespace); } -} + + return kvNamespaces; +}; export const createKVNamespace = async () => { - try { - console.log(`🆕 Creating new KV namespace: "${KV_NAMESPACE_NAME}"`); - const kvNamespace = await client.kv.namespaces.create({ - account_id: CF_ACCOUNT_ID, - title: KV_NAMESPACE_NAME, - }); - console.log("✅ KV namespace created successfully"); + console.log(`🆕 Creating new KV namespace: "${KV_NAMESPACE_NAME}"`); - return kvNamespace; - } catch (error) { - throw error; - } -}; \ No newline at end of file + const kvNamespace = await client.kv.namespaces.create({ + account_id: CF_ACCOUNT_ID, + title: KV_NAMESPACE_NAME, + }); + + console.log("✅ KV namespace created successfully"); + + return kvNamespace; +}; diff --git a/scripts/deploy/index.ts b/scripts/deploy/index.ts index f4cd02a..92f7253 100644 --- a/scripts/deploy/index.ts +++ b/scripts/deploy/index.ts @@ -8,17 +8,14 @@ import { createKVNamespace, createPages, getDatabase, - getKVNamespace, getKVNamespaceList, getPages, } from "./cloudflare"; const PROJECT_NAME = process.env.PROJECT_NAME || "moemail"; const DATABASE_NAME = process.env.DATABASE_NAME || "moemail-db"; -const KV_NAMESPACE_NAME = process.env.KV_NAME || "moemail-kv"; +const KV_NAMESPACE_NAME = process.env.KV_NAMESPACE_NAME || "moemail-kv"; const CUSTOM_DOMAIN = process.env.CUSTOM_DOMAIN; -const DATABASE_ID = process.env.DATABASE_ID || ""; -const KV_NAMESPACE_ID = process.env.KV_NAMESPACE_ID || ""; /** * 验证必要的环境变量 @@ -56,14 +53,6 @@ const setupConfigFile = (examplePath: string, targetPath: string) => { // 处理数据库配置 if (json.d1_databases && json.d1_databases.length > 0) { json.d1_databases[0].database_name = DATABASE_NAME; - if (DATABASE_ID) { - json.d1_databases[0].database_id = DATABASE_ID; - } - } - - // 处理KV配置 - if (json.kv_namespaces && json.kv_namespaces.length > 0 && KV_NAMESPACE_ID) { - json.kv_namespaces[0].id = KV_NAMESPACE_ID; } // 写入配置文件 @@ -102,9 +91,6 @@ const setupWranglerConfigs = () => { const updateDatabaseConfig = (dbId: string) => { console.log(`📝 Updating database ID (${dbId}) in configurations...`); - // 更新环境变量 - updateEnvVar("DATABASE_ID", dbId); - // 更新所有配置文件 const configFiles = [ "wrangler.json", @@ -135,9 +121,6 @@ const updateDatabaseConfig = (dbId: string) => { const updateKVConfig = (namespaceId: string) => { console.log(`📝 Updating KV namespace ID (${namespaceId}) in configurations...`); - // 更新环境变量 - updateEnvVar("KV_NAMESPACE_ID", namespaceId); - // KV命名空间只在主wrangler.json中使用 const wranglerPath = resolve("wrangler.json"); if (existsSync(wranglerPath)) { @@ -215,24 +198,17 @@ const checkAndCreateKVNamespace = async () => { try { let namespace; - if (KV_NAMESPACE_ID) { - namespace = await getKVNamespace(KV_NAMESPACE_ID); - console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" already exists (ID: ${namespace.id})`); + const namespaceList = await getKVNamespaceList(); + namespace = namespaceList.find(ns => ns.title === KV_NAMESPACE_NAME); + + if (namespace && namespace.id) { + updateKVConfig(namespace.id); + console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" found by name (ID: ${namespace.id})`); } else { - console.log("⚠️ KV_NAMESPACE_ID is not set, checking by name..."); - - const namespaceList = await getKVNamespaceList(); - namespace = namespaceList.result.find(ns => ns.title === KV_NAMESPACE_NAME); - - if (namespace && namespace.id) { - updateKVConfig(namespace.id); - console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" found by name (ID: ${namespace.id})`); - } else { - console.log("⚠️ KV namespace not found by name, creating new KV namespace..."); - namespace = await createKVNamespace(); - updateKVConfig(namespace.id); - console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" created successfully (ID: ${namespace.id})`); - } + console.log("⚠️ KV namespace not found by name, creating new KV namespace..."); + namespace = await createKVNamespace(); + updateKVConfig(namespace.id); + console.log(`✅ KV namespace "${KV_NAMESPACE_NAME}" created successfully (ID: ${namespace.id})`); } } catch (error) { console.error(`❌ An error occurred while checking the KV namespace:`, error); @@ -433,6 +409,7 @@ const updateEnvVar = (name: string, value: string) => { const main = async () => { try { console.log("🚀 Starting deployment process..."); + validateEnvironment(); setupEnvFile(); setupWranglerConfigs();