From 0655101214460a1f4d6882690be9be04bc1564fb Mon Sep 17 00:00:00 2001 From: Hyein Jeong Date: Mon, 11 Nov 2024 17:50:31 +0900 Subject: [PATCH 01/11] =?UTF-8?q?[Feat][BE]=20:=20#40=20:=20webSocket=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - websocket 연결을 위한 js 구현 - http 서버 생성 및 websocket 서버 초기화 기능 구현 --- backend/package.json | 3 ++- backend/src/constants.js | 1 + backend/src/index.js | 14 +++++++++--- backend/src/websocketServer.js | 41 ++++++++++++++++++++++++++++++++++ pnpm-lock.yaml | 3 +++ 5 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 backend/src/constants.js create mode 100644 backend/src/websocketServer.js diff --git a/backend/package.json b/backend/package.json index 1b3cc2f9..8e9a39c5 100644 --- a/backend/package.json +++ b/backend/package.json @@ -24,6 +24,7 @@ "express": "^4.21.1", "pg": "^8.13.1", "swagger-jsdoc": "^6.2.8", - "swagger-ui-express": "^5.0.1" + "swagger-ui-express": "^5.0.1", + "ws": "^8.18.0" } } diff --git a/backend/src/constants.js b/backend/src/constants.js new file mode 100644 index 00000000..fcd872ce --- /dev/null +++ b/backend/src/constants.js @@ -0,0 +1 @@ +export const PORT = 3001; diff --git a/backend/src/index.js b/backend/src/index.js index da1ee137..a7dac632 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -1,11 +1,13 @@ import express from 'express'; import swaggerUi from 'swagger-ui-express'; +import http from 'http'; import { specs } from '../swaggerConfig'; import { pool } from './db'; +import { PORT } from './constants'; +import { initializeWebSocketServer } from './websocketServer'; const app = express(); app.use(express.json()); -const port = 3001; app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs)); @@ -25,6 +27,12 @@ app.get('/example', (req, res) => { res.send('Hello World'); }); -app.listen(port, () => { - console.log(`Server is running on http://localhost:${port}`); +// HTTP 서버 생성 +const server = http.createServer(app); + +// WebSocket 서버 초기화 +initializeWebSocketServer(server); + +app.listen(PORT, () => { + console.log(`Server is running on http://localhost:${PORT}`); }); diff --git a/backend/src/websocketServer.js b/backend/src/websocketServer.js new file mode 100644 index 00000000..de4a1b00 --- /dev/null +++ b/backend/src/websocketServer.js @@ -0,0 +1,41 @@ +import { WebSocketServer } from 'ws'; + +const activeConnections = {}; // token별로 연결을 관리하기 위한 객체 + +export function initializeWebSocketServer(server) { + const wss = new WebSocketServer({ server }); + + wss.on('connection', (ws, req) => { + // URL에서 token 추출 + // TODO: 프론트 라우터 및 token 설정 완료 후 테스트 + const url = new URL(req.url, `http://${req.headers.host}`); + const token = url.searchParams.get('token'); + + if (!token) { + ws.close(4001, 'Token is required'); + return; + } + + // 동일한 token으로 이미 연결된 클라이언트가 있으면 이전 연결을 강제로 종료 + if (activeConnections[token]) { + activeConnections[token].close(4000, 'Duplicate connection'); + } + + // 새로운 연결을 활성화된 연결 목록에 저장 + activeConnections[token] = ws; + + console.log(`Client connected with token: ${token}`); + + // 클라이언트로부터 메시지 받았을 때의 이벤트 처리 + ws.on('message', message => { + console.log(`Received from ${token}:`, message); + }); + + // 클라이언트 연결 종료 시 + ws.on('close', (code, reason) => { + console.log(`Client disconnected with token: ${token}, Code: ${code}, Reason: ${reason}`); + // 연결이 종료되면 activeConnections에서 해당 token 제거 + delete activeConnections[token]; + }); + }); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5208ee6..31a5723f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -120,6 +120,9 @@ importers: swagger-ui-express: specifier: ^5.0.1 version: 5.0.1(express@4.21.1) + ws: + specifier: ^8.18.0 + version: 8.18.0 docs/docusaurus: dependencies: From b18870b48e4fa0a7e99f09c390d63c4433609c9a Mon Sep 17 00:00:00 2001 From: Hyein Jeong Date: Mon, 11 Nov 2024 23:51:29 +0900 Subject: [PATCH 02/11] =?UTF-8?q?[BE][Feat]=20:=20#42=20:=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인을 위한 패키지 (bcrypt, express-validator, jsonwebtoken) 설치 - 로그인을 위한 router, controller, service, repository, middleware 구현 - index.js router 호출 및 사용 - 로그인 api 구현 테스트 --- backend/src/index.js | 18 +++++++++++++----- backend/src/websocketServer.js | 4 ++-- eslint.config.mjs | 7 ++++--- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/backend/src/index.js b/backend/src/index.js index a7dac632..8fbfd440 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -1,16 +1,19 @@ import express from 'express'; import swaggerUi from 'swagger-ui-express'; import http from 'http'; -import { specs } from '../swaggerConfig'; -import { pool } from './db'; -import { PORT } from './constants'; -import { initializeWebSocketServer } from './websocketServer'; +import { specs } from '../swaggerConfig.js'; +import { pool } from './db/db.js'; +import { PORT } from './constants/constants.js'; +import { initializeWebSocketServer } from './websocketServer.js'; +import authRouter from './routes/authRouter.js'; const app = express(); app.use(express.json()); app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs)); +app.use('/api/auth', authRouter); + // TODO: 데이터베이스에서 데이터 가져오기 예시 app.get('/guests', async (req, res) => { try { @@ -31,7 +34,12 @@ app.get('/example', (req, res) => { const server = http.createServer(app); // WebSocket 서버 초기화 -initializeWebSocketServer(server); +try { + initializeWebSocketServer(server); + console.log('WebSocket server initialized successfully.'); +} catch (error) { + console.error('Failed to initialize WebSocket server:', error); +} app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); diff --git a/backend/src/websocketServer.js b/backend/src/websocketServer.js index de4a1b00..de324c78 100644 --- a/backend/src/websocketServer.js +++ b/backend/src/websocketServer.js @@ -2,7 +2,7 @@ import { WebSocketServer } from 'ws'; const activeConnections = {}; // token별로 연결을 관리하기 위한 객체 -export function initializeWebSocketServer(server) { +export const initializeWebSocketServer = server => { const wss = new WebSocketServer({ server }); wss.on('connection', (ws, req) => { @@ -38,4 +38,4 @@ export function initializeWebSocketServer(server) { delete activeConnections[token]; }); }); -} +}; diff --git a/eslint.config.mjs b/eslint.config.mjs index 4dc8d4c1..4f7bcb1f 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -58,10 +58,10 @@ export default [ 'no-undef': 'off', 'import/extensions': [ 'error', - 'ignorePackages', + 'always', { - js: 'never', - jsx: 'never', + js: 'always', + jsx: 'always', ts: 'never', tsx: 'never', }, @@ -111,6 +111,7 @@ export default [ 'import/prefer-default-export': 'off', 'import/no-unresolved': 'warn', 'no-console': 'off', + 'import/extensions': 'off', }, }, From cd60fab1cf52627b83d0bd1b35a92159bce3bbd5 Mon Sep 17 00:00:00 2001 From: Hyein Jeong Date: Mon, 11 Nov 2024 23:52:55 +0900 Subject: [PATCH 03/11] =?UTF-8?q?[BE][Feat]=20:=20#42=20:=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인을 위한 패키지 (bcrypt, express-validator, jsonwebtoken) 설치 - 로그인을 위한 router, controller, service, repository, middleware 구현 - index.js router 호출 및 사용 - 로그인 api 구현 테스트 --- backend/src/routes/authRouter.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 backend/src/routes/authRouter.js diff --git a/backend/src/routes/authRouter.js b/backend/src/routes/authRouter.js new file mode 100644 index 00000000..3cb2d080 --- /dev/null +++ b/backend/src/routes/authRouter.js @@ -0,0 +1,18 @@ +import express from 'express'; +import { body } from 'express-validator'; +import { login } from '../controllers/authController.js'; +import validationMiddleware from '../middleware/validationMiddleware.js'; + +export const router = express.Router(); + +router.post( + '/login', + [ + body('id').notEmpty().withMessage('ID is required'), + body('password') + .isLength({ min: 6 }) + .withMessage('Password must be at least 6 characters long'), + ], + validationMiddleware, + login, +); From eb33e2d5e764f59934773cecb651dc71830ddaf3 Mon Sep 17 00:00:00 2001 From: Hyein Jeong Date: Mon, 11 Nov 2024 23:59:32 +0900 Subject: [PATCH 04/11] =?UTF-8?q?[BE][ENV]=20:=20eslint=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - eslint 설정 변경 --- eslint.config.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/eslint.config.mjs b/eslint.config.mjs index 4f7bcb1f..f2f552a9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -111,6 +111,7 @@ export default [ 'import/prefer-default-export': 'off', 'import/no-unresolved': 'warn', 'no-console': 'off', + 'consistent-return': 'off', 'import/extensions': 'off', }, }, From 9be7e01fa7a0c6913bd9370c29d77daa625912c0 Mon Sep 17 00:00:00 2001 From: Hyein Jeong Date: Tue, 12 Nov 2024 00:00:08 +0900 Subject: [PATCH 05/11] =?UTF-8?q?[BE][Feat]=20:=20#42=20:=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로그인을 위한 패키지 (bcrypt, express-validator, jsonwebtoken) 설치 - 로그인을 위한 router, controller, service, repository, middleware 구현 - index.js router 호출 및 사용 - 로그인 api 구현 테스트 --- backend/package.json | 5 +- backend/src/{ => constants}/constants.js | 0 backend/src/controllers/authController.js | 16 + backend/src/{ => db}/db.js | 0 backend/src/index.js | 2 +- .../src/middleware/validationMiddleware.js | 9 + backend/src/repositories/userRepository.js | 6 + backend/src/routes/authRouter.js | 6 +- backend/src/services/authService.js | 19 + pnpm-lock.yaml | 364 +++++++++++++++++- 10 files changed, 420 insertions(+), 7 deletions(-) rename backend/src/{ => constants}/constants.js (100%) create mode 100644 backend/src/controllers/authController.js rename backend/src/{ => db}/db.js (100%) create mode 100644 backend/src/middleware/validationMiddleware.js create mode 100644 backend/src/repositories/userRepository.js create mode 100644 backend/src/services/authService.js diff --git a/backend/package.json b/backend/package.json index 8e9a39c5..eb7ad713 100644 --- a/backend/package.json +++ b/backend/package.json @@ -20,11 +20,14 @@ "author": "", "license": "ISC", "dependencies": { + "bcrypt": "^5.1.1", "dotenv": "^16.4.5", "express": "^4.21.1", + "express-validator": "^7.2.0", + "jsonwebtoken": "^9.0.2", "pg": "^8.13.1", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", - "ws": "^8.18.0" + "ws": "^8.11.0" } } diff --git a/backend/src/constants.js b/backend/src/constants/constants.js similarity index 100% rename from backend/src/constants.js rename to backend/src/constants/constants.js diff --git a/backend/src/controllers/authController.js b/backend/src/controllers/authController.js new file mode 100644 index 00000000..6739eee0 --- /dev/null +++ b/backend/src/controllers/authController.js @@ -0,0 +1,16 @@ +import { loginUser } from '../services/authService.js'; + +export const login = async (req, res) => { + const { id, password } = req.body; + + try { + const token = await loginUser(id, password); + if (!token) { + return res.status(401).json({ message: 'Invalid ID or password' }); + } + return res.status(200).json({ token }); + } catch (error) { + console.error('Login error:', error); + return res.status(500).json({ message: 'Server error occurred' }); + } +}; diff --git a/backend/src/db.js b/backend/src/db/db.js similarity index 100% rename from backend/src/db.js rename to backend/src/db/db.js diff --git a/backend/src/index.js b/backend/src/index.js index 8fbfd440..0f772826 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -5,7 +5,7 @@ import { specs } from '../swaggerConfig.js'; import { pool } from './db/db.js'; import { PORT } from './constants/constants.js'; import { initializeWebSocketServer } from './websocketServer.js'; -import authRouter from './routes/authRouter.js'; +import { authRouter } from './routes/authRouter.js'; const app = express(); app.use(express.json()); diff --git a/backend/src/middleware/validationMiddleware.js b/backend/src/middleware/validationMiddleware.js new file mode 100644 index 00000000..023c2ce4 --- /dev/null +++ b/backend/src/middleware/validationMiddleware.js @@ -0,0 +1,9 @@ +import { validationResult } from 'express-validator'; + +export const validationMiddleware = (req, res, next) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ errors: errors.array() }); + } + next(); +}; diff --git a/backend/src/repositories/userRepository.js b/backend/src/repositories/userRepository.js new file mode 100644 index 00000000..56e133e5 --- /dev/null +++ b/backend/src/repositories/userRepository.js @@ -0,0 +1,6 @@ +import { pool } from '../db/db.js'; + +export const findUserById = async id => { + const result = await pool.query('SELECT * FROM "main"."user" WHERE id = $1', [id]); + return result.rows[0]; +}; diff --git a/backend/src/routes/authRouter.js b/backend/src/routes/authRouter.js index 3cb2d080..caf0b954 100644 --- a/backend/src/routes/authRouter.js +++ b/backend/src/routes/authRouter.js @@ -1,11 +1,11 @@ import express from 'express'; import { body } from 'express-validator'; import { login } from '../controllers/authController.js'; -import validationMiddleware from '../middleware/validationMiddleware.js'; +import { validationMiddleware } from '../middleware/validationMiddleware.js'; -export const router = express.Router(); +export const authRouter = express.Router(); -router.post( +authRouter.post( '/login', [ body('id').notEmpty().withMessage('ID is required'), diff --git a/backend/src/services/authService.js b/backend/src/services/authService.js new file mode 100644 index 00000000..e8f7c36c --- /dev/null +++ b/backend/src/services/authService.js @@ -0,0 +1,19 @@ +import bcrypt from 'bcrypt'; +import jwt from 'jsonwebtoken'; +import { findUserById } from '../repositories/userRepository.js'; + +export const loginUser = async (id, password) => { + const user = await findUserById(id); + if (!user) { + throw new Error('User not found'); + } + + const isPasswordValid = await bcrypt.compare(password, user.password); + if (!isPasswordValid) { + throw new Error('Invalid password'); + } + + // JWT 생성 + const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' }); + return { token, userId: user.id }; +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31a5723f..de1686db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,12 +105,21 @@ importers: backend: dependencies: + bcrypt: + specifier: ^5.1.1 + version: 5.1.1 dotenv: specifier: ^16.4.5 version: 16.4.5 express: specifier: ^4.21.1 version: 4.21.1 + express-validator: + specifier: ^7.2.0 + version: 7.2.0 + jsonwebtoken: + specifier: ^9.0.2 + version: 9.0.2 pg: specifier: ^8.13.1 version: 8.13.1 @@ -121,8 +130,8 @@ importers: specifier: ^5.0.1 version: 5.0.1(express@4.21.1) ws: - specifier: ^8.18.0 - version: 8.18.0 + specifier: ^8.11.0 + version: 8.11.0 docs/docusaurus: dependencies: @@ -1482,6 +1491,10 @@ packages: '@leichtgewicht/ip-codec@2.0.5': resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} + '@mapbox/node-pre-gyp@1.0.11': + resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} + hasBin: true + '@mdx-js/mdx@3.1.0': resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==} @@ -2480,6 +2493,9 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -2519,6 +2535,10 @@ packages: resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} engines: {node: '>= 10.0.0'} + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -2606,6 +2626,14 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + + are-we-there-yet@2.0.0: + resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -2733,6 +2761,10 @@ packages: batch@0.6.1: resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} + bcrypt@5.1.1: + resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} + engines: {node: '>= 10.0.0'} + better-opn@3.0.2: resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} engines: {node: '>=12.0.0'} @@ -2783,6 +2815,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -2904,6 +2939,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + chromatic@11.16.5: resolution: {integrity: sha512-wUEKXyu3GYmUg6Jq13uyRE9iC8ph5gbfDHdyHH0vQathkGQrcjHHdoxI/GXKIjU6d+xupLon8sxRV9NuZKTWbA==} hasBin: true @@ -2975,6 +3014,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} @@ -3058,6 +3101,9 @@ packages: resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} engines: {node: ^14.18.0 || >=16.10.0} + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + content-disposition@0.5.2: resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} engines: {node: '>= 0.6'} @@ -3480,6 +3526,9 @@ packages: delaunator@5.0.1: resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} @@ -3496,6 +3545,10 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} @@ -3586,6 +3639,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -3963,6 +4019,10 @@ packages: resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} engines: {node: '>=12.0.0'} + express-validator@7.2.0: + resolution: {integrity: sha512-I2ByKD8panjtr8Y05l21Wph9xk7kk64UMyvJCl/fFM/3CTJq8isXYPLeKW/aZBCdb/LYNv63PwhY8khw8VWocA==} + engines: {node: '>= 8.0.0'} + express@4.21.1: resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==} engines: {node: '>= 0.10.0'} @@ -4121,6 +4181,10 @@ packages: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + fs-monkey@1.0.6: resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} @@ -4142,6 +4206,11 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + gauge@3.0.2: + resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -4299,6 +4368,9 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + has-yarn@3.0.0: resolution: {integrity: sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4422,6 +4494,10 @@ packages: resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} engines: {node: '>=10.19.0'} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -4842,10 +4918,20 @@ packages: jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + katex@0.16.11: resolution: {integrity: sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==} hasBin: true @@ -4962,9 +5048,27 @@ packages: lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} @@ -4974,6 +5078,9 @@ packages: lodash.mergewith@4.6.2: resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} @@ -5024,6 +5131,10 @@ packages: magic-string@0.30.12: resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + map-or-similar@1.5.0: resolution: {integrity: sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==} @@ -5339,10 +5450,22 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} @@ -5390,10 +5513,22 @@ packages: no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + node-addon-api@5.1.0: + resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} + node-emoji@2.1.3: resolution: {integrity: sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==} engines: {node: '>=18'} + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-forge@1.3.1: resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} @@ -5401,6 +5536,11 @@ packages: node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -5421,6 +5561,10 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + npmlog@5.0.1: + resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + deprecated: This package is no longer supported. + nprogress@0.2.0: resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} @@ -6524,6 +6668,9 @@ packages: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -6853,6 +7000,10 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + terser-webpack-plugin@5.3.10: resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} @@ -6923,6 +7074,9 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -7274,6 +7428,9 @@ packages: web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webpack-bundle-analyzer@4.10.2: resolution: {integrity: sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==} engines: {node: '>= 10.13.0'} @@ -7337,6 +7494,9 @@ packages: resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} engines: {node: '>=0.8.0'} + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -7366,6 +7526,9 @@ packages: engines: {node: '>=8'} hasBin: true + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + widest-line@4.0.1: resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} engines: {node: '>=12'} @@ -7407,6 +7570,18 @@ packages: utf-8-validate: optional: true + ws@8.11.0: + resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==} + 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 + ws@8.18.0: resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} engines: {node: '>=10.0.0'} @@ -7437,6 +7612,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -9516,6 +9694,21 @@ snapshots: '@leichtgewicht/ip-codec@2.0.5': {} + '@mapbox/node-pre-gyp@1.0.11': + dependencies: + detect-libc: 2.0.3 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.7.0 + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.6.3 + tar: 6.2.1 + transitivePeerDependencies: + - encoding + - supports-color + '@mdx-js/mdx@3.1.0(acorn@6.4.2)': dependencies: '@types/estree': 1.0.6 @@ -10717,6 +10910,8 @@ snapshots: '@xtuc/long@4.2.2': {} + abbrev@1.1.1: {} + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -10748,6 +10943,12 @@ snapshots: address@1.2.2: {} + agent-base@6.0.2: + dependencies: + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 @@ -10856,6 +11057,13 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + aproba@2.0.0: {} + + are-we-there-yet@2.0.0: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + arg@5.0.2: {} argparse@1.0.10: @@ -11010,6 +11218,14 @@ snapshots: batch@0.6.1: {} + bcrypt@5.1.1: + dependencies: + '@mapbox/node-pre-gyp': 1.0.11 + node-addon-api: 5.1.0 + transitivePeerDependencies: + - encoding + - supports-color + better-opn@3.0.2: dependencies: open: 8.4.2 @@ -11088,6 +11304,8 @@ snapshots: node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.2) + buffer-equal-constant-time@1.0.1: {} + buffer-from@1.1.2: {} bytes@3.0.0: {} @@ -11229,6 +11447,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chownr@2.0.0: {} + chromatic@11.16.5: {} chrome-trace-event@1.0.4: {} @@ -11282,6 +11502,8 @@ snapshots: color-name@1.1.4: {} + color-support@1.1.3: {} + colord@2.9.3: {} colorette@2.0.20: {} @@ -11350,6 +11572,8 @@ snapshots: consola@3.2.3: {} + console-control-strings@1.1.0: {} + content-disposition@0.5.2: {} content-disposition@0.5.4: @@ -11814,6 +12038,8 @@ snapshots: dependencies: robust-predicates: 3.0.2 + delegates@1.0.0: {} + depd@1.1.2: {} depd@2.0.0: {} @@ -11822,6 +12048,8 @@ snapshots: destroy@1.2.0: {} + detect-libc@2.0.3: {} + detect-node@2.1.0: {} detect-port-alt@1.1.6: @@ -11921,6 +12149,10 @@ snapshots: eastasianwidth@0.2.0: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + ee-first@1.1.1: {} electron-to-chromium@1.5.51: {} @@ -12452,6 +12684,11 @@ snapshots: expect-type@1.1.0: {} + express-validator@7.2.0: + dependencies: + lodash: 4.17.21 + validator: 13.12.0 + express@4.21.1: dependencies: accepts: 1.3.8 @@ -12644,6 +12881,10 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.1 + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + fs-monkey@1.0.6: {} fs.realpath@1.0.0: {} @@ -12662,6 +12903,18 @@ snapshots: functions-have-names@1.2.3: {} + gauge@3.0.2: + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + gensync@1.0.0-beta.2: {} get-east-asian-width@1.3.0: {} @@ -12835,6 +13088,8 @@ snapshots: dependencies: has-symbols: 1.0.3 + has-unicode@2.0.1: {} + has-yarn@3.0.0: {} hasown@2.0.2: @@ -13069,6 +13324,13 @@ snapshots: quick-lru: 5.1.1 resolve-alpn: 1.2.1 + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} human-signals@5.0.0: {} @@ -13433,6 +13695,19 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.6.3 + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 @@ -13440,6 +13715,17 @@ snapshots: object.assign: 4.1.5 object.values: 1.2.0 + jwa@1.4.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + katex@0.16.11: dependencies: commander: 8.3.0 @@ -13562,14 +13848,28 @@ snapshots: lodash.get@4.4.2: {} + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + lodash.isequal@4.5.0: {} + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + lodash.memoize@4.1.2: {} lodash.merge@4.6.2: {} lodash.mergewith@4.6.2: {} + lodash.once@4.1.1: {} + lodash.uniq@4.5.0: {} lodash@4.17.21: {} @@ -13618,6 +13918,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + map-or-similar@1.5.0: {} markdown-extensions@2.0.0: {} @@ -14229,8 +14533,19 @@ snapshots: minimist@1.2.8: {} + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + minipass@7.1.2: {} + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + mkdirp@1.0.4: {} mlly@1.7.2: @@ -14272,6 +14587,8 @@ snapshots: lower-case: 2.0.2 tslib: 2.8.1 + node-addon-api@5.1.0: {} + node-emoji@2.1.3: dependencies: '@sindresorhus/is': 4.6.0 @@ -14279,10 +14596,18 @@ snapshots: emojilib: 2.4.0 skin-tone: 2.0.0 + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + node-forge@1.3.1: {} node-releases@2.0.18: {} + nopt@5.0.0: + dependencies: + abbrev: 1.1.1 + normalize-path@3.0.0: {} normalize-range@0.1.2: {} @@ -14297,6 +14622,13 @@ snapshots: dependencies: path-key: 4.0.0 + npmlog@5.0.1: + dependencies: + are-we-there-yet: 2.0.0 + console-control-strings: 1.1.0 + gauge: 3.0.2 + set-blocking: 2.0.0 + nprogress@0.2.0: {} nth-check@2.1.1: @@ -15549,6 +15881,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-blocking@2.0.0: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -15943,6 +16277,15 @@ snapshots: tapable@2.2.1: {} + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + terser-webpack-plugin@5.3.10(webpack@5.96.1): dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -15993,6 +16336,8 @@ snapshots: totalist@3.0.1: {} + tr46@0.0.3: {} + trim-lines@3.0.1: {} trough@2.2.0: {} @@ -16361,6 +16706,8 @@ snapshots: web-namespaces@2.0.1: {} + webidl-conversions@3.0.1: {} + webpack-bundle-analyzer@4.10.2: dependencies: '@discoveryjs/json-ext': 0.5.7 @@ -16494,6 +16841,11 @@ snapshots: websocket-extensions@0.1.4: {} + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 @@ -16545,6 +16897,10 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + widest-line@4.0.1: dependencies: string-width: 5.1.2 @@ -16582,6 +16938,8 @@ snapshots: ws@7.5.10: {} + ws@8.11.0: {} + ws@8.18.0: {} xdg-basedir@5.1.0: {} @@ -16596,6 +16954,8 @@ snapshots: yallist@3.1.1: {} + yallist@4.0.0: {} + yaml@1.10.2: {} yaml@2.0.0-1: {} From 96b64d49a5218b7351677902b2ef90aa02195d7a Mon Sep 17 00:00:00 2001 From: effozen Date: Tue, 12 Nov 2024 08:50:56 +0900 Subject: [PATCH 06/11] =?UTF-8?q?[FE][Feat]=20#108=20:=20dropdownButton=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/dropdown/DropdownButton.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 frontend/src/component/common/dropdown/DropdownButton.tsx diff --git a/frontend/src/component/common/dropdown/DropdownButton.tsx b/frontend/src/component/common/dropdown/DropdownButton.tsx new file mode 100644 index 00000000..6e5043d9 --- /dev/null +++ b/frontend/src/component/common/dropdown/DropdownButton.tsx @@ -0,0 +1,27 @@ +import { ReactNode } from 'react'; +import classNames from 'classnames'; + +interface IDropdownButtonProps extends React.ButtonHTMLAttributes { + children: ReactNode; + className?: string; +} + +export const DropdownButton = (props: IDropdownButtonProps) => { + return ( + + ); +}; From bf816334e0deb82ae31bb9c6ce79bf5a9cceaf70 Mon Sep 17 00:00:00 2001 From: effozen Date: Tue, 12 Nov 2024 08:54:20 +0900 Subject: [PATCH 07/11] =?UTF-8?q?[FE][Docs]=20#108=20:=20dropdownButton=20?= =?UTF-8?q?=EC=8A=A4=ED=86=A0=EB=A6=AC=EB=B6=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/stories/DropdownButton.stories.tsx | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 frontend/src/stories/DropdownButton.stories.tsx diff --git a/frontend/src/stories/DropdownButton.stories.tsx b/frontend/src/stories/DropdownButton.stories.tsx new file mode 100644 index 00000000..46745bee --- /dev/null +++ b/frontend/src/stories/DropdownButton.stories.tsx @@ -0,0 +1,46 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { fn } from '@storybook/test'; + +import { DropdownButton } from '@/component/common/dropdown/DropdownButton.tsx'; + +import { MdDensityMedium } from 'react-icons/md'; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export +const meta = { + title: 'Dropdown/Button', + component: DropdownButton, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout + layout: 'centered', + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + tags: ['autodocs'], + // More on argTypes: https://storybook.js.org/docs/api/argtypes + argTypes: { + children: { + control: 'object', + description: '자식 컴포넌트로 항상 리액트 노드를 넘겨준다.', + table: { + type: { summary: 'ReactNode' }, + }, + required: true, // 설명 목적으로 required 여부는 table 필드로 작성함 + }, + className: { + control: 'text', + description: '테일 윈드 기반의 클래스 이름을 넘겨준다.', + }, + }, + // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args + args: { onClick: fn() }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const Default: Story = { + args: { + children: , + className: '', + }, +}; From bdb253f16298822111e32ec7959834d9c82b26cb Mon Sep 17 00:00:00 2001 From: effozen Date: Tue, 12 Nov 2024 09:05:30 +0900 Subject: [PATCH 08/11] =?UTF-8?q?[FE][Feat]=20#108=20:=20vite.svg=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/public/vite.svg | 1 - 1 file changed, 1 deletion(-) delete mode 100644 frontend/public/vite.svg diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg deleted file mode 100644 index e7b8dfb1..00000000 --- a/frontend/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From fc3dc202a8f8605cf7611559b1a9cae3e4043e70 Mon Sep 17 00:00:00 2001 From: effozen Date: Tue, 12 Nov 2024 09:54:11 +0900 Subject: [PATCH 09/11] =?UTF-8?q?[FE][Chore]=20#108=20:=20storybook=20tail?= =?UTF-8?q?wind=EC=99=80=20font=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/.storybook/preview.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend/.storybook/preview.ts b/frontend/.storybook/preview.ts index adcda96b..a9a9b4fb 100644 --- a/frontend/.storybook/preview.ts +++ b/frontend/.storybook/preview.ts @@ -1,4 +1,15 @@ import type { Preview } from '@storybook/react'; +import '../src/index.css'; +// 우선은 폰트 다 포함시켰는데, 나중에 사용할 것들만 따로 뺴자. +import '@fontsource/pretendard/100.css'; +import '@fontsource/pretendard/200.css'; +import '@fontsource/pretendard/300.css'; +import '@fontsource/pretendard/400.css'; +import '@fontsource/pretendard/500.css'; +import '@fontsource/pretendard/600.css'; +import '@fontsource/pretendard/700.css'; +import '@fontsource/pretendard/800.css'; +import '@fontsource/pretendard/900.css'; const preview: Preview = { parameters: { From 39206d1d9a086c2a79a719535777b4accf240544 Mon Sep 17 00:00:00 2001 From: effozen Date: Tue, 12 Nov 2024 09:54:38 +0900 Subject: [PATCH 10/11] =?UTF-8?q?[FE][Chore]=20#108=20:=20react-icons=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20react-router-dom=20dependency?= =?UTF-8?q?=20=EC=9C=84=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package.json | 5 +++-- pnpm-lock.yaml | 12 ++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 9551d54a..829f880a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,7 +21,9 @@ "dependencies": { "@fontsource/pretendard": "^5.1.0", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-icons": "^5.3.0", + "react-router-dom": "^6.28.0" }, "devDependencies": { "@chromatic-com/storybook": "^3.2.2", @@ -47,7 +49,6 @@ "eslint-plugin-storybook": "^0.11.0", "globals": "^15.11.0", "postcss": "^8.4.47", - "react-router-dom": "^6.28.0", "storybook": "^8.4.2", "tailwindcss": "^3.4.14", "typescript": "~5.6.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cdb29a94..7b7f3249 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -187,6 +187,9 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-icons: + specifier: ^5.3.0 + version: 5.3.0(react@18.3.1) devDependencies: '@chromatic-com/storybook': specifier: ^3.2.2 @@ -6173,6 +6176,11 @@ packages: peerDependencies: react: ^16.6.0 || ^17.0.0 || ^18.0.0 + react-icons@5.3.0: + resolution: {integrity: sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==} + peerDependencies: + react: '*' + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -15054,6 +15062,10 @@ snapshots: react-fast-compare: 3.2.2 shallowequal: 1.1.0 + react-icons@5.3.0(react@18.3.1): + dependencies: + react: 18.3.1 + react-is@16.13.1: {} react-is@17.0.2: {} From cf4d640ddd3d913b1a23fd22ae3b1a46a313801d Mon Sep 17 00:00:00 2001 From: effozen Date: Tue, 12 Nov 2024 13:30:02 +0900 Subject: [PATCH 11/11] =?UTF-8?q?[FE][Chore]=20:=20Tailwindcss=20prettier?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- pnpm-lock.yaml | 68 +++++++++++++++++++++++++++++++++++++++++++-- prettier.config.mjs | 2 ++ 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 1bbfecc7..5b2e16a0 100644 --- a/package.json +++ b/package.json @@ -55,12 +55,12 @@ "jsdoc": "^4.0.4", "lint-staged": "^15.2.10", "prettier": "^3.3.3", + "prettier-plugin-tailwindcss": "^0.6.8", "typedoc": "^0.26.11", "typescript": "~5.6.2", "typescript-eslint": "^8.11.0", "vitest": "^2.1.4" }, - "lint-staged": { "**/*.{js,jsx,ts,tsx}": [ "eslint --fix", @@ -68,5 +68,4 @@ ], "!docs/**/*.{js,jsx,ts,tsx}": "eslint --fix" } - } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b80ae4c4..0b4d00a8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -90,6 +90,9 @@ importers: prettier: specifier: ^3.3.3 version: 3.3.3 + prettier-plugin-tailwindcss: + specifier: ^0.6.8 + version: 0.6.8(prettier@3.3.3) typedoc: specifier: ^0.26.11 version: 0.26.11(typescript@5.6.3) @@ -202,6 +205,9 @@ importers: react-icons: specifier: ^5.3.0 version: 5.3.0(react@18.3.1) + react-router-dom: + specifier: ^6.28.0 + version: 6.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) devDependencies: '@chromatic-com/storybook': specifier: ^3.2.2 @@ -272,9 +278,6 @@ importers: postcss: specifier: ^8.4.47 version: 8.4.47 - react-router-dom: - specifier: ^6.28.0 - version: 6.28.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) storybook: specifier: ^8.4.2 version: 8.4.2(prettier@3.3.3) @@ -6182,6 +6185,61 @@ packages: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} + prettier-plugin-tailwindcss@0.6.8: + resolution: {integrity: sha512-dGu3kdm7SXPkiW4nzeWKCl3uoImdd5CTZEJGxyypEPL37Wj0HT2pLqjrvSei1nTeuQfO4PUfjeW5cTUNRLZ4sA==} + engines: {node: '>=14.21.3'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@trivago/prettier-plugin-sort-imports': '*' + '@zackad/prettier-plugin-twig-melody': '*' + prettier: ^3.0 + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-import-sort: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-sort-imports: '*' + prettier-plugin-style-order: '*' + prettier-plugin-svelte: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + '@zackad/prettier-plugin-twig-melody': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-import-sort: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-multiline-arrays: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-sort-imports: + optional: true + prettier-plugin-style-order: + optional: true + prettier-plugin-svelte: + optional: true + prettier@3.3.3: resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} engines: {node: '>=14'} @@ -15223,6 +15281,10 @@ snapshots: dependencies: fast-diff: 1.3.0 + prettier-plugin-tailwindcss@0.6.8(prettier@3.3.3): + dependencies: + prettier: 3.3.3 + prettier@3.3.3: {} pretty-error@4.0.0: diff --git a/prettier.config.mjs b/prettier.config.mjs index ccdd8bf9..6dd384c5 100644 --- a/prettier.config.mjs +++ b/prettier.config.mjs @@ -64,4 +64,6 @@ export default { // JSON에서는 두 개의 스페이스를 사용하여 들여쓰기를 맞춥니다. jsonIndent: 2, + + plugins: ['prettier-plugin-tailwindcss'], };