From 5173cbf9d3105a1eb68703513ff30a076fd7c9cb Mon Sep 17 00:00:00 2001 From: sunny Date: Tue, 4 Mar 2025 16:38:15 +0800 Subject: [PATCH] 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