diff --git a/config/custom-environment-variables.js b/config/custom-environment-variables.js index 6942117e6..8e3ec2427 100644 --- a/config/custom-environment-variables.js +++ b/config/custom-environment-variables.js @@ -44,6 +44,8 @@ module.exports = { githubAccessToken: "GITHUB_PERSONAL_ACCESS_TOKEN", + googleGenerativeAiApiKey: "GOOGLE_GENERATIVE_AI_API_KEY", + firestore: "FIRESTORE_CONFIG", services: { diff --git a/config/default.js b/config/default.js index df95c1188..c68313d85 100644 --- a/config/default.js +++ b/config/default.js @@ -138,6 +138,8 @@ module.exports = { }, githubAccessToken: "GITHUB_PERSONAL_ACCESS_TOKEN", + googleGenerativeAiApiKey: "", + externalServices: { EXTERNAL_SERVICE_PUBLIC_KEY: "EXTERNAL_SERVICE_PUBLIC_KEY", }, diff --git a/constants/application.ts b/constants/application.ts index 1c830ffa2..f8038dc6a 100644 --- a/constants/application.ts +++ b/constants/application.ts @@ -4,4 +4,39 @@ const API_RESPONSE_MESSAGES = { APPLICATION_RETURN_SUCCESS: "Applications returned successfully", }; -module.exports = { APPLICATION_STATUS_TYPES, API_RESPONSE_MESSAGES }; +const APPLICATION_REVIEW_SYSTEM_PROMPT = `You are an expert application reviewer for a developer community. Your task is to analyze applications and provide structured, fair, and constructive feedback. + +Evaluate each application based on the following criteria: + +1. **Honesty**: Assess the authenticity and genuineness of the applicant's responses. Look for signs of sincerity, personal experiences, and genuine interest rather than generic or copied content. + +2. **Curiosity**: Evaluate the applicant's intellectual curiosity, willingness to learn, and engagement with technology and the community. Look for evidence of self-directed learning, questions asked, and exploration of new concepts. + +3. **Hard Work**: Assess the applicant's work ethic, dedication, and commitment. Look for examples of perseverance, completion of projects, overcoming challenges, and consistent effort. + +4. **Motivation**: Evaluate the applicant's drive, goals, and reasons for joining. Assess whether their motivation aligns with the community's values and whether they demonstrate clear purpose and direction. + +5. **Sloppiness/AI Detection**: Identify signs of carelessness, lack of attention to detail, or potential use of AI-generated content. Look for: + - Generic or templated responses + - Lack of personal details or specific examples + - Inconsistencies in writing style + - Overly polished or formulaic language that suggests AI assistance + - Missing or incomplete information + +6. **Grammar and Language**: Assess the quality of written communication. Note: + - Spelling and grammar errors + - Call out any attempt of profanity as one of negatives + - Clarity and coherence of writing + - Professional communication skills + - Attention to detail in written responses + +Provide a comprehensive review that includes: +- A numerical score (0-10) reflecting overall assessment +- A brief message summarizing your evaluation +- A detailed review explaining your assessment across all criteria +- Specific strengths identified in the application +- Constructive improvement suggestions + +Be fair, objective, and supportive in your feedback.`; + +module.exports = { APPLICATION_STATUS_TYPES, API_RESPONSE_MESSAGES, APPLICATION_REVIEW_SYSTEM_PROMPT }; diff --git a/controllers/applications.ts b/controllers/applications.ts index 74a973b96..db7945278 100644 --- a/controllers/applications.ts +++ b/controllers/applications.ts @@ -3,9 +3,15 @@ const { logType } = require("../constants/logs"); import { CustomRequest, CustomResponse } from "../types/global"; const { INTERNAL_SERVER_ERROR } = require("../constants/errorMessages"); const ApplicationModel = require("../models/applications"); -const { API_RESPONSE_MESSAGES } = require("../constants/application"); +const { API_RESPONSE_MESSAGES, APPLICATION_REVIEW_SYSTEM_PROMPT } = require("../constants/application"); const { getUserApplicationObject } = require("../utils/application"); const admin = require("firebase-admin"); +const logger = require("../utils/logger"); +const config = require("config"); +const { generateObject } = require("ai"); +const { google } = require("@ai-sdk/google"); +const joiToJsonSchema = require("joi-to-json-schema"); +const { applicationReviewSchema } = require("../middlewares/validators/application"); const getAllOrUserApplication = async (req: CustomRequest, res: CustomResponse): Promise => { try { @@ -149,9 +155,93 @@ const getApplicationById = async (req: CustomRequest, res: CustomResponse) => { } }; +const generateReview = async ( + introduction: string, + whyRds: string, + forFun: string, + funFact: string +): Promise => { + try { + const apiKey = config.get("googleGenerativeAiApiKey"); + if (!apiKey) { + throw new Error("GOOGLE_GENERATIVE_AI_API_KEY is not configured"); + } + + const jsonSchema = joiToJsonSchema(applicationReviewSchema); + const userPrompt = `Please review the following application: + Introduction: + ${introduction} + + Why RDS: + ${whyRds} + + Hobbies/Interests (forFun): + ${forFun} + + Fun Fact: + ${funFact} + + Provide a comprehensive review based on the evaluation criteria.`; + + const { object } = await generateObject({ + model: google("gemini-2.5-flash-lite", { + apiKey: apiKey, + }), + schema: jsonSchema, + system: APPLICATION_REVIEW_SYSTEM_PROMPT, + prompt: userPrompt, + }); + + const { error, value } = applicationReviewSchema.validate(object); + + if (error) { + throw new Error(`AI output validation failed: ${error.details[0].message}`); + } + + return value; + } catch (err) { + logger.error(`Error in generating review: ${err}`); + throw err; + } +}; + +const reviewApplication = async (req: CustomRequest, res: CustomResponse) => { + try { + const { applicationId } = req.body; + const application = await ApplicationModel.getApplicationById(applicationId); + + if (application.notFound) { + return res.boom.notFound("Application not found"); + } + + const { introduction, whyRds, forFun, funFact } = application.intro; + + if (!introduction || !whyRds || !forFun || !funFact) { + return res.boom.badRequest("Application is missing required fields for review"); + } + + const review = await generateReview(introduction, whyRds, forFun, funFact); + + return res.json({ + message: "Application review generated successfully", + review, + }); + } catch (err) { + logger.error(`Error in reviewing application: ${err}`); + if (err.message && err.message.includes("not found")) { + return res.boom.notFound(err.message); + } + if (err.message && err.message.includes("missing required")) { + return res.boom.badRequest(err.message); + } + return res.boom.badImplementation(INTERNAL_SERVER_ERROR); + } +}; + module.exports = { getAllOrUserApplication, addApplication, updateApplication, getApplicationById, + reviewApplication, }; diff --git a/middlewares/validators/application.ts b/middlewares/validators/application.ts index c1324b8ba..cfbe5b11b 100644 --- a/middlewares/validators/application.ts +++ b/middlewares/validators/application.ts @@ -90,8 +90,38 @@ const validateApplicationQueryParam = async (req: CustomRequest, res: CustomResp } }; +const applicationReviewSchema = joi + .object() + .strict() + .keys({ + score: joi.number().required(), + message: joi.string().required(), + detailedReview: joi.string().required(), + strength: joi.array().items(joi.string()).required(), + improvement: joi.array().items(joi.string()).required(), + }); + +const validateReviewApplicationRequest = async (req: CustomRequest, res: CustomResponse, next: NextFunction) => { + const schema = joi + .object() + .strict() + .keys({ + applicationId: joi.string().required(), + }); + + try { + await schema.validateAsync(req.body); + next(); + } catch (error) { + logger.error(`Error in validating review application request: ${error}`); + res.boom.badRequest(error.details[0].message); + } +}; + module.exports = { validateApplicationData, validateApplicationUpdateData, validateApplicationQueryParam, + applicationReviewSchema, + validateReviewApplicationRequest, }; diff --git a/package.json b/package.json index 1280b2ae9..147ff0978 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,10 @@ "tdd:watch": "sh scripts/tests/tdd.sh" }, "dependencies": { + "@ai-sdk/google": "^2.0.42", "@aws-sdk/client-identitystore": "^3.665.0", "@types/nodemailer": "^6.4.15", + "ai": "^5.0.100", "axios": "1.7.4", "cloudinary": "2.0.3", "config": "3.3.7", @@ -31,6 +33,7 @@ "helmet": "7.1.0", "http-errors": "~2.0.0", "joi": "17.12.2", + "joi-to-json-schema": "^5.1.0", "jsdoc": "4.0.2", "jsonwebtoken": "^8.5.1", "morgan": "1.10.0", @@ -42,7 +45,8 @@ "passport-github2": "0.1.12", "passport-google-oauth20": "^2.0.0", "rate-limiter-flexible": "5.0.3", - "winston": "3.13.0" + "winston": "3.13.0", + "zod": "^4.1.12" }, "devDependencies": { "@types/chai": "4.3.16", diff --git a/routes/applications.ts b/routes/applications.ts index 7c687e78f..57213d7ce 100644 --- a/routes/applications.ts +++ b/routes/applications.ts @@ -24,5 +24,12 @@ router.patch( applicationValidator.validateApplicationUpdateData, applications.updateApplication ); +router.post( + "/review-application", + // authenticate, + // authorizeRoles([SUPERUSER]), + applicationValidator.validateReviewApplicationRequest, + applications.reviewApplication +); module.exports = router; diff --git a/yarn.lock b/yarn.lock index 027e73056..c2b44c2fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,39 @@ # yarn lockfile v1 +"@ai-sdk/gateway@2.0.14": + version "2.0.14" + resolved "https://registry.yarnpkg.com/@ai-sdk/gateway/-/gateway-2.0.14.tgz#18b3ae4cd1bb3e05267ccfeecc847da4b430534b" + integrity sha512-QU+ZVizSXN/V5uWgwapXrCLvkUEmmJeojAbikMH4gLgbeQF3oRugcQm3D8X9B+Rnestbz5cevNap7vKyJT/jfA== + dependencies: + "@ai-sdk/provider" "2.0.0" + "@ai-sdk/provider-utils" "3.0.17" + "@vercel/oidc" "3.0.5" + +"@ai-sdk/google@^2.0.42": + version "2.0.42" + resolved "https://registry.yarnpkg.com/@ai-sdk/google/-/google-2.0.42.tgz#9468a96539ec0560ca52d315d17621bced911bad" + integrity sha512-Jdn+3TZm4iIt62CUjjUoIOshqFIXyzNmUDfkSVV4FcjlSo5+AuhzI1KC7QiNHlqPNejzR6NLIqGJx96VAES34g== + dependencies: + "@ai-sdk/provider" "2.0.0" + "@ai-sdk/provider-utils" "3.0.17" + +"@ai-sdk/provider-utils@3.0.17": + version "3.0.17" + resolved "https://registry.yarnpkg.com/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz#2f3d0be398d3f165efe8dd252b63aea6ac3896d1" + integrity sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw== + dependencies: + "@ai-sdk/provider" "2.0.0" + "@standard-schema/spec" "^1.0.0" + eventsource-parser "^3.0.6" + +"@ai-sdk/provider@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@ai-sdk/provider/-/provider-2.0.0.tgz#b853c739d523b33675bc74b6c506b2c690bc602b" + integrity sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA== + dependencies: + json-schema "^0.4.0" + "@ampproject/remapping@^2.2.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" @@ -1133,7 +1166,7 @@ dependencies: semver "^7.3.5" -"@opentelemetry/api@^1.6.0": +"@opentelemetry/api@1.9.0", "@opentelemetry/api@^1.6.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== @@ -1677,6 +1710,11 @@ "@smithy/util-buffer-from" "^3.0.0" tslib "^2.6.2" +"@standard-schema/spec@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.0.0.tgz#f193b73dc316c4170f2e82a881da0f550d551b9c" + integrity sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA== + "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" @@ -2075,6 +2113,11 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@vercel/oidc@3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@vercel/oidc/-/oidc-3.0.5.tgz#bd8db7ee777255c686443413492db4d98ef49657" + integrity sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw== + abbrev@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" @@ -2139,6 +2182,16 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" +ai@^5.0.100: + version "5.0.100" + resolved "https://registry.yarnpkg.com/ai/-/ai-5.0.100.tgz#fb2572cd65c739b44dc5e5b4516632c00b949c36" + integrity sha512-+ANP4EJomTcUKdEF3UpVAWEl6DGn+ozDLxVZKXmTV7NRfyEC2cLYcKwoU4o3sKJpqXMUKNzpFlJFBKOcsKdMyg== + dependencies: + "@ai-sdk/gateway" "2.0.14" + "@ai-sdk/provider" "2.0.0" + "@ai-sdk/provider-utils" "3.0.17" + "@opentelemetry/api" "1.9.0" + ajv-formats@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" @@ -3998,6 +4051,11 @@ events-listener@^1.1.0: resolved "https://registry.yarnpkg.com/events-listener/-/events-listener-1.1.0.tgz#dd49b4628480eba58fde31b870ee346b3990b349" integrity sha512-Kd3EgYfODHueq6GzVfs/VUolh2EgJsS8hkO3KpnDrxVjU3eq63eXM2ujXkhPP+OkeUOhL8CxdfZbQXzryb5C4g== +eventsource-parser@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.6.tgz#292e165e34cacbc936c3c92719ef326d4aeb4e90" + integrity sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg== + exegesis-express@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/exegesis-express/-/exegesis-express-4.0.0.tgz#f5f8486f6f0d81739e8e27ce75ce0f61ba3f3578" @@ -5339,6 +5397,13 @@ is-core-module@^2.13.0, is-core-module@^2.13.1: dependencies: hasown "^2.0.0" +is-core-module@^2.16.1: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + is-data-view@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" @@ -5693,6 +5758,11 @@ jju@^1.1.0: resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA== +joi-to-json-schema@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/joi-to-json-schema/-/joi-to-json-schema-5.1.0.tgz#5cf2aa05c8d63a0a0a480661b7c4f3c65fa5707d" + integrity sha512-suSQS0zGdkIgnxKuc9huX+J/+RGtADNmp7Uh6HdCpkguN0Lxhy+7MT7d/BtQW4IBaYv/5AeR4uADN3m6BijQtw== + joi@17.12.2: version "17.12.2" resolved "https://registry.yarnpkg.com/joi/-/joi-17.12.2.tgz#283a664dabb80c7e52943c557aab82faea09f521" @@ -5831,6 +5901,11 @@ json-schema-traverse@^1.0.0: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== +json-schema@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" + integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -7720,11 +7795,11 @@ resolve-from@^5.0.0: integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve@^1.0.0, resolve@^1.10.1, resolve@^1.22.1, resolve@^1.22.4: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + version "1.22.11" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" + integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== dependencies: - is-core-module "^2.13.0" + is-core-module "^2.16.1" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -8188,16 +8263,7 @@ streamsearch@^1.1.0: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8262,14 +8328,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -9051,7 +9110,7 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -9069,15 +9128,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -9251,3 +9301,8 @@ zip-stream@^4.1.0: archiver-utils "^3.0.4" compress-commons "^4.1.2" readable-stream "^3.6.0" + +zod@^4.1.12: + version "4.1.12" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.1.12.tgz#64f1ea53d00eab91853195653b5af9eee68970f0" + integrity sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==