diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9dfcdd5 --- /dev/null +++ b/.env.example @@ -0,0 +1,21 @@ +## DATO (롤백용 유지) +API_TOKEN= + +## SUPABASE +DATABASE_URL= + +SUPABASE_URL= +SUPABASE_ANON_KEY= +SUPABASE_SERVICE_ROLE_KEY= + +# AWS S3 Configuration +AWS_REGION= +S3_BUCKET= +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= + +# CloudFront +CLOUDFRONT_DOMAIN= + +# Data Source (datocms | supabase) +DATA_SOURCE=datocms \ No newline at end of file diff --git a/next.config.js b/next.config.js index 5a033fb..b2c6671 100644 --- a/next.config.js +++ b/next.config.js @@ -12,9 +12,11 @@ const nextConfig = { "prod-files-secure.s3.us-west-2.amazonaws.com", "junesdevlog-s3.s3.ap-northeast-2.amazonaws.com", "img1.daumcdn.net", - "www.datocms-assets.com", + "www.datocms-assets.com", // 롤백용 유지 "image4.coupangcdn.com", - ], + ].concat( + process.env.CLOUDFRONT_DOMAIN ? [process.env.CLOUDFRONT_DOMAIN] : [], + ), }, crossOrigin: "anonymous", }; diff --git a/package.json b/package.json index 3307581..bc25b11 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,9 @@ "test": "jest --watch" }, "dependencies": { + "@aws-sdk/client-s3": "^3.975.0", + "@supabase/supabase-js": "^2.91.1", + "dotenv": "^17.2.3", "fuse.js": "^7.1.0", "github-markdown-css": "^5.3.0", "junyeol-components": "^0.1.50", @@ -66,6 +69,7 @@ "stylelint-scss": "^3.18.0", "ts-node": "^10.9.1", "tsconfig-paths-jest": "^0.0.1", + "tsx": "^4.21.0", "typescript": "5.8.3" }, "engines": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b2200e..50b5f34 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,15 @@ importers: .: dependencies: + '@aws-sdk/client-s3': + specifier: ^3.975.0 + version: 3.975.0 + '@supabase/supabase-js': + specifier: ^2.91.1 + version: 2.91.1 + dotenv: + specifier: ^17.2.3 + version: 17.2.3 fuse.js: specifier: ^7.1.0 version: 7.1.0 @@ -156,6 +165,9 @@ importers: tsconfig-paths-jest: specifier: ^0.0.1 version: 0.0.1 + tsx: + specifier: ^4.21.0 + version: 4.21.0 typescript: specifier: 5.8.3 version: 5.8.3 @@ -181,6 +193,189 @@ packages: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/crc32c@5.2.0': + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} + + '@aws-crypto/sha1-browser@5.2.0': + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} + + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-s3@3.975.0': + resolution: {integrity: sha512-aF1M/iMD29BPcpxjqoym0YFa4WR9Xie1/IhVumwOGH6TB45DaqYO7vLwantDBcYNRn/cZH6DFHksO7RmwTFBhw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/client-sso@3.974.0': + resolution: {integrity: sha512-ci+GiM0c4ULo4D79UMcY06LcOLcfvUfiyt8PzNY0vbt5O8BfCPYf4QomwVgkNcLLCYmroO4ge2Yy1EsLUlcD6g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.972.0': + resolution: {integrity: sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/core@3.973.1': + resolution: {integrity: sha512-Ocubx42QsMyVs9ANSmFpRm0S+hubWljpPLjOi9UFrtcnVJjrVJTzQ51sN0e5g4e8i8QZ7uY73zosLmgYL7kZTQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/crc64-nvme@3.972.0': + resolution: {integrity: sha512-ThlLhTqX68jvoIVv+pryOdb5coP1cX1/MaTbB9xkGDCbWbsqQcLqzPxuSoW1DCnAAIacmXCWpzUNOB9pv+xXQw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-env@3.972.1': + resolution: {integrity: sha512-/etNHqnx96phy/SjI0HRC588o4vKH5F0xfkZ13yAATV7aNrb+5gYGNE6ePWafP+FuZ3HkULSSlJFj0AxgrAqYw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-http@3.972.2': + resolution: {integrity: sha512-mXgdaUfe5oM+tWKyeZ7Vh/iQ94FrkMky1uuzwTOmFADiRcSk5uHy/e3boEFedXiT/PRGzgBmqvJVK4F6lUISCg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-ini@3.972.1': + resolution: {integrity: sha512-OdbJA3v+XlNDsrYzNPRUwr8l7gw1r/nR8l4r96MDzSBDU8WEo8T6C06SvwaXR8SpzsjO3sq5KMP86wXWg7Rj4g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-login@3.972.1': + resolution: {integrity: sha512-CccqDGL6ZrF3/EFWZefvKW7QwwRdxlHUO8NVBKNVcNq6womrPDvqB6xc9icACtE0XB0a7PLoSTkAg8bQVkTO2w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-node@3.972.1': + resolution: {integrity: sha512-DwXPk9GfuU/xG9tmCyXFVkCr6X3W8ZCoL5Ptb0pbltEx1/LCcg7T+PBqDlPiiinNCD6ilIoMJDWsnJ8ikzZA7Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-process@3.972.1': + resolution: {integrity: sha512-bi47Zigu3692SJwdBvo8y1dEwE6B61stCwCFnuRWJVTfiM84B+VTSCV661CSWJmIZzmcy7J5J3kWyxL02iHj0w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-sso@3.972.1': + resolution: {integrity: sha512-dLZVNhM7wSgVUFsgVYgI5hb5Z/9PUkT46pk/SHrSmUqfx6YDvoV4YcPtaiRqviPpEGGiRtdQMEadyOKIRqulUQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.972.1': + resolution: {integrity: sha512-YMDeYgi0u687Ay0dAq/pFPKuijrlKTgsaB/UATbxCs/FzZfMiG4If5ksywHmmW7MiYUF8VVv+uou3TczvLrN4w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-bucket-endpoint@3.972.1': + resolution: {integrity: sha512-YVvoitBdE8WOpHqIXvv49efT73F4bJ99XH2bi3Dn3mx7WngI4RwHwn/zF5i0q1Wdi5frGSCNF3vuh+pY817//w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-expect-continue@3.972.1': + resolution: {integrity: sha512-6lfl2/J/kutzw/RLu1kjbahsz4vrGPysrdxWaw8fkjLYG+6M6AswocIAZFS/LgAVi/IWRwPTx9YC0/NH2wDrSw==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-flexible-checksums@3.972.1': + resolution: {integrity: sha512-kjVVREpqeUkYQsXr78AcsJbEUlxGH7+H6yS7zkjrnu6HyEVxbdSndkKX6VpKneFOihjCAhIXlk4wf3butDHkNQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-host-header@3.972.1': + resolution: {integrity: sha512-/R82lXLPmZ9JaUGSUdKtBp2k/5xQxvBT3zZWyKiBOhyulFotlfvdlrO8TnqstBimsl4lYEYySDL+W6ldFh6ALg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-location-constraint@3.972.1': + resolution: {integrity: sha512-YisPaCbvBk9gY5aUI8jDMDKXsLZ9Fet0WYj1MviK8tZYMgxBIYHM6l3O/OHaAIujojZvamd9F3haYYYWp5/V3w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-logger@3.972.1': + resolution: {integrity: sha512-JGgFl6cHg9G2FHu4lyFIzmFN8KESBiRr84gLC3Aeni0Gt1nKm+KxWLBuha/RPcXxJygGXCcMM4AykkIwxor8RA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.972.1': + resolution: {integrity: sha512-taGzNRe8vPHjnliqXIHp9kBgIemLE/xCaRTMH1NH0cncHeaPcjxtnCroAAM9aOlPuKvBe2CpZESyvM1+D8oI7Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.972.0': + resolution: {integrity: sha512-0bcKFXWx+NZ7tIlOo7KjQ+O2rydiHdIQahrq+fN6k9Osky29v17guy68urUKfhTobR6iY6KvxkroFWaFtTgS5w==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.972.2': + resolution: {integrity: sha512-5f9x9/G+StE8+7wd9EVDF3d+J74xK+WBA3FhZwLSkf3pHFGLKzlmUfxJJE1kkXkbj/j/H+Dh3zL/hrtQE9hNsg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-ssec@3.972.1': + resolution: {integrity: sha512-fLtRTPd/MxJT2drJKft2GVGKm35PiNEeQ1Dvz1vc/WhhgAteYrp4f1SfSgjgLaYWGMExESJL4bt8Dxqp6tVsog==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/middleware-user-agent@3.972.2': + resolution: {integrity: sha512-d+Exq074wy0X6wvShg/kmZVtkah+28vMuqCtuY3cydg8LUZOJBtbAolCpEJizSyb8mJJZF9BjWaTANXL4OYnkg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/nested-clients@3.974.0': + resolution: {integrity: sha512-k3dwdo/vOiHMJc9gMnkPl1BA5aQfTrZbz+8fiDkWrPagqAioZgmo5oiaOaeX0grObfJQKDtcpPFR4iWf8cgl8Q==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/region-config-resolver@3.972.1': + resolution: {integrity: sha512-voIY8RORpxLAEgEkYaTFnkaIuRwVBEc+RjVZYcSSllPV+ZEKAacai6kNhJeE3D70Le+JCfvRb52tng/AVHY+jQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.972.0': + resolution: {integrity: sha512-2udiRijmjpN81Pvajje4TsjbXDZNP6K9bYUanBYH8hXa/tZG5qfGCySD+TyX0sgDxCQmEDMg3LaQdfjNHBDEgQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/token-providers@3.974.0': + resolution: {integrity: sha512-cBykL0LiccKIgNhGWvQRTPvsBLPZxnmJU3pYxG538jpFX8lQtrCy1L7mmIHNEdxIdIGEPgAEHF8/JQxgBToqUQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.972.0': + resolution: {integrity: sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/types@3.973.0': + resolution: {integrity: sha512-jYIdB7a7jhRTvyb378nsjyvJh1Si+zVduJ6urMNGpz8RjkmHZ+9vM2H07XaIB2Cfq0GhJRZYOfUCH8uqQhqBkQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-arn-parser@3.972.0': + resolution: {integrity: sha512-RM5Mmo/KJ593iMSrALlHEOcc9YOIyOsDmS5x2NLOMdEmzv1o00fcpAkCQ02IGu1eFneBFT7uX0Mpag0HI+Cz2g==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-arn-parser@3.972.1': + resolution: {integrity: sha512-XnNit6H9PPHhqUXW/usjX6JeJ6Pm8ZNqivTjmNjgWHeOfVpblUc/MTic02UmCNR0jJLPjQ3mBKiMen0tnkNQjQ==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-endpoints@3.972.0': + resolution: {integrity: sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-locate-window@3.965.3': + resolution: {integrity: sha512-FNUqAjlKAGA7GM05kywE99q8wiPHPZqrzhq3wXRga6PRD6A0kzT85Pb0AzYBVTBRpSrKyyr6M92Y6bnSBVp2BA==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/util-user-agent-browser@3.972.1': + resolution: {integrity: sha512-IgF55NFmJX8d9Wql9M0nEpk2eYbuD8G4781FN4/fFgwTXBn86DvlZJuRWDCMcMqZymnBVX7HW9r+3r9ylqfW0w==} + + '@aws-sdk/util-user-agent-node@3.972.1': + resolution: {integrity: sha512-oIs4JFcADzoZ0c915R83XvK2HltWupxNsXUIuZse2rgk7b97zTpkxaqXiH0h9ylh31qtgo/t8hp4tIqcsMrEbQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.972.0': + resolution: {integrity: sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==} + engines: {node: '>=20.0.0'} + + '@aws-sdk/xml-builder@3.972.1': + resolution: {integrity: sha512-6zZGlPOqn7Xb+25MAXGb1JhgvaC5HjZj6GzszuVrnEgbhvzBRFGKYemuHBV4bho+dtqeYKPgaZUv7/e80hIGNg==} + engines: {node: '>=20.0.0'} + + '@aws/lambda-invoke-store@0.2.3': + resolution: {integrity: sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==} + engines: {node: '>=18.0.0'} + '@babel/code-frame@7.23.5': resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} engines: {node: '>=6.9.0'} @@ -356,6 +551,162 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -642,6 +993,222 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@smithy/abort-controller@4.2.8': + resolution: {integrity: sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==} + engines: {node: '>=18.0.0'} + + '@smithy/chunked-blob-reader-native@4.2.1': + resolution: {integrity: sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==} + engines: {node: '>=18.0.0'} + + '@smithy/chunked-blob-reader@5.2.0': + resolution: {integrity: sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==} + engines: {node: '>=18.0.0'} + + '@smithy/config-resolver@4.4.6': + resolution: {integrity: sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.21.1': + resolution: {integrity: sha512-NUH8R4O6FkN8HKMojzbGg/5pNjsfTjlMmeFclyPfPaXXUrbr5TzhWgbf7t92wfrpCHRgpjyz7ffASIS3wX28aA==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.2.8': + resolution: {integrity: sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-codec@4.2.8': + resolution: {integrity: sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-browser@4.2.8': + resolution: {integrity: sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-config-resolver@4.3.8': + resolution: {integrity: sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-node@4.2.8': + resolution: {integrity: sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A==} + engines: {node: '>=18.0.0'} + + '@smithy/eventstream-serde-universal@4.2.8': + resolution: {integrity: sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.3.9': + resolution: {integrity: sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-blob-browser@4.2.9': + resolution: {integrity: sha512-m80d/iicI7DlBDxyQP6Th7BW/ejDGiF0bgI754+tiwK0lgMkcaIBgvwwVc7OFbY4eUzpGtnig52MhPAEJ7iNYg==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-node@4.2.8': + resolution: {integrity: sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-stream-node@4.2.8': + resolution: {integrity: sha512-v0FLTXgHrTeheYZFGhR+ehX5qUm4IQsjAiL9qehad2cyjMWcN2QG6/4mSwbSgEQzI7jwfoXj7z4fxZUx/Mhj2w==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.2.8': + resolution: {integrity: sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/is-array-buffer@4.2.0': + resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==} + engines: {node: '>=18.0.0'} + + '@smithy/md5-js@4.2.8': + resolution: {integrity: sha512-oGMaLj4tVZzLi3itBa9TCswgMBr7k9b+qKYowQ6x1rTyTuO1IU2YHdHUa+891OsOH+wCsH7aTPRsTJO3RMQmjQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-content-length@4.2.8': + resolution: {integrity: sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-endpoint@4.4.11': + resolution: {integrity: sha512-/WqsrycweGGfb9sSzME4CrsuayjJF6BueBmkKlcbeU5q18OhxRrvvKlmfw3tpDsK5ilx2XUJvoukwxHB0nHs/Q==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.4.27': + resolution: {integrity: sha512-xFUYCGRVsfgiN5EjsJJSzih9+yjStgMTCLANPlf0LVQkPDYCe0hz97qbdTZosFOiYlGBlHYityGRxrQ/hxhfVQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-serde@4.2.9': + resolution: {integrity: sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-stack@4.2.8': + resolution: {integrity: sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==} + engines: {node: '>=18.0.0'} + + '@smithy/node-config-provider@4.3.8': + resolution: {integrity: sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.4.8': + resolution: {integrity: sha512-q9u+MSbJVIJ1QmJ4+1u+cERXkrhuILCBDsJUBAW1MPE6sFonbCNaegFuwW9ll8kh5UdyY3jOkoOGlc7BesoLpg==} + engines: {node: '>=18.0.0'} + + '@smithy/property-provider@4.2.8': + resolution: {integrity: sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==} + engines: {node: '>=18.0.0'} + + '@smithy/protocol-http@5.3.8': + resolution: {integrity: sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-builder@4.2.8': + resolution: {integrity: sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-parser@4.2.8': + resolution: {integrity: sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==} + engines: {node: '>=18.0.0'} + + '@smithy/service-error-classification@4.2.8': + resolution: {integrity: sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==} + engines: {node: '>=18.0.0'} + + '@smithy/shared-ini-file-loader@4.4.3': + resolution: {integrity: sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.3.8': + resolution: {integrity: sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.10.12': + resolution: {integrity: sha512-VKO/HKoQ5OrSHW6AJUmEnUKeXI1/5LfCwO9cwyao7CmLvGnZeM1i36Lyful3LK1XU7HwTVieTqO1y2C/6t3qtA==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.12.0': + resolution: {integrity: sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.2.8': + resolution: {integrity: sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-base64@4.3.0': + resolution: {integrity: sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-browser@4.2.0': + resolution: {integrity: sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-node@4.2.1': + resolution: {integrity: sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@4.2.0': + resolution: {integrity: sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==} + engines: {node: '>=18.0.0'} + + '@smithy/util-config-provider@4.2.0': + resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-browser@4.3.26': + resolution: {integrity: sha512-vva0dzYUTgn7DdE0uaha10uEdAgmdLnNFowKFjpMm6p2R0XDk5FHPX3CBJLzWQkQXuEprsb0hGz9YwbicNWhjw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-node@4.2.29': + resolution: {integrity: sha512-c6D7IUBsZt/aNnTBHMTf+OVh+h/JcxUUgfTcIJaWRe6zhOum1X+pNKSZtZ+7fbOn5I99XVFtmrnXKv8yHHErTQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-endpoints@3.2.8': + resolution: {integrity: sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-hex-encoding@4.2.0': + resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-middleware@4.2.8': + resolution: {integrity: sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==} + engines: {node: '>=18.0.0'} + + '@smithy/util-retry@4.2.8': + resolution: {integrity: sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-stream@4.5.10': + resolution: {integrity: sha512-jbqemy51UFSZSp2y0ZmRfckmrzuKww95zT9BYMmuJ8v3altGcqjwoV1tzpOwuHaKrwQrCjIzOib499ymr2f98g==} + engines: {node: '>=18.0.0'} + + '@smithy/util-uri-escape@4.2.0': + resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@4.2.0': + resolution: {integrity: sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-waiter@4.2.8': + resolution: {integrity: sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg==} + engines: {node: '>=18.0.0'} + + '@smithy/uuid@1.1.0': + resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==} + engines: {node: '>=18.0.0'} + '@stylelint/postcss-css-in-js@0.37.3': resolution: {integrity: sha512-scLk3cSH1H9KggSniseb2KNAU5D9FWc3H7BxCSAIdtU9OWIyw0zkEZ9qEKHryRM+SExYXRKNb7tOOVNAsQ3iwg==} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. @@ -656,6 +1223,30 @@ packages: postcss: '>=7.0.0' postcss-syntax: '>=0.36.2' + '@supabase/auth-js@2.91.1': + resolution: {integrity: sha512-3gFGMPuif2BOuAHXLAGsoOlDa64PROct1v7G94pMnvUAhh75u6+vnx4MYz1wyoyDBN5lCkJPGQNg5+RIgqxnpA==} + engines: {node: '>=20.0.0'} + + '@supabase/functions-js@2.91.1': + resolution: {integrity: sha512-xKepd3HZ6K6rKibriehKggIegsoz+jjV67tikN51q/YQq3AlUAkjUMSnMrqs8t5LMlAi+a3dJU812acXanR0cw==} + engines: {node: '>=20.0.0'} + + '@supabase/postgrest-js@2.91.1': + resolution: {integrity: sha512-UKumTC6SGHd65G/5Gj0V58u+SkUyiH4zEJ8OP2eb06+Tqnges1E/3Tl7lyq2qbcMP8nEyH/0M7m2bYjrn++haw==} + engines: {node: '>=20.0.0'} + + '@supabase/realtime-js@2.91.1': + resolution: {integrity: sha512-Y4rifuvzekFgd2hUfiEvcMoh/JU3s1hmpWYS7tNGL2QHuFfWg8a4w/qg5qoSMVDvgGRz6G4L6yB1FaQRTplENQ==} + engines: {node: '>=20.0.0'} + + '@supabase/storage-js@2.91.1': + resolution: {integrity: sha512-hMJNT2tSleOrWwx4FmHTpihIA2PRDixAsWflECuQ4YDkeduBZGX5m2txnstMnteWW+H+mm+92WRRFLuidXqbfA==} + engines: {node: '>=20.0.0'} + + '@supabase/supabase-js@2.91.1': + resolution: {integrity: sha512-57Fb4s5nfLn5ed2a1rPtl+LI1Wbtms8MS4qcUa0w6luaStBlFhmSeD2TLBgJWdMIupWRF6iFTH4QTrO2+pG/ZQ==} + engines: {node: '>=20.0.0'} + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -797,6 +1388,9 @@ packages: '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + '@types/phoenix@1.6.7': + resolution: {integrity: sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==} + '@types/prop-types@15.7.11': resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==} @@ -833,6 +1427,9 @@ packages: '@types/unist@3.0.2': resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -1135,6 +1732,9 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + bowser@2.13.1: + resolution: {integrity: sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1517,6 +2117,10 @@ packages: domutils@1.7.0: resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + engines: {node: '>=12'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1607,6 +2211,11 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -1860,6 +2469,10 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-xml-parser@5.2.5: + resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==} + hasBin: true + fastest-levenshtein@1.0.16: resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} engines: {node: '>= 4.9.1'} @@ -1988,6 +2601,9 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + get-tsconfig@4.7.2: resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} @@ -2183,6 +2799,10 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} + iceberg-js@0.8.1: + resolution: {integrity: sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==} + engines: {node: '>=20.0.0'} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -3901,6 +4521,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strnum@2.1.2: + resolution: {integrity: sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==} + style-search@0.1.0: resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==} @@ -4074,6 +4697,14 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -4372,6 +5003,18 @@ packages: utf-8-validate: optional: true + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} @@ -4451,10 +5094,543 @@ snapshots: '@adobe/css-tools@4.3.2': {} - '@ampproject/remapping@2.2.1': + '@ampproject/remapping@2.2.1': + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.20 + + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.0 + tslib: 2.6.2 + + '@aws-crypto/crc32c@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.0 + tslib: 2.6.2 + + '@aws-crypto/sha1-browser@5.2.0': + dependencies: + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.0 + '@aws-sdk/util-locate-window': 3.965.3 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.0 + '@aws-sdk/util-locate-window': 3.965.3 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.973.0 + tslib: 2.6.2 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.6.2 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.973.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + + '@aws-sdk/client-s3@3.975.0': + dependencies: + '@aws-crypto/sha1-browser': 5.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.1 + '@aws-sdk/credential-provider-node': 3.972.1 + '@aws-sdk/middleware-bucket-endpoint': 3.972.1 + '@aws-sdk/middleware-expect-continue': 3.972.1 + '@aws-sdk/middleware-flexible-checksums': 3.972.1 + '@aws-sdk/middleware-host-header': 3.972.1 + '@aws-sdk/middleware-location-constraint': 3.972.1 + '@aws-sdk/middleware-logger': 3.972.1 + '@aws-sdk/middleware-recursion-detection': 3.972.1 + '@aws-sdk/middleware-sdk-s3': 3.972.2 + '@aws-sdk/middleware-ssec': 3.972.1 + '@aws-sdk/middleware-user-agent': 3.972.2 + '@aws-sdk/region-config-resolver': 3.972.1 + '@aws-sdk/signature-v4-multi-region': 3.972.0 + '@aws-sdk/types': 3.973.0 + '@aws-sdk/util-endpoints': 3.972.0 + '@aws-sdk/util-user-agent-browser': 3.972.1 + '@aws-sdk/util-user-agent-node': 3.972.1 + '@smithy/config-resolver': 4.4.6 + '@smithy/core': 3.21.1 + '@smithy/eventstream-serde-browser': 4.2.8 + '@smithy/eventstream-serde-config-resolver': 4.3.8 + '@smithy/eventstream-serde-node': 4.2.8 + '@smithy/fetch-http-handler': 5.3.9 + '@smithy/hash-blob-browser': 4.2.9 + '@smithy/hash-node': 4.2.8 + '@smithy/hash-stream-node': 4.2.8 + '@smithy/invalid-dependency': 4.2.8 + '@smithy/md5-js': 4.2.8 + '@smithy/middleware-content-length': 4.2.8 + '@smithy/middleware-endpoint': 4.4.11 + '@smithy/middleware-retry': 4.4.27 + '@smithy/middleware-serde': 4.2.9 + '@smithy/middleware-stack': 4.2.8 + '@smithy/node-config-provider': 4.3.8 + '@smithy/node-http-handler': 4.4.8 + '@smithy/protocol-http': 5.3.8 + '@smithy/smithy-client': 4.10.12 + '@smithy/types': 4.12.0 + '@smithy/url-parser': 4.2.8 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.26 + '@smithy/util-defaults-mode-node': 4.2.29 + '@smithy/util-endpoints': 3.2.8 + '@smithy/util-middleware': 4.2.8 + '@smithy/util-retry': 4.2.8 + '@smithy/util-stream': 4.5.10 + '@smithy/util-utf8': 4.2.0 + '@smithy/util-waiter': 4.2.8 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sso@3.974.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.1 + '@aws-sdk/middleware-host-header': 3.972.1 + '@aws-sdk/middleware-logger': 3.972.1 + '@aws-sdk/middleware-recursion-detection': 3.972.1 + '@aws-sdk/middleware-user-agent': 3.972.2 + '@aws-sdk/region-config-resolver': 3.972.1 + '@aws-sdk/types': 3.973.0 + '@aws-sdk/util-endpoints': 3.972.0 + '@aws-sdk/util-user-agent-browser': 3.972.1 + '@aws-sdk/util-user-agent-node': 3.972.1 + '@smithy/config-resolver': 4.4.6 + '@smithy/core': 3.21.1 + '@smithy/fetch-http-handler': 5.3.9 + '@smithy/hash-node': 4.2.8 + '@smithy/invalid-dependency': 4.2.8 + '@smithy/middleware-content-length': 4.2.8 + '@smithy/middleware-endpoint': 4.4.11 + '@smithy/middleware-retry': 4.4.27 + '@smithy/middleware-serde': 4.2.9 + '@smithy/middleware-stack': 4.2.8 + '@smithy/node-config-provider': 4.3.8 + '@smithy/node-http-handler': 4.4.8 + '@smithy/protocol-http': 5.3.8 + '@smithy/smithy-client': 4.10.12 + '@smithy/types': 4.12.0 + '@smithy/url-parser': 4.2.8 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.26 + '@smithy/util-defaults-mode-node': 4.2.29 + '@smithy/util-endpoints': 3.2.8 + '@smithy/util-middleware': 4.2.8 + '@smithy/util-retry': 4.2.8 + '@smithy/util-utf8': 4.2.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.972.0': + dependencies: + '@aws-sdk/types': 3.972.0 + '@aws-sdk/xml-builder': 3.972.0 + '@smithy/core': 3.21.1 + '@smithy/node-config-provider': 4.3.8 + '@smithy/property-provider': 4.2.8 + '@smithy/protocol-http': 5.3.8 + '@smithy/signature-v4': 5.3.8 + '@smithy/smithy-client': 4.10.12 + '@smithy/types': 4.12.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-middleware': 4.2.8 + '@smithy/util-utf8': 4.2.0 + tslib: 2.6.2 + + '@aws-sdk/core@3.973.1': + dependencies: + '@aws-sdk/types': 3.973.0 + '@aws-sdk/xml-builder': 3.972.1 + '@smithy/core': 3.21.1 + '@smithy/node-config-provider': 4.3.8 + '@smithy/property-provider': 4.2.8 + '@smithy/protocol-http': 5.3.8 + '@smithy/signature-v4': 5.3.8 + '@smithy/smithy-client': 4.10.12 + '@smithy/types': 4.12.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-middleware': 4.2.8 + '@smithy/util-utf8': 4.2.0 + tslib: 2.6.2 + + '@aws-sdk/crc64-nvme@3.972.0': + dependencies: + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@aws-sdk/credential-provider-env@3.972.1': + dependencies: + '@aws-sdk/core': 3.973.1 + '@aws-sdk/types': 3.973.0 + '@smithy/property-provider': 4.2.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@aws-sdk/credential-provider-http@3.972.2': + dependencies: + '@aws-sdk/core': 3.973.1 + '@aws-sdk/types': 3.973.0 + '@smithy/fetch-http-handler': 5.3.9 + '@smithy/node-http-handler': 4.4.8 + '@smithy/property-provider': 4.2.8 + '@smithy/protocol-http': 5.3.8 + '@smithy/smithy-client': 4.10.12 + '@smithy/types': 4.12.0 + '@smithy/util-stream': 4.5.10 + tslib: 2.6.2 + + '@aws-sdk/credential-provider-ini@3.972.1': + dependencies: + '@aws-sdk/core': 3.973.1 + '@aws-sdk/credential-provider-env': 3.972.1 + '@aws-sdk/credential-provider-http': 3.972.2 + '@aws-sdk/credential-provider-login': 3.972.1 + '@aws-sdk/credential-provider-process': 3.972.1 + '@aws-sdk/credential-provider-sso': 3.972.1 + '@aws-sdk/credential-provider-web-identity': 3.972.1 + '@aws-sdk/nested-clients': 3.974.0 + '@aws-sdk/types': 3.973.0 + '@smithy/credential-provider-imds': 4.2.8 + '@smithy/property-provider': 4.2.8 + '@smithy/shared-ini-file-loader': 4.4.3 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-login@3.972.1': + dependencies: + '@aws-sdk/core': 3.973.1 + '@aws-sdk/nested-clients': 3.974.0 + '@aws-sdk/types': 3.973.0 + '@smithy/property-provider': 4.2.8 + '@smithy/protocol-http': 5.3.8 + '@smithy/shared-ini-file-loader': 4.4.3 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.972.1': + dependencies: + '@aws-sdk/credential-provider-env': 3.972.1 + '@aws-sdk/credential-provider-http': 3.972.2 + '@aws-sdk/credential-provider-ini': 3.972.1 + '@aws-sdk/credential-provider-process': 3.972.1 + '@aws-sdk/credential-provider-sso': 3.972.1 + '@aws-sdk/credential-provider-web-identity': 3.972.1 + '@aws-sdk/types': 3.973.0 + '@smithy/credential-provider-imds': 4.2.8 + '@smithy/property-provider': 4.2.8 + '@smithy/shared-ini-file-loader': 4.4.3 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.972.1': + dependencies: + '@aws-sdk/core': 3.973.1 + '@aws-sdk/types': 3.973.0 + '@smithy/property-provider': 4.2.8 + '@smithy/shared-ini-file-loader': 4.4.3 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@aws-sdk/credential-provider-sso@3.972.1': + dependencies: + '@aws-sdk/client-sso': 3.974.0 + '@aws-sdk/core': 3.973.1 + '@aws-sdk/token-providers': 3.974.0 + '@aws-sdk/types': 3.973.0 + '@smithy/property-provider': 4.2.8 + '@smithy/shared-ini-file-loader': 4.4.3 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.972.1': + dependencies: + '@aws-sdk/core': 3.973.1 + '@aws-sdk/nested-clients': 3.974.0 + '@aws-sdk/types': 3.973.0 + '@smithy/property-provider': 4.2.8 + '@smithy/shared-ini-file-loader': 4.4.3 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/middleware-bucket-endpoint@3.972.1': + dependencies: + '@aws-sdk/types': 3.973.0 + '@aws-sdk/util-arn-parser': 3.972.1 + '@smithy/node-config-provider': 4.3.8 + '@smithy/protocol-http': 5.3.8 + '@smithy/types': 4.12.0 + '@smithy/util-config-provider': 4.2.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-expect-continue@3.972.1': + dependencies: + '@aws-sdk/types': 3.973.0 + '@smithy/protocol-http': 5.3.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-flexible-checksums@3.972.1': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.973.1 + '@aws-sdk/crc64-nvme': 3.972.0 + '@aws-sdk/types': 3.973.0 + '@smithy/is-array-buffer': 4.2.0 + '@smithy/node-config-provider': 4.3.8 + '@smithy/protocol-http': 5.3.8 + '@smithy/types': 4.12.0 + '@smithy/util-middleware': 4.2.8 + '@smithy/util-stream': 4.5.10 + '@smithy/util-utf8': 4.2.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-host-header@3.972.1': + dependencies: + '@aws-sdk/types': 3.973.0 + '@smithy/protocol-http': 5.3.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-location-constraint@3.972.1': + dependencies: + '@aws-sdk/types': 3.973.0 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-logger@3.972.1': + dependencies: + '@aws-sdk/types': 3.973.0 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-recursion-detection@3.972.1': + dependencies: + '@aws-sdk/types': 3.973.0 + '@aws/lambda-invoke-store': 0.2.3 + '@smithy/protocol-http': 5.3.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-sdk-s3@3.972.0': + dependencies: + '@aws-sdk/core': 3.972.0 + '@aws-sdk/types': 3.972.0 + '@aws-sdk/util-arn-parser': 3.972.0 + '@smithy/core': 3.21.1 + '@smithy/node-config-provider': 4.3.8 + '@smithy/protocol-http': 5.3.8 + '@smithy/signature-v4': 5.3.8 + '@smithy/smithy-client': 4.10.12 + '@smithy/types': 4.12.0 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-middleware': 4.2.8 + '@smithy/util-stream': 4.5.10 + '@smithy/util-utf8': 4.2.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-sdk-s3@3.972.2': + dependencies: + '@aws-sdk/core': 3.973.1 + '@aws-sdk/types': 3.973.0 + '@aws-sdk/util-arn-parser': 3.972.1 + '@smithy/core': 3.21.1 + '@smithy/node-config-provider': 4.3.8 + '@smithy/protocol-http': 5.3.8 + '@smithy/signature-v4': 5.3.8 + '@smithy/smithy-client': 4.10.12 + '@smithy/types': 4.12.0 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-middleware': 4.2.8 + '@smithy/util-stream': 4.5.10 + '@smithy/util-utf8': 4.2.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-ssec@3.972.1': + dependencies: + '@aws-sdk/types': 3.973.0 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@aws-sdk/middleware-user-agent@3.972.2': + dependencies: + '@aws-sdk/core': 3.973.1 + '@aws-sdk/types': 3.973.0 + '@aws-sdk/util-endpoints': 3.972.0 + '@smithy/core': 3.21.1 + '@smithy/protocol-http': 5.3.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@aws-sdk/nested-clients@3.974.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.973.1 + '@aws-sdk/middleware-host-header': 3.972.1 + '@aws-sdk/middleware-logger': 3.972.1 + '@aws-sdk/middleware-recursion-detection': 3.972.1 + '@aws-sdk/middleware-user-agent': 3.972.2 + '@aws-sdk/region-config-resolver': 3.972.1 + '@aws-sdk/types': 3.973.0 + '@aws-sdk/util-endpoints': 3.972.0 + '@aws-sdk/util-user-agent-browser': 3.972.1 + '@aws-sdk/util-user-agent-node': 3.972.1 + '@smithy/config-resolver': 4.4.6 + '@smithy/core': 3.21.1 + '@smithy/fetch-http-handler': 5.3.9 + '@smithy/hash-node': 4.2.8 + '@smithy/invalid-dependency': 4.2.8 + '@smithy/middleware-content-length': 4.2.8 + '@smithy/middleware-endpoint': 4.4.11 + '@smithy/middleware-retry': 4.4.27 + '@smithy/middleware-serde': 4.2.9 + '@smithy/middleware-stack': 4.2.8 + '@smithy/node-config-provider': 4.3.8 + '@smithy/node-http-handler': 4.4.8 + '@smithy/protocol-http': 5.3.8 + '@smithy/smithy-client': 4.10.12 + '@smithy/types': 4.12.0 + '@smithy/url-parser': 4.2.8 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.26 + '@smithy/util-defaults-mode-node': 4.2.29 + '@smithy/util-endpoints': 3.2.8 + '@smithy/util-middleware': 4.2.8 + '@smithy/util-retry': 4.2.8 + '@smithy/util-utf8': 4.2.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/region-config-resolver@3.972.1': + dependencies: + '@aws-sdk/types': 3.973.0 + '@smithy/config-resolver': 4.4.6 + '@smithy/node-config-provider': 4.3.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@aws-sdk/signature-v4-multi-region@3.972.0': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.972.0 + '@aws-sdk/types': 3.972.0 + '@smithy/protocol-http': 5.3.8 + '@smithy/signature-v4': 5.3.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@aws-sdk/token-providers@3.974.0': + dependencies: + '@aws-sdk/core': 3.973.1 + '@aws-sdk/nested-clients': 3.974.0 + '@aws-sdk/types': 3.973.0 + '@smithy/property-provider': 4.2.8 + '@smithy/shared-ini-file-loader': 4.4.3 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.972.0': + dependencies: + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@aws-sdk/types@3.973.0': + dependencies: + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@aws-sdk/util-arn-parser@3.972.0': + dependencies: + tslib: 2.6.2 + + '@aws-sdk/util-arn-parser@3.972.1': + dependencies: + tslib: 2.6.2 + + '@aws-sdk/util-endpoints@3.972.0': + dependencies: + '@aws-sdk/types': 3.972.0 + '@smithy/types': 4.12.0 + '@smithy/url-parser': 4.2.8 + '@smithy/util-endpoints': 3.2.8 + tslib: 2.6.2 + + '@aws-sdk/util-locate-window@3.965.3': + dependencies: + tslib: 2.6.2 + + '@aws-sdk/util-user-agent-browser@3.972.1': + dependencies: + '@aws-sdk/types': 3.973.0 + '@smithy/types': 4.12.0 + bowser: 2.13.1 + tslib: 2.6.2 + + '@aws-sdk/util-user-agent-node@3.972.1': + dependencies: + '@aws-sdk/middleware-user-agent': 3.972.2 + '@aws-sdk/types': 3.973.0 + '@smithy/node-config-provider': 4.3.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@aws-sdk/xml-builder@3.972.0': + dependencies: + '@smithy/types': 4.12.0 + fast-xml-parser: 5.2.5 + tslib: 2.6.2 + + '@aws-sdk/xml-builder@3.972.1': dependencies: - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.20 + '@smithy/types': 4.12.0 + fast-xml-parser: 5.2.5 + tslib: 2.6.2 + + '@aws/lambda-invoke-store@0.2.3': {} '@babel/code-frame@7.23.5': dependencies: @@ -4663,6 +5839,84 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm@0.27.2': + optional: true + + '@esbuild/linux-ia32@0.27.2': + optional: true + + '@esbuild/linux-loong64@0.27.2': + optional: true + + '@esbuild/linux-mips64el@0.27.2': + optional: true + + '@esbuild/linux-ppc64@0.27.2': + optional: true + + '@esbuild/linux-riscv64@0.27.2': + optional: true + + '@esbuild/linux-s390x@0.27.2': + optional: true + + '@esbuild/linux-x64@0.27.2': + optional: true + + '@esbuild/netbsd-arm64@0.27.2': + optional: true + + '@esbuild/netbsd-x64@0.27.2': + optional: true + + '@esbuild/openbsd-arm64@0.27.2': + optional: true + + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true + '@eslint-community/eslint-utils@4.4.0(eslint@9.25.1)': dependencies: eslint: 9.25.1 @@ -5039,6 +6293,344 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.0 + '@smithy/abort-controller@4.2.8': + dependencies: + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/chunked-blob-reader-native@4.2.1': + dependencies: + '@smithy/util-base64': 4.3.0 + tslib: 2.6.2 + + '@smithy/chunked-blob-reader@5.2.0': + dependencies: + tslib: 2.6.2 + + '@smithy/config-resolver@4.4.6': + dependencies: + '@smithy/node-config-provider': 4.3.8 + '@smithy/types': 4.12.0 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-endpoints': 3.2.8 + '@smithy/util-middleware': 4.2.8 + tslib: 2.6.2 + + '@smithy/core@3.21.1': + dependencies: + '@smithy/middleware-serde': 4.2.9 + '@smithy/protocol-http': 5.3.8 + '@smithy/types': 4.12.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-middleware': 4.2.8 + '@smithy/util-stream': 4.5.10 + '@smithy/util-utf8': 4.2.0 + '@smithy/uuid': 1.1.0 + tslib: 2.6.2 + + '@smithy/credential-provider-imds@4.2.8': + dependencies: + '@smithy/node-config-provider': 4.3.8 + '@smithy/property-provider': 4.2.8 + '@smithy/types': 4.12.0 + '@smithy/url-parser': 4.2.8 + tslib: 2.6.2 + + '@smithy/eventstream-codec@4.2.8': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 4.12.0 + '@smithy/util-hex-encoding': 4.2.0 + tslib: 2.6.2 + + '@smithy/eventstream-serde-browser@4.2.8': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/eventstream-serde-config-resolver@4.3.8': + dependencies: + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/eventstream-serde-node@4.2.8': + dependencies: + '@smithy/eventstream-serde-universal': 4.2.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/eventstream-serde-universal@4.2.8': + dependencies: + '@smithy/eventstream-codec': 4.2.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/fetch-http-handler@5.3.9': + dependencies: + '@smithy/protocol-http': 5.3.8 + '@smithy/querystring-builder': 4.2.8 + '@smithy/types': 4.12.0 + '@smithy/util-base64': 4.3.0 + tslib: 2.6.2 + + '@smithy/hash-blob-browser@4.2.9': + dependencies: + '@smithy/chunked-blob-reader': 5.2.0 + '@smithy/chunked-blob-reader-native': 4.2.1 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/hash-node@4.2.8': + dependencies: + '@smithy/types': 4.12.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.6.2 + + '@smithy/hash-stream-node@4.2.8': + dependencies: + '@smithy/types': 4.12.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.6.2 + + '@smithy/invalid-dependency@4.2.8': + dependencies: + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.6.2 + + '@smithy/is-array-buffer@4.2.0': + dependencies: + tslib: 2.6.2 + + '@smithy/md5-js@4.2.8': + dependencies: + '@smithy/types': 4.12.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.6.2 + + '@smithy/middleware-content-length@4.2.8': + dependencies: + '@smithy/protocol-http': 5.3.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/middleware-endpoint@4.4.11': + dependencies: + '@smithy/core': 3.21.1 + '@smithy/middleware-serde': 4.2.9 + '@smithy/node-config-provider': 4.3.8 + '@smithy/shared-ini-file-loader': 4.4.3 + '@smithy/types': 4.12.0 + '@smithy/url-parser': 4.2.8 + '@smithy/util-middleware': 4.2.8 + tslib: 2.6.2 + + '@smithy/middleware-retry@4.4.27': + dependencies: + '@smithy/node-config-provider': 4.3.8 + '@smithy/protocol-http': 5.3.8 + '@smithy/service-error-classification': 4.2.8 + '@smithy/smithy-client': 4.10.12 + '@smithy/types': 4.12.0 + '@smithy/util-middleware': 4.2.8 + '@smithy/util-retry': 4.2.8 + '@smithy/uuid': 1.1.0 + tslib: 2.6.2 + + '@smithy/middleware-serde@4.2.9': + dependencies: + '@smithy/protocol-http': 5.3.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/middleware-stack@4.2.8': + dependencies: + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/node-config-provider@4.3.8': + dependencies: + '@smithy/property-provider': 4.2.8 + '@smithy/shared-ini-file-loader': 4.4.3 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/node-http-handler@4.4.8': + dependencies: + '@smithy/abort-controller': 4.2.8 + '@smithy/protocol-http': 5.3.8 + '@smithy/querystring-builder': 4.2.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/property-provider@4.2.8': + dependencies: + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/protocol-http@5.3.8': + dependencies: + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/querystring-builder@4.2.8': + dependencies: + '@smithy/types': 4.12.0 + '@smithy/util-uri-escape': 4.2.0 + tslib: 2.6.2 + + '@smithy/querystring-parser@4.2.8': + dependencies: + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/service-error-classification@4.2.8': + dependencies: + '@smithy/types': 4.12.0 + + '@smithy/shared-ini-file-loader@4.4.3': + dependencies: + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/signature-v4@5.3.8': + dependencies: + '@smithy/is-array-buffer': 4.2.0 + '@smithy/protocol-http': 5.3.8 + '@smithy/types': 4.12.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-middleware': 4.2.8 + '@smithy/util-uri-escape': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.6.2 + + '@smithy/smithy-client@4.10.12': + dependencies: + '@smithy/core': 3.21.1 + '@smithy/middleware-endpoint': 4.4.11 + '@smithy/middleware-stack': 4.2.8 + '@smithy/protocol-http': 5.3.8 + '@smithy/types': 4.12.0 + '@smithy/util-stream': 4.5.10 + tslib: 2.6.2 + + '@smithy/types@4.12.0': + dependencies: + tslib: 2.6.2 + + '@smithy/url-parser@4.2.8': + dependencies: + '@smithy/querystring-parser': 4.2.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/util-base64@4.3.0': + dependencies: + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.6.2 + + '@smithy/util-body-length-browser@4.2.0': + dependencies: + tslib: 2.6.2 + + '@smithy/util-body-length-node@4.2.1': + dependencies: + tslib: 2.6.2 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.6.2 + + '@smithy/util-buffer-from@4.2.0': + dependencies: + '@smithy/is-array-buffer': 4.2.0 + tslib: 2.6.2 + + '@smithy/util-config-provider@4.2.0': + dependencies: + tslib: 2.6.2 + + '@smithy/util-defaults-mode-browser@4.3.26': + dependencies: + '@smithy/property-provider': 4.2.8 + '@smithy/smithy-client': 4.10.12 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/util-defaults-mode-node@4.2.29': + dependencies: + '@smithy/config-resolver': 4.4.6 + '@smithy/credential-provider-imds': 4.2.8 + '@smithy/node-config-provider': 4.3.8 + '@smithy/property-provider': 4.2.8 + '@smithy/smithy-client': 4.10.12 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/util-endpoints@3.2.8': + dependencies: + '@smithy/node-config-provider': 4.3.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/util-hex-encoding@4.2.0': + dependencies: + tslib: 2.6.2 + + '@smithy/util-middleware@4.2.8': + dependencies: + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/util-retry@4.2.8': + dependencies: + '@smithy/service-error-classification': 4.2.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/util-stream@4.5.10': + dependencies: + '@smithy/fetch-http-handler': 5.3.9 + '@smithy/node-http-handler': 4.4.8 + '@smithy/types': 4.12.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.6.2 + + '@smithy/util-uri-escape@4.2.0': + dependencies: + tslib: 2.6.2 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.6.2 + + '@smithy/util-utf8@4.2.0': + dependencies: + '@smithy/util-buffer-from': 4.2.0 + tslib: 2.6.2 + + '@smithy/util-waiter@4.2.8': + dependencies: + '@smithy/abort-controller': 4.2.8 + '@smithy/types': 4.12.0 + tslib: 2.6.2 + + '@smithy/uuid@1.1.0': + dependencies: + tslib: 2.6.2 + '@stylelint/postcss-css-in-js@0.37.3(postcss-syntax@0.36.2)(postcss@7.0.39)': dependencies: '@babel/core': 7.23.7 @@ -5056,6 +6648,44 @@ snapshots: transitivePeerDependencies: - supports-color + '@supabase/auth-js@2.91.1': + dependencies: + tslib: 2.8.1 + + '@supabase/functions-js@2.91.1': + dependencies: + tslib: 2.8.1 + + '@supabase/postgrest-js@2.91.1': + dependencies: + tslib: 2.8.1 + + '@supabase/realtime-js@2.91.1': + dependencies: + '@types/phoenix': 1.6.7 + '@types/ws': 8.18.1 + tslib: 2.8.1 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@supabase/storage-js@2.91.1': + dependencies: + iceberg-js: 0.8.1 + tslib: 2.8.1 + + '@supabase/supabase-js@2.91.1': + dependencies: + '@supabase/auth-js': 2.91.1 + '@supabase/functions-js': 2.91.1 + '@supabase/postgrest-js': 2.91.1 + '@supabase/realtime-js': 2.91.1 + '@supabase/storage-js': 2.91.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.5': @@ -5209,6 +6839,8 @@ snapshots: '@types/parse-json@4.0.2': {} + '@types/phoenix@1.6.7': {} + '@types/prop-types@15.7.11': {} '@types/react-dom@18.2.8': @@ -5243,6 +6875,10 @@ snapshots: '@types/unist@3.0.2': {} + '@types/ws@8.18.1': + dependencies: + '@types/node': 22.15.2 + '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.32': @@ -5631,6 +7267,8 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + bowser@2.13.1: {} + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -6020,6 +7658,8 @@ snapshots: dom-serializer: 0.2.2 domelementtype: 1.3.1 + dotenv@17.2.3: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -6236,6 +7876,35 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.0.4 + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + escalade@3.1.1: {} escape-string-regexp@1.0.5: {} @@ -6570,6 +8239,10 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-xml-parser@5.2.5: + dependencies: + strnum: 2.1.2 + fastest-levenshtein@1.0.16: {} fastq@1.16.0: @@ -6712,6 +8385,10 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + get-tsconfig@4.7.2: dependencies: resolve-pkg-maps: 1.0.0 @@ -6933,6 +8610,8 @@ snapshots: human-signals@2.1.0: {} + iceberg-js@0.8.1: {} + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -9255,6 +10934,8 @@ snapshots: strip-json-comments@3.1.1: {} + strnum@2.1.2: {} + style-search@0.1.0: {} style-to-object@1.0.5: @@ -9470,6 +11151,15 @@ snapshots: tslib@2.6.2: {} + tslib@2.8.1: {} + + tsx@4.21.0: + dependencies: + esbuild: 0.27.2 + get-tsconfig: 4.13.0 + optionalDependencies: + fsevents: 2.3.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -9879,6 +11569,8 @@ snapshots: ws@8.16.0: {} + ws@8.19.0: {} + xml-name-validator@4.0.0: {} xml@1.0.1: {} diff --git a/scripts/migration/01-extract-datocms.ts b/scripts/migration/01-extract-datocms.ts new file mode 100644 index 0000000..0aa1f75 --- /dev/null +++ b/scripts/migration/01-extract-datocms.ts @@ -0,0 +1,152 @@ +/** + * Step 1: DatoCMS에서 모든 데이터 추출 + * + * 실행: pnpm tsx scripts/migration/01-extract-datocms.ts + */ + +import * as fs from "fs"; +import * as path from "path"; +import { config, validateConfig } from "./config"; + +interface DatoCMSPost { + id: number; + _createdAt: string; + markdown: string; + ispublic: boolean; + category: { + category: Record; + }; + metaField: { + title: string; + description: string; + image: { + alt: string; + url: string; + title?: string; + width?: number; + height?: number; + responsiveImage?: { + base64: string; + }; + }; + }; +} + +interface ExtractedData { + posts: DatoCMSPost[]; + extractedAt: string; +} + +const QUERY = ` + query allArticles { + allArticles(orderBy: _createdAt_DESC, first: "100") { + id + _createdAt + markdown(markdown: false) + ispublic + category + metaField { + title + description + image { + alt + url + title + width + height + responsiveImage { + base64 + } + } + } + } + } +`; + +async function fetchFromDatoCMS(): Promise { + console.log("Fetching data from DatoCMS..."); + + const response = await fetch(config.datoApiEndpoint, { + method: "POST", + headers: { + Authorization: `Bearer ${config.datoApiToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ query: QUERY }), + }); + + if (!response.ok) { + throw new Error(`DatoCMS API error: ${response.status} ${response.statusText}`); + } + + const result = await response.json(); + + if (result.errors) { + throw new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`); + } + + return result.data.allArticles; +} + +async function main() { + console.log("=== Step 1: Extract data from DatoCMS ===\n"); + + // Validate required config + validateConfig(["datoApiToken"]); + + // Ensure output directory exists + if (!fs.existsSync(config.outputDir)) { + fs.mkdirSync(config.outputDir, { recursive: true }); + } + + // Fetch data + const posts = await fetchFromDatoCMS(); + console.log(`Found ${posts.length} posts\n`); + + // Log summary + const publicPosts = posts.filter((p) => p.ispublic !== false); + const privatePosts = posts.filter((p) => p.ispublic === false); + console.log(`Public posts: ${publicPosts.length}`); + console.log(`Private posts: ${privatePosts.length}`); + + // Extract unique categories + const categories = new Set(); + posts.forEach((post) => { + const cat = post.category?.category; + if (cat) { + Object.entries(cat).forEach(([main, sub]) => { + categories.add(`${main}/${sub}`); + }); + } + }); + console.log(`\nUnique categories: ${categories.size}`); + categories.forEach((cat) => console.log(` - ${cat}`)); + + // Extract unique image URLs + const imageUrls = new Set(); + posts.forEach((post) => { + if (post.metaField?.image?.url) { + imageUrls.add(post.metaField.image.url); + } + }); + console.log(`\nUnique images: ${imageUrls.size}`); + + // Save extracted data + const extractedData: ExtractedData = { + posts, + extractedAt: new Date().toISOString(), + }; + + fs.writeFileSync( + config.extractedDataPath, + JSON.stringify(extractedData, null, 2), + ); + + console.log(`\nData saved to: ${config.extractedDataPath}`); + console.log("\n=== Step 1 Complete ==="); +} + +main().catch((error) => { + console.error("Error:", error); + process.exit(1); +}); diff --git a/scripts/migration/02-upload-images-s3.ts b/scripts/migration/02-upload-images-s3.ts new file mode 100644 index 0000000..8d13899 --- /dev/null +++ b/scripts/migration/02-upload-images-s3.ts @@ -0,0 +1,184 @@ +/** + * Step 2: 이미지를 S3에 업로드 + * + * 실행: pnpm tsx scripts/migration/02-upload-images-s3.ts + */ + +import * as fs from "fs"; +import * as path from "path"; +import { + S3Client, + PutObjectCommand, + HeadObjectCommand, +} from "@aws-sdk/client-s3"; +import { config, validateConfig } from "./config"; + +interface ImageMap { + [originalUrl: string]: { + s3Key: string; + uploadedAt: string; + contentType: string; + size: number; + }; +} + +interface ExtractedData { + posts: Array<{ + id: number; + metaField: { + image: { + url: string; + alt?: string; + title?: string; + }; + }; + }>; +} + +async function downloadImage(url: string): Promise { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to download ${url}: ${response.status}`); + } + const arrayBuffer = await response.arrayBuffer(); + return Buffer.from(arrayBuffer); +} + +function getContentType(url: string): string { + const ext = path.extname(new URL(url).pathname).toLowerCase(); + const contentTypes: Record = { + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".png": "image/png", + ".gif": "image/gif", + ".webp": "image/webp", + ".svg": "image/svg+xml", + }; + return contentTypes[ext] || "image/jpeg"; +} + +function generateS3Key(url: string, postId: number): string { + const ext = path.extname(new URL(url).pathname) || ".jpg"; + return `images/posts/${postId}/thumbnail${ext}`; +} + +async function main() { + console.log("=== Step 2: Upload images to S3 ===\n"); + + // Validate required config + validateConfig([ + "awsRegion", + "s3Bucket", + "awsAccessKeyId", + "awsSecretAccessKey", + ]); + + // Load extracted data + if (!fs.existsSync(config.extractedDataPath)) { + throw new Error( + `Extracted data not found. Run 01-extract-datocms.ts first.`, + ); + } + + const extractedData: ExtractedData = JSON.parse( + fs.readFileSync(config.extractedDataPath, "utf-8"), + ); + + // Initialize S3 client + const s3Client = new S3Client({ + region: config.awsRegion, + credentials: { + accessKeyId: config.awsAccessKeyId, + secretAccessKey: config.awsSecretAccessKey, + }, + }); + + // Load existing image map or create new one + let imageMap: ImageMap = {}; + if (fs.existsSync(config.imageMapPath)) { + imageMap = JSON.parse(fs.readFileSync(config.imageMapPath, "utf-8")); + console.log(`Loaded existing image map with ${Object.keys(imageMap).length} entries\n`); + } + + // Collect unique images to upload + const imagesToUpload: Array<{ url: string; postId: number }> = []; + extractedData.posts.forEach((post) => { + const imageUrl = post.metaField?.image?.url; + if (imageUrl && !imageMap[imageUrl]) { + imagesToUpload.push({ url: imageUrl, postId: post.id }); + } + }); + + console.log(`Images to upload: ${imagesToUpload.length}\n`); + + // Upload images + let successCount = 0; + let errorCount = 0; + + for (const { url, postId } of imagesToUpload) { + try { + console.log(`Uploading: ${url}`); + + // Download image + const imageBuffer = await downloadImage(url); + const contentType = getContentType(url); + const s3Key = generateS3Key(url, postId); + + // Check if already exists in S3 + try { + await s3Client.send( + new HeadObjectCommand({ + Bucket: config.s3Bucket, + Key: s3Key, + }), + ); + console.log(` Already exists in S3: ${s3Key}`); + } catch { + // Upload to S3 + await s3Client.send( + new PutObjectCommand({ + Bucket: config.s3Bucket, + Key: s3Key, + Body: imageBuffer, + ContentType: contentType, + CacheControl: "public, max-age=31536000", // 1 year cache + }), + ); + console.log(` Uploaded to: ${s3Key}`); + } + + // Update image map + imageMap[url] = { + s3Key, + uploadedAt: new Date().toISOString(), + contentType, + size: imageBuffer.length, + }; + + successCount++; + } catch (error) { + console.error(` Error: ${error}`); + errorCount++; + } + + // Save progress periodically + if ((successCount + errorCount) % 10 === 0) { + fs.writeFileSync(config.imageMapPath, JSON.stringify(imageMap, null, 2)); + } + } + + // Save final image map + fs.writeFileSync(config.imageMapPath, JSON.stringify(imageMap, null, 2)); + + console.log(`\n=== Upload Summary ===`); + console.log(`Successful: ${successCount}`); + console.log(`Errors: ${errorCount}`); + console.log(`Total in map: ${Object.keys(imageMap).length}`); + console.log(`\nImage map saved to: ${config.imageMapPath}`); + console.log("\n=== Step 2 Complete ==="); +} + +main().catch((error) => { + console.error("Error:", error); + process.exit(1); +}); diff --git a/scripts/migration/03-generate-blur.ts b/scripts/migration/03-generate-blur.ts new file mode 100644 index 0000000..1e3704d --- /dev/null +++ b/scripts/migration/03-generate-blur.ts @@ -0,0 +1,142 @@ +/** + * Step 3: Blur placeholder 데이터 생성 + * + * DatoCMS에서 이미 base64 blur 데이터를 제공하므로 그것을 사용하거나, + * 새로 생성해야 하는 경우 plaiceholder 라이브러리 사용 + * + * 실행: pnpm tsx scripts/migration/03-generate-blur.ts + */ + +import * as fs from "fs"; +import { config, validateConfig } from "./config"; + +interface ExtractedData { + posts: Array<{ + id: number; + metaField: { + image: { + url: string; + responsiveImage?: { + base64: string; + }; + }; + }; + }>; +} + +interface ImageMap { + [originalUrl: string]: { + s3Key: string; + }; +} + +interface BlurMap { + [s3Key: string]: { + base64: string; + generatedAt: string; + source: "datocms" | "generated"; + }; +} + +async function generateBlurPlaceholder(imageUrl: string): Promise { + // 간단한 회색 placeholder 생성 (실제로는 plaiceholder 사용 권장) + // 이 함수는 DatoCMS base64가 없는 경우의 폴백 + console.log(` Generating placeholder for: ${imageUrl}`); + + // 간단한 1x1 회색 픽셀 placeholder + return "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMCwsLCwsKCw4QDQsNDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAD8AVN//2Q=="; +} + +async function main() { + console.log("=== Step 3: Generate blur placeholders ===\n"); + + // Load extracted data + if (!fs.existsSync(config.extractedDataPath)) { + throw new Error( + `Extracted data not found. Run 01-extract-datocms.ts first.`, + ); + } + + if (!fs.existsSync(config.imageMapPath)) { + throw new Error(`Image map not found. Run 02-upload-images-s3.ts first.`); + } + + const extractedData: ExtractedData = JSON.parse( + fs.readFileSync(config.extractedDataPath, "utf-8"), + ); + + const imageMap: ImageMap = JSON.parse( + fs.readFileSync(config.imageMapPath, "utf-8"), + ); + + // Load existing blur map or create new one + let blurMap: BlurMap = {}; + if (fs.existsSync(config.blurMapPath)) { + blurMap = JSON.parse(fs.readFileSync(config.blurMapPath, "utf-8")); + console.log( + `Loaded existing blur map with ${Object.keys(blurMap).length} entries\n`, + ); + } + + let datocmsCount = 0; + let generatedCount = 0; + let skippedCount = 0; + + for (const post of extractedData.posts) { + const imageUrl = post.metaField?.image?.url; + if (!imageUrl) continue; + + const imageInfo = imageMap[imageUrl]; + if (!imageInfo) { + console.log(`Skipping (not in image map): ${imageUrl}`); + skippedCount++; + continue; + } + + const s3Key = imageInfo.s3Key; + + // Skip if already in blur map + if (blurMap[s3Key]) { + skippedCount++; + continue; + } + + // Use DatoCMS base64 if available + const datocmsBase64 = post.metaField?.image?.responsiveImage?.base64; + + if (datocmsBase64) { + blurMap[s3Key] = { + base64: datocmsBase64, + generatedAt: new Date().toISOString(), + source: "datocms", + }; + console.log(`Using DatoCMS base64 for: ${s3Key}`); + datocmsCount++; + } else { + // Generate new placeholder + const base64 = await generateBlurPlaceholder(imageUrl); + blurMap[s3Key] = { + base64, + generatedAt: new Date().toISOString(), + source: "generated", + }; + generatedCount++; + } + } + + // Save blur map + fs.writeFileSync(config.blurMapPath, JSON.stringify(blurMap, null, 2)); + + console.log(`\n=== Blur Placeholder Summary ===`); + console.log(`From DatoCMS: ${datocmsCount}`); + console.log(`Generated: ${generatedCount}`); + console.log(`Skipped (already exists): ${skippedCount}`); + console.log(`Total in map: ${Object.keys(blurMap).length}`); + console.log(`\nBlur map saved to: ${config.blurMapPath}`); + console.log("\n=== Step 3 Complete ==="); +} + +main().catch((error) => { + console.error("Error:", error); + process.exit(1); +}); diff --git a/scripts/migration/04-insert-supabase.ts b/scripts/migration/04-insert-supabase.ts new file mode 100644 index 0000000..2ca148e --- /dev/null +++ b/scripts/migration/04-insert-supabase.ts @@ -0,0 +1,271 @@ +/** + * Step 4: Supabase에 데이터 삽입 + * + * 실행: pnpm tsx scripts/migration/04-insert-supabase.ts + */ + +import * as fs from "fs"; +import { createClient } from "@supabase/supabase-js"; +import { config, validateConfig } from "./config"; + +interface ExtractedData { + posts: Array<{ + id: string; + _createdAt: string; + markdown: string; + ispublic: boolean; + category: { + category: Record; + }; + metaField: { + title: string; + description: string; + image: { + url: string; + alt?: string; + title?: string; + width?: number; + height?: number; + }; + }; + }>; +} + +interface ImageMap { + [originalUrl: string]: { + s3Key: string; + contentType: string; + size: number; + }; +} + +interface BlurMap { + [s3Key: string]: { + base64: string; + }; +} + +async function main() { + console.log("=== Step 4: Insert data into Supabase ===\n"); + + // Validate required config + validateConfig(["supabaseUrl", "supabaseServiceKey"]); + + // Load all data + if (!fs.existsSync(config.extractedDataPath)) { + throw new Error( + `Extracted data not found. Run 01-extract-datocms.ts first.`, + ); + } + + if (!fs.existsSync(config.imageMapPath)) { + throw new Error(`Image map not found. Run 02-upload-images-s3.ts first.`); + } + + if (!fs.existsSync(config.blurMapPath)) { + throw new Error(`Blur map not found. Run 03-generate-blur.ts first.`); + } + + const extractedData: ExtractedData = JSON.parse( + fs.readFileSync(config.extractedDataPath, "utf-8"), + ); + + const imageMap: ImageMap = JSON.parse( + fs.readFileSync(config.imageMapPath, "utf-8"), + ); + + const blurMap: BlurMap = JSON.parse( + fs.readFileSync(config.blurMapPath, "utf-8"), + ); + + // Initialize Supabase client with service role key + const supabase = createClient(config.supabaseUrl, config.supabaseServiceKey); + + // Collect unique categories + const categoryMap = new Map(); // "main/sub" -> category_id + + console.log("Step 4.1: Inserting categories...\n"); + + for (const post of extractedData.posts) { + const cat = post.category?.category; + if (!cat) continue; + + for (const [mainCategory, subCategory] of Object.entries(cat)) { + const key = `${mainCategory}/${subCategory}`; + if (categoryMap.has(key)) continue; + + // Check if category exists + const { data: existing } = await supabase + .from("categories") + .select("id") + .eq("main_category", mainCategory) + .eq("sub_category", subCategory) + .single(); + + if (existing) { + categoryMap.set(key, existing.id); + console.log(`Category exists: ${key}`); + } else { + // Insert new category + const { data: inserted, error } = await supabase + .from("categories") + .insert({ + main_category: mainCategory, + sub_category: subCategory, + }) + .select("id") + .single(); + + if (error) { + console.error(`Error inserting category ${key}:`, error); + continue; + } + + categoryMap.set(key, inserted.id); + console.log(`Inserted category: ${key}`); + } + } + } + + console.log(`\nTotal categories: ${categoryMap.size}\n`); + + console.log("Step 4.2: Inserting images...\n"); + + const imageIdMap = new Map(); // s3Key -> image_id + + for (const [originalUrl, imageInfo] of Object.entries(imageMap)) { + const { s3Key } = imageInfo; + + // Find post that uses this image + const post = extractedData.posts.find( + (p) => p.metaField?.image?.url === originalUrl, + ); + + // Check if image exists + const { data: existing } = await supabase + .from("images") + .select("id") + .eq("s3_key", s3Key) + .single(); + + if (existing) { + imageIdMap.set(s3Key, existing.id); + console.log(`Image exists: ${s3Key}`); + continue; + } + + // Get blur data + const blurData = blurMap[s3Key]; + + // Insert new image + const { data: inserted, error } = await supabase + .from("images") + .insert({ + s3_key: s3Key, + original_url: originalUrl, + alt: post?.metaField?.image?.alt || null, + title: post?.metaField?.image?.title || null, + width: post?.metaField?.image?.width || null, + height: post?.metaField?.image?.height || null, + blur_data_url: blurData?.base64 || null, + }) + .select("id") + .single(); + + if (error) { + console.error(`Error inserting image ${s3Key}:`, error); + continue; + } + + imageIdMap.set(s3Key, inserted.id); + console.log(`Inserted image: ${s3Key}`); + } + + console.log(`\nTotal images: ${imageIdMap.size}\n`); + + console.log("Step 4.3: Inserting posts...\n"); + + let successCount = 0; + let errorCount = 0; + let skippedCount = 0; + + for (const post of extractedData.posts) { + // DatoCMS ID를 문자열로 저장 + const datocmsId = String(post.id); + + // Check if post already exists by datocms_id + const { data: existing } = await supabase + .from("posts") + .select("id") + .eq("datocms_id", datocmsId) + .single(); + + if (existing) { + console.log(`Post exists (datocms_id: ${datocmsId}): ${post.metaField?.title}`); + skippedCount++; + continue; + } + + // Get category ID + const cat = post.category?.category; + let categoryId: string | null = null; + + if (cat) { + const [mainCategory, subCategory] = Object.entries(cat)[0]; + const key = `${mainCategory}/${subCategory}`; + categoryId = categoryMap.get(key) || null; + } + + if (!categoryId) { + console.error(`No category found for post ${post.id}`); + errorCount++; + continue; + } + + // Get thumbnail ID + let thumbnailId: string | null = null; + const imageUrl = post.metaField?.image?.url; + + if (imageUrl) { + const imageInfo = imageMap[imageUrl]; + if (imageInfo) { + thumbnailId = imageIdMap.get(imageInfo.s3Key) || null; + } + } + + // Insert post + const { error } = await supabase.from("posts").insert({ + datocms_id: datocmsId, + title: post.metaField?.title || "Untitled", + description: post.metaField?.description || null, + markdown: post.markdown || "", + thumbnail_id: thumbnailId, + category_id: categoryId, + is_public: post.ispublic !== false, + created_at: post._createdAt, + updated_at: post._createdAt, + }); + + if (error) { + console.error(`Error inserting post ${post.id}:`, error); + errorCount++; + continue; + } + + console.log(`Inserted post: ${post.metaField?.title}`); + successCount++; + } + + console.log(`\n=== Insert Summary ===`); + console.log(`Categories: ${categoryMap.size}`); + console.log(`Images: ${imageIdMap.size}`); + console.log(`Posts - Successful: ${successCount}`); + console.log(`Posts - Skipped (already exists): ${skippedCount}`); + console.log(`Posts - Errors: ${errorCount}`); + console.log("\n=== Step 4 Complete ==="); +} + +main().catch((error) => { + console.error("Error:", error); + process.exit(1); +}); diff --git a/scripts/migration/05-verify.ts b/scripts/migration/05-verify.ts new file mode 100644 index 0000000..6228e7e --- /dev/null +++ b/scripts/migration/05-verify.ts @@ -0,0 +1,201 @@ +/** + * Step 5: 마이그레이션 검증 + * + * 실행: pnpm tsx scripts/migration/05-verify.ts + */ + +import * as fs from "fs"; +import { createClient } from "@supabase/supabase-js"; +import { config, validateConfig } from "./config"; + +interface ExtractedData { + posts: Array<{ + id: number; + _createdAt: string; + metaField: { + title: string; + }; + }>; +} + +async function main() { + console.log("=== Step 5: Verify Migration ===\n"); + + // Validate required config + validateConfig(["supabaseUrl", "supabaseServiceKey", "cloudfrontDomain"]); + + // Load extracted data for comparison + if (!fs.existsSync(config.extractedDataPath)) { + throw new Error( + `Extracted data not found. Run 01-extract-datocms.ts first.`, + ); + } + + const extractedData: ExtractedData = JSON.parse( + fs.readFileSync(config.extractedDataPath, "utf-8"), + ); + + // Initialize Supabase client + const supabase = createClient(config.supabaseUrl, config.supabaseServiceKey); + + console.log("1. Checking record counts...\n"); + + // Count posts + const { count: postCount, error: postError } = await supabase + .from("posts") + .select("*", { count: "exact", head: true }); + + if (postError) { + console.error("Error counting posts:", postError); + } else { + console.log(`Posts in DatoCMS: ${extractedData.posts.length}`); + console.log(`Posts in Supabase: ${postCount}`); + console.log( + postCount === extractedData.posts.length + ? "✓ Post count matches" + : "✗ Post count mismatch", + ); + } + + // Count categories + const { count: categoryCount } = await supabase + .from("categories") + .select("*", { count: "exact", head: true }); + + console.log(`\nCategories in Supabase: ${categoryCount}`); + + // Count images + const { count: imageCount } = await supabase + .from("images") + .select("*", { count: "exact", head: true }); + + console.log(`Images in Supabase: ${imageCount}`); + + console.log("\n2. Checking image accessibility...\n"); + + // Sample a few images to check CloudFront accessibility + const { data: sampleImages } = await supabase + .from("images") + .select("s3_key") + .limit(5); + + let accessibleCount = 0; + let inaccessibleCount = 0; + + if (sampleImages) { + for (const image of sampleImages) { + const url = `https://${config.cloudfrontDomain}/${image.s3_key}`; + + try { + const response = await fetch(url, { method: "HEAD" }); + if (response.ok) { + console.log(`✓ Accessible: ${image.s3_key}`); + accessibleCount++; + } else { + console.log(`✗ Not accessible (${response.status}): ${image.s3_key}`); + inaccessibleCount++; + } + } catch (error) { + console.log(`✗ Error checking: ${image.s3_key}`); + inaccessibleCount++; + } + } + } + + console.log( + `\nImage accessibility: ${accessibleCount}/${sampleImages?.length || 0} accessible`, + ); + + console.log("\n3. Checking post data integrity...\n"); + + // Verify a few posts have all required fields + const { data: samplePosts } = await supabase + .from("posts") + .select( + ` + id, + legacy_id, + title, + markdown, + category:categories(main_category, sub_category), + thumbnail:images(s3_key, blur_data_url) + `, + ) + .limit(5); + + if (samplePosts) { + for (const post of samplePosts) { + const issues: string[] = []; + + if (!post.title) issues.push("missing title"); + if (!post.markdown) issues.push("missing markdown"); + if (!post.category) issues.push("missing category"); + + if (issues.length === 0) { + console.log(`✓ Post OK: ${post.title}`); + } else { + console.log(`✗ Post issues: ${post.title} - ${issues.join(", ")}`); + } + } + } + + console.log("\n4. Checking legacy_id mapping...\n"); + + // Verify legacy_id mapping + let mappedCount = 0; + let unmappedCount = 0; + + for (const datoPost of extractedData.posts.slice(0, 10)) { + const { data } = await supabase + .from("posts") + .select("id, title") + .eq("legacy_id", datoPost.id) + .single(); + + if (data) { + console.log(`✓ Mapped: DatoCMS ID ${datoPost.id} -> Supabase`); + mappedCount++; + } else { + console.log(`✗ Not mapped: DatoCMS ID ${datoPost.id}`); + unmappedCount++; + } + } + + console.log(`\nLegacy ID mapping: ${mappedCount}/${mappedCount + unmappedCount} mapped`); + + console.log("\n=== Verification Summary ===\n"); + + const allGood = + postCount === extractedData.posts.length && + inaccessibleCount === 0 && + unmappedCount === 0; + + if (allGood) { + console.log("✓ All checks passed! Migration appears successful."); + console.log("\nNext steps:"); + console.log("1. Set DATA_SOURCE=supabase in .env"); + console.log("2. Run: pnpm dev"); + console.log("3. Verify all pages load correctly"); + console.log("4. Run: pnpm build"); + console.log("5. Deploy to staging for final verification"); + } else { + console.log("✗ Some checks failed. Please review the issues above."); + console.log("\nRecommendations:"); + if (postCount !== extractedData.posts.length) { + console.log("- Re-run 04-insert-supabase.ts to insert missing posts"); + } + if (inaccessibleCount > 0) { + console.log("- Check S3 bucket permissions and CloudFront configuration"); + } + if (unmappedCount > 0) { + console.log("- Check for posts that failed to migrate"); + } + } + + console.log("\n=== Step 5 Complete ==="); +} + +main().catch((error) => { + console.error("Error:", error); + process.exit(1); +}); diff --git a/scripts/migration/config.ts b/scripts/migration/config.ts new file mode 100644 index 0000000..c149c48 --- /dev/null +++ b/scripts/migration/config.ts @@ -0,0 +1,48 @@ +import * as dotenv from "dotenv"; +import * as path from "path"; + +// Load environment variables from .env file +dotenv.config({ path: path.resolve(process.cwd(), ".env") }); + +export const config = { + // DatoCMS + datoApiToken: process.env.API_TOKEN!, + datoApiEndpoint: "https://graphql.datocms.com/", + + // Supabase + supabaseUrl: process.env.SUPABASE_URL!, + supabaseServiceKey: process.env.SUPABASE_SERVICE_ROLE_KEY!, + + // AWS S3 + awsRegion: process.env.AWS_REGION || "ap-northeast-2", + s3Bucket: process.env.S3_BUCKET!, + awsAccessKeyId: process.env.AWS_ACCESS_KEY_ID!, + awsSecretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, + + // CloudFront + cloudfrontDomain: process.env.CLOUDFRONT_DOMAIN!, + + // Migration output paths + outputDir: path.resolve(process.cwd(), "scripts/migration/output"), + extractedDataPath: path.resolve( + process.cwd(), + "scripts/migration/output/extracted-data.json", + ), + imageMapPath: path.resolve( + process.cwd(), + "scripts/migration/output/image-map.json", + ), + blurMapPath: path.resolve( + process.cwd(), + "scripts/migration/output/blur-map.json", + ), +}; + +export const validateConfig = (requiredKeys: (keyof typeof config)[]) => { + const missing = requiredKeys.filter((key) => !config[key]); + if (missing.length > 0) { + throw new Error( + `Missing required environment variables: ${missing.join(", ")}`, + ); + } +}; diff --git a/src/app/_components/Cards/Card/index.tsx b/src/app/_components/Cards/Card/index.tsx index 857fdcd..672d4c9 100644 --- a/src/app/_components/Cards/Card/index.tsx +++ b/src/app/_components/Cards/Card/index.tsx @@ -3,7 +3,7 @@ import styles from "./index.module.scss"; import { ReactNode } from "react"; interface CardProps { - id: number; + id: string | number; Thumbnail: JSX.Element; title: string; description: string; @@ -18,24 +18,26 @@ export const Card = ({ description, createdAt, subCategory, -}: CardProps) => ( - -
- {Thumbnail} -
-

{title}

-
-

{description}

-
-
-
- {createdAt} +}: CardProps) => { + return ( + +
+ {Thumbnail} +
+

{title}

+
+

{description}

-
- {subCategory} +
+
+ {createdAt} +
+
+ {subCategory} +
-
- -); + + ); +}; diff --git a/src/app/_components/Cards/index.tsx b/src/app/_components/Cards/index.tsx index 8f8337b..84e4abd 100644 --- a/src/app/_components/Cards/index.tsx +++ b/src/app/_components/Cards/index.tsx @@ -8,7 +8,10 @@ import { devideCategoryObject } from "@/utils/getCategoryLink"; import { IMAGE_SIZE_IN_POSTS } from "@/utils/constant"; interface CardsProps { - articles: Pick[]; + articles: Pick< + PostType, + "id" | "datocmsId" | "metaField" | "category" | "_createdAt" + >[]; } const Cards = ({ articles }: CardsProps) => ( @@ -16,7 +19,7 @@ const Cards = ({ articles }: CardsProps) => ( {articles.map((article, i) => { const { metaField, - id, + datocmsId, _createdAt, category: { category }, } = article; @@ -26,7 +29,7 @@ const Cards = ({ articles }: CardsProps) => ( return ( metaField.image.responsiveImage && ( { diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index d92d1f9..d5c254e 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -1,5 +1,5 @@ import { Metadata } from "next"; -import { getPostById } from "@/app/api/dato/getPostById"; +import { getPostById } from "@/app/api"; import { PostType } from "@/types/apiResponseType"; import Markdown from "@/app/post/[id]/Markdown"; diff --git a/src/app/api/dato/getPosts.ts b/src/app/api/dato/getPosts.ts index 2ab9807..ac5c8d0 100644 --- a/src/app/api/dato/getPosts.ts +++ b/src/app/api/dato/getPosts.ts @@ -8,6 +8,7 @@ export const GET_META_FIELDS = ` ispublic: {eq: true} },first: "100") { id + datocmsId: id category _createdAt metaField { diff --git a/src/app/api/dato/index.ts b/src/app/api/dato/index.ts new file mode 100644 index 0000000..be70d09 --- /dev/null +++ b/src/app/api/dato/index.ts @@ -0,0 +1,4 @@ +export { getPosts } from "./getPosts"; +export { getPostById } from "./getPostById"; +export { getCategories } from "./getCategories"; +export { getPostIds } from "./getPostIds"; diff --git a/src/app/api/index.ts b/src/app/api/index.ts new file mode 100644 index 0000000..c49d0e3 --- /dev/null +++ b/src/app/api/index.ts @@ -0,0 +1,11 @@ +import { DATA_SOURCE } from "@/config/dataSource"; +import * as dato from "./dato"; +import * as supabase from "./supabase"; + +// Feature Flag 기반 API 전환 +const api = DATA_SOURCE === "supabase" ? supabase : dato; + +export const { getPosts, getPostById, getCategories, getPostIds } = api; + +// 타입 re-export (기존 코드 호환성 유지) +export type { PostType, PostWithoutMarkdownType } from "@/types/apiResponseType"; diff --git a/src/app/api/supabase/getCategories.ts b/src/app/api/supabase/getCategories.ts new file mode 100644 index 0000000..6b565d4 --- /dev/null +++ b/src/app/api/supabase/getCategories.ts @@ -0,0 +1,28 @@ +import { cache } from "react"; +import { supabase } from "@/libs/supabase"; +import { toCategoryFormat } from "@/libs/supabase/converter"; +import type { PostWithoutMarkdownType } from "@/types/apiResponseType"; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const _getCategories = async (_query?: string): Promise => { + const { data, error } = await supabase + .from("categories") + .select("*") + .order("created_at", { ascending: true }); + + if (error) { + throw new Error(`Failed to fetch categories: ${error.message}`); + } + + if (!data) { + return { allArticles: [] } as T; + } + + const categories: Pick[] = + data.map((row) => toCategoryFormat(row)); + + return { allArticles: categories } as T; +}; + +// React.cache()로 같은 렌더링 요청 내 중복 호출 방지 +export const getCategories = cache(_getCategories); diff --git a/src/app/api/supabase/getPostById.ts b/src/app/api/supabase/getPostById.ts new file mode 100644 index 0000000..f0cc285 --- /dev/null +++ b/src/app/api/supabase/getPostById.ts @@ -0,0 +1,67 @@ +import { cache } from "react"; +import { supabase } from "@/libs/supabase"; +import { toPostType } from "@/libs/supabase/converter"; +import type { PostWithRelations } from "@/libs/supabase/types"; + +interface GetPostByIdParams { + postId: string; +} + +// UUID 형식인지 확인하는 함수 +const isUuid = (str: string): boolean => { + const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + return uuidRegex.test(str); +}; + +const _getPostById = async ({ + postId, +}: GetPostByIdParams): Promise<{ article: T }> => { + let data: PostWithRelations | null = null; + let error: { message: string } | null = null; + + // UUID 형식이면 Supabase id로 조회 + if (isUuid(postId)) { + const { data: dataByUuid, error: errorByUuid } = await supabase + .from("posts") + .select( + ` + *, + category:categories(*), + thumbnail:images(*) + `, + ) + .eq("id", postId) + .single(); + + data = dataByUuid as PostWithRelations | null; + error = errorByUuid; + } + // 그 외 형식 (DatoCMS ID - 숫자 또는 문자열)은 datocms_id로 조회 + else { + const { data: dataByDatocmsId, error: errorByDatocmsId } = await supabase + .from("posts") + .select( + ` + *, + category:categories(*), + thumbnail:images(*) + `, + ) + .eq("datocms_id", postId) + .single(); + + data = dataByDatocmsId as PostWithRelations | null; + error = errorByDatocmsId; + } + + if (error || !data) { + throw new Error(`Post not found: ${postId}`); + } + + const post = toPostType(data) as unknown as T; + + return { article: post }; +}; + +// React.cache()로 같은 렌더링 요청 내 중복 호출 방지 +export const getPostById = cache(_getPostById); diff --git a/src/app/api/supabase/getPostIds.ts b/src/app/api/supabase/getPostIds.ts new file mode 100644 index 0000000..516ee31 --- /dev/null +++ b/src/app/api/supabase/getPostIds.ts @@ -0,0 +1,38 @@ +import { cache } from "react"; +import { supabase } from "@/libs/supabase"; + +interface PostId { + id: string; +} + +interface PostIdRow { + id: string; + datocms_id: string | null; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const _getPostIds = async (_query?: string): Promise<{ allArticles: T }> => { + const { data, error } = await supabase + .from("posts") + .select("id, datocms_id") + .eq("is_public", true) + .order("created_at", { ascending: false }); + + if (error) { + throw new Error(`Failed to fetch post IDs: ${error.message}`); + } + + if (!data) { + return { allArticles: [] as unknown as T }; + } + + // datocms_id가 있으면 그것을 사용, 없으면 UUID 사용 + const postIds: PostId[] = (data as PostIdRow[]).map((row) => ({ + id: row.datocms_id || row.id, + })); + + return { allArticles: postIds as unknown as T }; +}; + +// React.cache()로 같은 렌더링 요청 내 중복 호출 방지 +export const getPostIds = cache(_getPostIds); diff --git a/src/app/api/supabase/getPosts.ts b/src/app/api/supabase/getPosts.ts new file mode 100644 index 0000000..acc6a02 --- /dev/null +++ b/src/app/api/supabase/getPosts.ts @@ -0,0 +1,37 @@ +import { cache } from "react"; +import { supabase } from "@/libs/supabase"; +import { toPostWithoutMarkdownType } from "@/libs/supabase/converter"; +import type { PostWithoutMarkdownType } from "@/types/apiResponseType"; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const _getPosts = async (_query?: string): Promise => { + const { data, error } = await supabase + .from("posts") + .select( + ` + *, + category:categories(*), + thumbnail:images(*) + `, + ) + .eq("is_public", true) + .order("created_at", { ascending: false }) + .limit(100); + + if (error) { + throw new Error(`Failed to fetch posts: ${error.message}`); + } + + if (!data) { + return { allArticles: [] } as T; + } + + const posts: PostWithoutMarkdownType[] = data.map((row) => + toPostWithoutMarkdownType(row), + ); + + return { allArticles: posts } as T; +}; + +// React.cache()로 같은 렌더링 요청 내 중복 호출 방지 +export const getPosts = cache(_getPosts); diff --git a/src/app/api/supabase/index.ts b/src/app/api/supabase/index.ts new file mode 100644 index 0000000..be70d09 --- /dev/null +++ b/src/app/api/supabase/index.ts @@ -0,0 +1,4 @@ +export { getPosts } from "./getPosts"; +export { getPostById } from "./getPostById"; +export { getCategories } from "./getCategories"; +export { getPostIds } from "./getPostIds"; diff --git a/src/app/post/[id]/page.tsx b/src/app/post/[id]/page.tsx index 22921bf..7799d3c 100644 --- a/src/app/post/[id]/page.tsx +++ b/src/app/post/[id]/page.tsx @@ -1,5 +1,4 @@ -import { getPostById } from "@/app/api/dato/getPostById"; -import { getPosts } from "@/app/api/dato/getPosts"; +import { getPostById, getPosts } from "@/app/api"; import { HeadingIndexNav } from "@/app/_components/HeadingIndexNav"; import Markdown from "@/app/post/[id]/Markdown"; import MixpanelPageView from "@/app/post/[id]/MixpanelPageView"; diff --git a/src/app/posts/[category]/[subCategory]/page.tsx b/src/app/posts/[category]/[subCategory]/page.tsx index b4ce227..3ea0f46 100644 --- a/src/app/posts/[category]/[subCategory]/page.tsx +++ b/src/app/posts/[category]/[subCategory]/page.tsx @@ -1,12 +1,11 @@ import { Posts } from "@/app/_components/Posts"; -import { getPosts } from "@/app/api/dato/getPosts"; +import { getPosts, getCategories } from "@/app/api"; import { PostWithoutMarkdownType } from "@/types/apiResponseType"; import { SearchParamsType } from "@/types/nextSegmentType"; import { CategoryType } from "junyeol-components"; import { Metadata } from "next"; import React from "react"; import styles from "../../page.module.scss"; -import { getCategories } from "@/app/api/dato/getCategories"; import { getCategoriesAndSubCategories } from "@/app/sitemap"; import { paginatePosts } from "@/libs/paginate"; diff --git a/src/app/posts/[category]/page.tsx b/src/app/posts/[category]/page.tsx index bd08a1a..72b95e0 100644 --- a/src/app/posts/[category]/page.tsx +++ b/src/app/posts/[category]/page.tsx @@ -1,12 +1,11 @@ import { Posts } from "@/app/_components/Posts"; -import { getPosts } from "@/app/api/dato/getPosts"; +import { getPosts, getCategories } from "@/app/api"; import { PostWithoutMarkdownType } from "@/types/apiResponseType"; import { SearchParamsType } from "@/types/nextSegmentType"; import { CategoryType } from "junyeol-components"; import { Metadata } from "next"; import React from "react"; import styles from "../page.module.scss"; -import { getCategories } from "@/app/api/dato/getCategories"; import { getCategoriesAndSubCategories } from "@/app/sitemap"; interface PostsPageFilteredByCategory extends SearchParamsType { diff --git a/src/app/posts/page.tsx b/src/app/posts/page.tsx index ae6f7b1..d4896de 100644 --- a/src/app/posts/page.tsx +++ b/src/app/posts/page.tsx @@ -1,4 +1,4 @@ -import { getPosts } from "@/app/api/dato/getPosts"; +import { getPosts } from "@/app/api"; import { Metadata } from "next"; import { PostType, PostWithoutMarkdownType } from "@/types/apiResponseType"; import { SearchParamsType } from "@/types/nextSegmentType"; diff --git a/src/app/rss.xml/generateRss.ts b/src/app/rss.xml/generateRss.ts index 85fb4f1..977ed55 100644 --- a/src/app/rss.xml/generateRss.ts +++ b/src/app/rss.xml/generateRss.ts @@ -1,4 +1,4 @@ -import { getPosts } from "@/app/api/dato/getPosts"; +import { getPosts } from "@/app/api"; import { PostType } from "@/types/apiResponseType"; import { BASE_URL } from "@/utils/constant"; import Rss from "rss"; diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 3df37db..af2f82f 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -1,5 +1,4 @@ -import { getPostIds } from "@/app/api/dato/getPostIds"; -import { getPosts } from "@/app/api/dato/getPosts"; +import { getPostIds, getPosts } from "@/app/api"; import { PostType } from "@/types/apiResponseType"; import { BASE_URL } from "@/utils/constant"; import { MetadataRoute } from "next"; diff --git a/src/config/dataSource.ts b/src/config/dataSource.ts new file mode 100644 index 0000000..c3bb410 --- /dev/null +++ b/src/config/dataSource.ts @@ -0,0 +1,6 @@ +export type DataSourceType = "datocms" | "supabase"; + +export const DATA_SOURCE: DataSourceType = + (process.env.DATA_SOURCE as DataSourceType) || "datocms"; + +export const CLOUDFRONT_DOMAIN = process.env.CLOUDFRONT_DOMAIN || ""; diff --git a/src/libs/supabase/client.ts b/src/libs/supabase/client.ts new file mode 100644 index 0000000..1f7a1ab --- /dev/null +++ b/src/libs/supabase/client.ts @@ -0,0 +1,16 @@ +import { createClient } from "@supabase/supabase-js"; +import type { Database } from "./types"; + +const supabaseUrl = process.env.SUPABASE_URL!; +const supabaseAnonKey = process.env.SUPABASE_ANON_KEY!; + +export const supabase = createClient(supabaseUrl, supabaseAnonKey); + +// Service role client for server-side operations (migration scripts 등) +export const createServiceClient = () => { + const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY; + if (!serviceRoleKey) { + throw new Error("SUPABASE_SERVICE_ROLE_KEY is not set"); + } + return createClient(supabaseUrl, serviceRoleKey); +}; diff --git a/src/libs/supabase/converter.ts b/src/libs/supabase/converter.ts new file mode 100644 index 0000000..25aad98 --- /dev/null +++ b/src/libs/supabase/converter.ts @@ -0,0 +1,98 @@ +import { CategoryType } from "junyeol-components"; +import { CLOUDFRONT_DOMAIN } from "@/config/dataSource"; +import { IMAGE_SIZE_IN_POSTS } from "@/utils/constant"; +import type { + PostType, + PostWithoutMarkdownType, + ResponsiveImageType, +} from "@/types/apiResponseType"; +import type { PostWithRelations, CategoryRow } from "./types"; + +/** + * CloudFront URL 생성 + */ +const getCloudFrontUrl = (s3Key: string): string => { + if (!CLOUDFRONT_DOMAIN) { + throw new Error("CLOUDFRONT_DOMAIN is not configured"); + } + return `https://${CLOUDFRONT_DOMAIN}/${s3Key}`; +}; + +/** + * Supabase PostRow를 기존 PostType으로 변환 + */ +export const toPostType = (row: PostWithRelations): PostType => { + const imageUrl = row.thumbnail + ? getCloudFrontUrl(row.thumbnail.s3_key) + : ""; + + const responsiveImage: ResponsiveImageType = row.thumbnail + ? { + src: imageUrl, + sizes: `(max-width: ${IMAGE_SIZE_IN_POSTS.width}px) 100vw, ${IMAGE_SIZE_IN_POSTS.width}px`, + width: row.thumbnail.width || IMAGE_SIZE_IN_POSTS.width, + height: row.thumbnail.height || IMAGE_SIZE_IN_POSTS.height, + alt: row.thumbnail.alt || "", + title: row.thumbnail.title || "", + base64: row.thumbnail.blur_data_url || "", + } + : { + src: "", + sizes: "", + width: IMAGE_SIZE_IN_POSTS.width, + height: IMAGE_SIZE_IN_POSTS.height, + alt: "", + title: "", + base64: "", + }; + + return { + id: row.id, // Supabase UUID + datocmsId: row.datocms_id || row.id, // URL용 DatoCMS ID + _createdAt: row.created_at, + category: { + category: { + [row.category.main_category as CategoryType]: row.category.sub_category, + }, + }, + markdown: row.markdown, + metaField: { + title: row.title, + description: row.description || "", + image: { + alt: row.thumbnail?.alt || "", + url: imageUrl, + responsiveImage, + }, + }, + isPublic: row.is_public, + }; +}; + +/** + * Supabase PostRow를 기존 PostWithoutMarkdownType으로 변환 + */ +export const toPostWithoutMarkdownType = ( + row: PostWithRelations, +): PostWithoutMarkdownType => { + const full = toPostType(row); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { markdown, ...withoutMarkdown } = full; + return withoutMarkdown; +}; + +/** + * CategoryRow를 기존 카테고리 형식으로 변환 + */ +export const toCategoryFormat = ( + row: CategoryRow, +): Pick => { + return { + _createdAt: row.created_at, + category: { + category: { + [row.main_category as CategoryType]: row.sub_category, + }, + }, + }; +}; diff --git a/src/libs/supabase/index.ts b/src/libs/supabase/index.ts new file mode 100644 index 0000000..7a1562d --- /dev/null +++ b/src/libs/supabase/index.ts @@ -0,0 +1,3 @@ +export { supabase, createServiceClient } from "./client"; +export * from "./types"; +export * from "./converter"; diff --git a/src/libs/supabase/types.ts b/src/libs/supabase/types.ts new file mode 100644 index 0000000..0eae538 --- /dev/null +++ b/src/libs/supabase/types.ts @@ -0,0 +1,74 @@ +export interface Database { + public: { + Tables: { + categories: { + Row: CategoryRow; + Insert: CategoryInsert; + Update: CategoryUpdate; + }; + images: { + Row: ImageRow; + Insert: ImageInsert; + Update: ImageUpdate; + }; + posts: { + Row: PostRow; + Insert: PostInsert; + Update: PostUpdate; + }; + }; + Views: Record; + Functions: Record; + Enums: Record; + }; +} + +// Category +export interface CategoryRow { + id: string; + main_category: string; + sub_category: string; + created_at: string; +} + +export type CategoryInsert = Omit; +export type CategoryUpdate = Partial; + +// Image +export interface ImageRow { + id: string; + s3_key: string; + original_url: string | null; + alt: string | null; + title: string | null; + width: number | null; + height: number | null; + blur_data_url: string | null; + created_at: string; +} + +export type ImageInsert = Omit; +export type ImageUpdate = Partial; + +// Post +export interface PostRow { + id: string; + datocms_id: string | null; + title: string; + description: string | null; + markdown: string; + thumbnail_id: string | null; + category_id: string; + is_public: boolean; + created_at: string; + updated_at: string; +} + +export type PostInsert = Omit; +export type PostUpdate = Partial; + +// Joined types for queries +export interface PostWithRelations extends PostRow { + category: CategoryRow; + thumbnail: ImageRow | null; +} diff --git a/src/types/apiResponseType.ts b/src/types/apiResponseType.ts index fd81f11..0c33509 100644 --- a/src/types/apiResponseType.ts +++ b/src/types/apiResponseType.ts @@ -25,11 +25,12 @@ export interface ResponsiveImageType { } export interface PostType { - id: number; + id: string | number; _createdAt: string; category: { category: Partial>; }; + datocmsId: string; markdown: string; metaField: MetaField; isPublic?: boolean; diff --git a/src/utils/constant.ts b/src/utils/constant.ts index 77fac8f..e8d634b 100644 --- a/src/utils/constant.ts +++ b/src/utils/constant.ts @@ -5,8 +5,12 @@ export const IMAGE_SIZE_IN_POSTS = { height: 120, }; +// NO_IMAGE는 DATA_SOURCE에 따라 동적으로 결정되어야 하지만, +// 빌드 타임에 결정되어야 하므로 CloudFront URL이 없으면 DatoCMS 폴백 사용 export const NO_IMAGE = { - src: "https://www.datocms-assets.com/107137/1699598396-noimage.jpg", + src: process.env.CLOUDFRONT_DOMAIN + ? `https://${process.env.CLOUDFRONT_DOMAIN}/images/noimage.jpg` + : "https://www.datocms-assets.com/107137/1699598396-noimage.jpg", alt: "No image", };