-
Notifications
You must be signed in to change notification settings - Fork 3
feat: 백그라운드 스크립트 API 핸들러 리팩토링 및 추가 #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| export const handleCosmeticDataFetch = async ( | ||
| message: any, | ||
| sender: any, | ||
| sendResponse: any, | ||
| ) => { | ||
| const { productId, html } = message.payload; | ||
|
|
||
| fetch("https://voim.store/api/v1/cosmetic", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ productId, html }), | ||
| }) | ||
| .then(async (res) => { | ||
| const json = await res.json(); | ||
| return json; | ||
| }) | ||
| .then((data) => { | ||
| const raw = data?.data; | ||
|
|
||
| if (!raw || typeof raw !== "object") { | ||
| console.warn("[voim][background] data.data 형식 이상함:", raw); | ||
| sendResponse({ | ||
| type: "COSMETIC_DATA_ERROR", | ||
| error: "API 응답 형식 오류", | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| sendResponse({ | ||
| type: "COSMETIC_DATA_RESPONSE", | ||
| data: raw, | ||
| }); | ||
|
|
||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "COSMETIC_DATA_RESPONSE", | ||
| data: raw, | ||
| }); | ||
| } | ||
| }) | ||
| .catch((err) => { | ||
| console.error("[voim][background] COSMETIC 요청 실패:", err); | ||
| sendResponse({ | ||
| type: "COSMETIC_DATA_ERROR", | ||
| error: err.message, | ||
| }); | ||
|
|
||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "COSMETIC_DATA_ERROR", | ||
| error: err.message, | ||
| }); | ||
| } | ||
| }); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,56 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const handleFoodDataFetch = async ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: any, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sender: any, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sendResponse: any, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const activeTab = tabs[0]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!activeTab?.url) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sendResponse({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: 400, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error: "상품 페이지를 찾을 수 없습니다.", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const productId = activeTab.url.match(/vp\/products\/(\d+)/)?.[1]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!productId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sendResponse({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: 400, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error: "상품 ID를 찾을 수 없습니다.", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const payload = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...message.payload, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| productId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fetch("https://voim.store/api/v1/products/foods", { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: "POST", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { "Content-Type": "application/json" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| body: JSON.stringify(payload), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }).then(async (res) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const text = await res.text(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const json = JSON.parse(text); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (res.ok) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sendResponse({ status: 200, data: json }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sendResponse({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: res.status, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error: json?.message ?? "에러 발생", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error("[voim] JSON 파싱 실패", text); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sendResponse({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status: res.status, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error: "JSON 파싱 실패", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네트워크 오류 미처리로 응답 누락 위험 fetch("https://voim.store/api/v1/products/foods", { … })
.then(async (res) => { … })
+ .catch((error) => {
+ console.error("[voim] FOOD DATA 네트워크 오류:", error);
+ sendResponse({ status: 500, error: "네트워크 오류" });
+ });또한 📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Check: lint-and-test[warning] 47-47: 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| export const handleHealthDataFetch = async ( | ||
| message: any, | ||
| sender: any, | ||
| sendResponse: any, | ||
| ) => { | ||
| const { productId, title, html, birthYear, gender, allergies } = | ||
| message.payload; | ||
|
|
||
| fetch("https://voim.store/api/v1/health-food/keywords", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| productId, | ||
| title, | ||
| html, | ||
| birthYear, | ||
| gender, | ||
| allergies, | ||
| }), | ||
| }) | ||
| .then((res) => res.json()) | ||
| .then((data) => { | ||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "HEALTH_DATA_RESPONSE", | ||
| data: data.data, | ||
| }); | ||
| } | ||
| sendResponse({ type: "HEALTH_DATA_RESPONSE", data: data.data }); | ||
| }) | ||
| .catch((err) => { | ||
| console.error("HEALTH 요청 실패:", err); | ||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "HEALTH_DATA_ERROR", | ||
| error: err.message, | ||
| }); | ||
| } | ||
| sendResponse({ type: "HEALTH_DATA_ERROR", error: err.message }); | ||
| }); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,42 @@ | ||||||||||||||||||||||||
| import { logger } from "@src/utils/logger"; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| export const handleImageAnalysisFetch = async ( | ||||||||||||||||||||||||
| message: any, | ||||||||||||||||||||||||
| sender: any, | ||||||||||||||||||||||||
| sendResponse: any, | ||||||||||||||||||||||||
| ) => { | ||||||||||||||||||||||||
|
Comment on lines
+3
to
+7
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 타입 선언 및 -export const handleImageAnalysisFetch = async (
- message: any,
- sender: any,
- sendResponse: any,
-) => {
+interface ImageAnalysisMessage { payload: { url?: string } }
+export const handleImageAnalysisFetch = (
+ message: ImageAnalysisMessage,
+ sender: chrome.runtime.MessageSender,
+ sendResponse: (response: unknown) => void,
+) => {
📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| const imageUrl = message.payload?.url; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| fetch("https://voim.store/api/v1/image-analysis", { | ||||||||||||||||||||||||
| method: "POST", | ||||||||||||||||||||||||
| headers: { "Content-Type": "application/json" }, | ||||||||||||||||||||||||
| body: JSON.stringify({ url: imageUrl }), | ||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||
|
Comment on lines
+8
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| .then((res) => res.json()) | ||||||||||||||||||||||||
|
Comment on lines
+10
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| .then((data) => { | ||||||||||||||||||||||||
| logger.debug("이미지 분석 API 응답:", data); | ||||||||||||||||||||||||
| if (sender.tab?.id) { | ||||||||||||||||||||||||
| chrome.tabs.sendMessage(sender.tab.id, { | ||||||||||||||||||||||||
| type: "IMAGE_ANALYSIS_RESPONSE", | ||||||||||||||||||||||||
| data: data.data, | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| sendResponse({ | ||||||||||||||||||||||||
| type: "IMAGE_ANALYSIS_RESPONSE", | ||||||||||||||||||||||||
| data: data.data, | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||
| .catch((err) => { | ||||||||||||||||||||||||
| console.error("이미지 분석 에러:", err); | ||||||||||||||||||||||||
| if (sender.tab?.id) { | ||||||||||||||||||||||||
| chrome.tabs.sendMessage(sender.tab.id, { | ||||||||||||||||||||||||
| type: "IMAGE_ANALYSIS_ERROR", | ||||||||||||||||||||||||
| error: err.message, | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| sendResponse({ | ||||||||||||||||||||||||
| type: "IMAGE_ANALYSIS_ERROR", | ||||||||||||||||||||||||
| error: err.message, | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| export { handleFoodDataFetch } from "./foodHandler"; | ||
| export { handleImageAnalysisFetch } from "./imageAnalysisHandler"; | ||
| export { handleOutlineInfoFetch } from "./outlineInfoHandler"; | ||
| export { handleCosmeticDataFetch } from "./cosmeticHandler"; | ||
| export { handleReviewSummaryFetch } from "./reviewSummaryHandler"; | ||
| export { handleHealthDataFetch } from "./healthDataHandler"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| export const handleOutlineInfoFetch = async ( | ||
| message: any, | ||
| sender: any, | ||
| sendResponse: any, | ||
| ) => { | ||
| const { outline, html } = message.payload; | ||
|
Comment on lines
+1
to
+6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 타입 및 🤖 Prompt for AI Agents |
||
|
|
||
| chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | ||
| const activeTab = tabs[0]; | ||
| if (!activeTab?.url) { | ||
| sendResponse({ | ||
| type: "OUTLINE_INFO_ERROR", | ||
| error: "상품 페이지를 찾을 수 없습니다.", | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| const productId = activeTab.url.match(/vp\/products\/(\d+)/)?.[1]; | ||
| if (!productId) { | ||
| sendResponse({ | ||
| type: "OUTLINE_INFO_ERROR", | ||
| error: "상품 ID를 찾을 수 없습니다.", | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| fetch(`https://voim.store/api/v1/product-detail/${outline}`, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ html, productId }), | ||
| }) | ||
| .then((res) => res.json()) | ||
| .then((data) => { | ||
|
Comment on lines
+27
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion
🤖 Prompt for AI Agents |
||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "OUTLINE_INFO_RESPONSE", | ||
| data: data.data, | ||
| }); | ||
| } | ||
| sendResponse({ | ||
| type: "OUTLINE_INFO_RESPONSE", | ||
| data: data.data, | ||
| }); | ||
| }) | ||
| .catch((err) => { | ||
| console.error("OUTLINE INFO 오류:", err); | ||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "OUTLINE_INFO_ERROR", | ||
| error: err.message, | ||
| }); | ||
| } | ||
| sendResponse({ | ||
| type: "OUTLINE_INFO_ERROR", | ||
| error: err.message, | ||
| }); | ||
| }); | ||
| }); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| export const handleReviewSummaryFetch = async ( | ||
| message: any, | ||
| sender: any, | ||
| sendResponse: any, | ||
| ) => { | ||
| const { productId, reviewRating, reviews } = message.payload; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
🤖 Prompt for AI Agents |
||
|
|
||
| fetch("https://voim.store/api/v1/review/summary", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ productId, reviewRating, reviews }), | ||
| }) | ||
| .then(async (res) => { | ||
| if (!res.ok) { | ||
| const errorData = await res.json().catch(() => ({})); | ||
| throw new Error( | ||
| errorData.message || `HTTP error! status: ${res.status}`, | ||
| ); | ||
| } | ||
| return res.json(); | ||
| }) | ||
| .then((data) => { | ||
| if (!data.data) { | ||
| throw new Error("서버 응답에 데이터가 없습니다."); | ||
| } | ||
|
|
||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "REVIEW_SUMMARY_RESPONSE", | ||
| data: data.data, | ||
| }); | ||
| } | ||
| sendResponse({ | ||
| type: "REVIEW_SUMMARY_RESPONSE", | ||
| data: data.data, | ||
| }); | ||
| }) | ||
| .catch((err) => { | ||
| console.error("[voim] REVIEW SUMMARY 오류:", err); | ||
| const errorMessage = | ||
| err.message || "리뷰 요약 처리 중 오류가 발생했습니다"; | ||
|
|
||
| if (sender.tab?.id) { | ||
| chrome.tabs.sendMessage(sender.tab.id, { | ||
| type: "REVIEW_SUMMARY_ERROR", | ||
| error: errorMessage, | ||
| }); | ||
| } | ||
| sendResponse({ | ||
| type: "REVIEW_SUMMARY_ERROR", | ||
| error: errorMessage, | ||
| }); | ||
| }); | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
타입 미지정 및
async불필요any사용과async키워드 문제는 다른 핸들러와 동일합니다. 함수 본문이await를 사용하지 않으므로async제거 후return fetch(...);권장드립니다.🧰 Tools
🪛 GitHub Check: lint-and-test
[warning] 4-4:
Unexpected any. Specify a different type
[warning] 3-3:
Unexpected any. Specify a different type
[warning] 2-2:
Unexpected any. Specify a different type
🤖 Prompt for AI Agents