diff --git a/package-lock.json b/package-lock.json
index 5a2575d73..8dea7dbc3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -33,8 +33,7 @@
"vscode-material-icons": "^0.1.1",
"yaml": "^2.5.1",
"yargs": "^17.7.2",
- "zod": "^3.23.8",
- "zod-validation-error": "^3.4.0"
+ "zod": "^4.0.5"
},
"devDependencies": {
"@beaussan/nx-knip": "^0.0.5-15",
@@ -105,7 +104,7 @@
"verdaccio": "^5.32.2",
"vite": "^5.4.8",
"vitest": "1.3.1",
- "zod2md": "^0.1.7"
+ "zod2md": "^0.2.4"
},
"engines": {
"node": ">=22.14"
@@ -3011,9 +3010,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz",
- "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz",
+ "integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==",
"cpu": [
"arm64"
],
@@ -3074,6 +3073,22 @@
"node": ">=18"
}
},
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz",
+ "integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@esbuild/sunos-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
@@ -3123,9 +3138,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz",
- "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz",
+ "integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==",
"cpu": [
"x64"
],
@@ -11600,6 +11615,15 @@
"devtools-protocol": "*"
}
},
+ "node_modules/chromium-bidi/node_modules/zod": {
+ "version": "3.23.8",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
+ "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
"node_modules/chromium/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -13923,9 +13947,9 @@
}
},
"node_modules/esbuild": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
- "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz",
+ "integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
@@ -13935,37 +13959,38 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.25.2",
- "@esbuild/android-arm": "0.25.2",
- "@esbuild/android-arm64": "0.25.2",
- "@esbuild/android-x64": "0.25.2",
- "@esbuild/darwin-arm64": "0.25.2",
- "@esbuild/darwin-x64": "0.25.2",
- "@esbuild/freebsd-arm64": "0.25.2",
- "@esbuild/freebsd-x64": "0.25.2",
- "@esbuild/linux-arm": "0.25.2",
- "@esbuild/linux-arm64": "0.25.2",
- "@esbuild/linux-ia32": "0.25.2",
- "@esbuild/linux-loong64": "0.25.2",
- "@esbuild/linux-mips64el": "0.25.2",
- "@esbuild/linux-ppc64": "0.25.2",
- "@esbuild/linux-riscv64": "0.25.2",
- "@esbuild/linux-s390x": "0.25.2",
- "@esbuild/linux-x64": "0.25.2",
- "@esbuild/netbsd-arm64": "0.25.2",
- "@esbuild/netbsd-x64": "0.25.2",
- "@esbuild/openbsd-arm64": "0.25.2",
- "@esbuild/openbsd-x64": "0.25.2",
- "@esbuild/sunos-x64": "0.25.2",
- "@esbuild/win32-arm64": "0.25.2",
- "@esbuild/win32-ia32": "0.25.2",
- "@esbuild/win32-x64": "0.25.2"
+ "@esbuild/aix-ppc64": "0.25.6",
+ "@esbuild/android-arm": "0.25.6",
+ "@esbuild/android-arm64": "0.25.6",
+ "@esbuild/android-x64": "0.25.6",
+ "@esbuild/darwin-arm64": "0.25.6",
+ "@esbuild/darwin-x64": "0.25.6",
+ "@esbuild/freebsd-arm64": "0.25.6",
+ "@esbuild/freebsd-x64": "0.25.6",
+ "@esbuild/linux-arm": "0.25.6",
+ "@esbuild/linux-arm64": "0.25.6",
+ "@esbuild/linux-ia32": "0.25.6",
+ "@esbuild/linux-loong64": "0.25.6",
+ "@esbuild/linux-mips64el": "0.25.6",
+ "@esbuild/linux-ppc64": "0.25.6",
+ "@esbuild/linux-riscv64": "0.25.6",
+ "@esbuild/linux-s390x": "0.25.6",
+ "@esbuild/linux-x64": "0.25.6",
+ "@esbuild/netbsd-arm64": "0.25.6",
+ "@esbuild/netbsd-x64": "0.25.6",
+ "@esbuild/openbsd-arm64": "0.25.6",
+ "@esbuild/openbsd-x64": "0.25.6",
+ "@esbuild/openharmony-arm64": "0.25.6",
+ "@esbuild/sunos-x64": "0.25.6",
+ "@esbuild/win32-arm64": "0.25.6",
+ "@esbuild/win32-ia32": "0.25.6",
+ "@esbuild/win32-x64": "0.25.6"
}
},
"node_modules/esbuild/node_modules/@esbuild/aix-ppc64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
- "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz",
+ "integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==",
"cpu": [
"ppc64"
],
@@ -13979,9 +14004,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/android-arm": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz",
- "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.6.tgz",
+ "integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==",
"cpu": [
"arm"
],
@@ -13995,9 +14020,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/android-arm64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz",
- "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz",
+ "integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==",
"cpu": [
"arm64"
],
@@ -14011,9 +14036,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/android-x64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz",
- "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.6.tgz",
+ "integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==",
"cpu": [
"x64"
],
@@ -14027,9 +14052,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/darwin-arm64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz",
- "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz",
+ "integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==",
"cpu": [
"arm64"
],
@@ -14043,9 +14068,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/darwin-x64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz",
- "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz",
+ "integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==",
"cpu": [
"x64"
],
@@ -14059,9 +14084,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz",
- "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz",
+ "integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==",
"cpu": [
"arm64"
],
@@ -14075,9 +14100,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/freebsd-x64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz",
- "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz",
+ "integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==",
"cpu": [
"x64"
],
@@ -14091,9 +14116,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-arm": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz",
- "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz",
+ "integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==",
"cpu": [
"arm"
],
@@ -14107,9 +14132,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-arm64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz",
- "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz",
+ "integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==",
"cpu": [
"arm64"
],
@@ -14123,9 +14148,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-ia32": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz",
- "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz",
+ "integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==",
"cpu": [
"ia32"
],
@@ -14139,9 +14164,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-loong64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz",
- "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz",
+ "integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==",
"cpu": [
"loong64"
],
@@ -14155,9 +14180,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-mips64el": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz",
- "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz",
+ "integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==",
"cpu": [
"mips64el"
],
@@ -14171,9 +14196,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-ppc64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz",
- "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz",
+ "integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==",
"cpu": [
"ppc64"
],
@@ -14187,9 +14212,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-riscv64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz",
- "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz",
+ "integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==",
"cpu": [
"riscv64"
],
@@ -14203,9 +14228,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-s390x": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz",
- "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz",
+ "integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==",
"cpu": [
"s390x"
],
@@ -14219,9 +14244,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/linux-x64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz",
- "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz",
+ "integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==",
"cpu": [
"x64"
],
@@ -14235,9 +14260,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/netbsd-x64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz",
- "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz",
+ "integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==",
"cpu": [
"x64"
],
@@ -14251,9 +14276,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/openbsd-arm64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz",
- "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz",
+ "integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==",
"cpu": [
"arm64"
],
@@ -14267,9 +14292,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/openbsd-x64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz",
- "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz",
+ "integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==",
"cpu": [
"x64"
],
@@ -14283,9 +14308,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/sunos-x64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz",
- "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz",
+ "integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==",
"cpu": [
"x64"
],
@@ -14299,9 +14324,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/win32-arm64": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz",
- "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz",
+ "integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==",
"cpu": [
"arm64"
],
@@ -14315,9 +14340,9 @@
}
},
"node_modules/esbuild/node_modules/@esbuild/win32-ia32": {
- "version": "0.25.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz",
- "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==",
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz",
+ "integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==",
"cpu": [
"ia32"
],
@@ -20537,6 +20562,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/knip/node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
"node_modules/koa": {
"version": "2.15.3",
"resolved": "https://registry.npmjs.org/koa/-/koa-2.15.3.tgz",
@@ -28362,42 +28397,44 @@
}
},
"node_modules/zod": {
- "version": "3.23.8",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
- "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.5.tgz",
+ "integrity": "sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA==",
+ "license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zod-validation-error": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.4.0.tgz",
- "integrity": "sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==",
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.5.3.tgz",
+ "integrity": "sha512-OT5Y8lbUadqVZCsnyFaTQ4/O2mys4tj7PqhdbBCp7McPwvIEKfPtdA6QfPeFQK2/Rz5LgwmAXRJTugBNBi0btw==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
- "zod": "^3.18.0"
+ "zod": "^3.25.0 || ^4.0.0"
}
},
"node_modules/zod2md": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/zod2md/-/zod2md-0.1.7.tgz",
- "integrity": "sha512-a/qcON8dBmNpABt0O/8aKiD6bORuYf/d5cSlMq65VGuv7prhF0KnoUacr0V2Y/PMkM2cmd8oKhXPNv/UZqSRgg==",
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/zod2md/-/zod2md-0.2.4.tgz",
+ "integrity": "sha512-P+12EKfWquooGSlkZ2RatVX9O8rrF7BvM8vlsrohOgvFM2NeGIS0VZQbkGNGLRI8sp82KTxxWG7H7JhRZhzHFg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@commander-js/extra-typings": "^12.0.0",
- "bundle-require": "^4.0.2",
+ "bundle-require": "^5.1.0",
"commander": "^12.0.0",
- "esbuild": "^0.19.11"
+ "esbuild": "^0.25.4"
},
"bin": {
"zod2md": "dist/bin.js"
},
"peerDependencies": {
- "zod": "^3.22.0"
+ "zod": "^3.25.0 || ^4.0.0"
}
},
"node_modules/zod2md/node_modules/@commander-js/extra-typings": {
@@ -28409,396 +28446,6 @@
"commander": "~12.1.0"
}
},
- "node_modules/zod2md/node_modules/@esbuild/aix-ppc64": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
- "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/android-arm": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
- "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/android-arm64": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
- "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/android-x64": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
- "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/darwin-x64": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
- "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/freebsd-arm64": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
- "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/freebsd-x64": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
- "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/linux-arm": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
- "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/linux-arm64": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
- "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/linux-ia32": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
- "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/linux-loong64": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
- "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/linux-mips64el": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
- "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/linux-ppc64": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
- "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/linux-riscv64": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
- "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/linux-s390x": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
- "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/linux-x64": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
- "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/netbsd-x64": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
- "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/openbsd-x64": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
- "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/sunos-x64": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
- "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/win32-arm64": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
- "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/win32-ia32": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
- "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/@esbuild/win32-x64": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
- "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod2md/node_modules/bundle-require": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-4.2.1.tgz",
- "integrity": "sha512-7Q/6vkyYAwOmQNRw75x+4yRtZCZJXUDmHHlFdkiV0wgv/reNjtJwpu1jPJ0w2kbEpIM0uoKI3S4/f39dU7AjSA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "load-tsconfig": "^0.2.3"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "peerDependencies": {
- "esbuild": ">=0.17"
- }
- },
"node_modules/zod2md/node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
@@ -28807,45 +28454,6 @@
"engines": {
"node": ">=18"
}
- },
- "node_modules/zod2md/node_modules/esbuild": {
- "version": "0.19.12",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
- "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=12"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.19.12",
- "@esbuild/android-arm": "0.19.12",
- "@esbuild/android-arm64": "0.19.12",
- "@esbuild/android-x64": "0.19.12",
- "@esbuild/darwin-arm64": "0.19.12",
- "@esbuild/darwin-x64": "0.19.12",
- "@esbuild/freebsd-arm64": "0.19.12",
- "@esbuild/freebsd-x64": "0.19.12",
- "@esbuild/linux-arm": "0.19.12",
- "@esbuild/linux-arm64": "0.19.12",
- "@esbuild/linux-ia32": "0.19.12",
- "@esbuild/linux-loong64": "0.19.12",
- "@esbuild/linux-mips64el": "0.19.12",
- "@esbuild/linux-ppc64": "0.19.12",
- "@esbuild/linux-riscv64": "0.19.12",
- "@esbuild/linux-s390x": "0.19.12",
- "@esbuild/linux-x64": "0.19.12",
- "@esbuild/netbsd-x64": "0.19.12",
- "@esbuild/openbsd-x64": "0.19.12",
- "@esbuild/sunos-x64": "0.19.12",
- "@esbuild/win32-arm64": "0.19.12",
- "@esbuild/win32-ia32": "0.19.12",
- "@esbuild/win32-x64": "0.19.12"
- }
}
}
}
diff --git a/package.json b/package.json
index 4d85151ca..b4e776c66 100644
--- a/package.json
+++ b/package.json
@@ -46,8 +46,7 @@
"vscode-material-icons": "^0.1.1",
"yaml": "^2.5.1",
"yargs": "^17.7.2",
- "zod": "^3.23.8",
- "zod-validation-error": "^3.4.0"
+ "zod": "^4.0.5"
},
"devDependencies": {
"@beaussan/nx-knip": "^0.0.5-15",
@@ -118,7 +117,7 @@
"verdaccio": "^5.32.2",
"vite": "^5.4.8",
"vitest": "1.3.1",
- "zod2md": "^0.1.7"
+ "zod2md": "^0.2.4"
},
"optionalDependencies": {
"@esbuild/darwin-arm64": "^0.19.12",
diff --git a/packages/ci/package.json b/packages/ci/package.json
index ccd168dd9..de2c7dd14 100644
--- a/packages/ci/package.json
+++ b/packages/ci/package.json
@@ -31,6 +31,6 @@
"glob": "^11.0.1",
"simple-git": "^3.20.0",
"yaml": "^2.5.1",
- "zod": "^3.22.1"
+ "zod": "^4.0.5"
}
}
diff --git a/packages/ci/src/lib/cli/persist.unit.test.ts b/packages/ci/src/lib/cli/persist.unit.test.ts
index 222824134..874b78258 100644
--- a/packages/ci/src/lib/cli/persist.unit.test.ts
+++ b/packages/ci/src/lib/cli/persist.unit.test.ts
@@ -114,7 +114,7 @@ describe('parsePersistConfig', () => {
await expect(
parsePersistConfig({ persist: { format: ['json', 'html'] } }),
).rejects.toThrow(
- /^Invalid persist config - ZodError:.*Invalid enum value. Expected 'json' \| 'md', received 'html'/s,
+ /^Invalid persist config - ZodError:.*Invalid option: expected one of \\"json\\"\|\\"md\\"/s,
);
});
});
diff --git a/packages/cli/src/lib/yargs-cli.ts b/packages/cli/src/lib/yargs-cli.ts
index 989ca8d5d..8caafa30c 100644
--- a/packages/cli/src/lib/yargs-cli.ts
+++ b/packages/cli/src/lib/yargs-cli.ts
@@ -155,9 +155,9 @@ function validatePersistFormat(persist: PersistConfig) {
return true;
} catch {
throw new Error(
- `Invalid persist.format option. Valid options are: ${Object.values(
- formatSchema.Values,
- ).join(', ')}`,
+ `Invalid persist.format option. Valid options are: ${formatSchema.options.join(
+ ', ',
+ )}`,
);
}
}
diff --git a/packages/core/src/lib/implementation/execute-plugin.unit.test.ts b/packages/core/src/lib/implementation/execute-plugin.unit.test.ts
index c74a9e509..cc63bf82c 100644
--- a/packages/core/src/lib/implementation/execute-plugin.unit.test.ts
+++ b/packages/core/src/lib/implementation/execute-plugin.unit.test.ts
@@ -134,21 +134,11 @@ describe('executePlugins', () => {
] satisfies PluginConfig[],
{ progress: false },
),
- ).rejects.toThrow(`Executing 1 plugin failed.\n\nError: - Plugin ${bold(
- title,
- )} (${bold(slug)}) produced the following error:
- - Audit output is invalid: [
- {
- "validation": "regex",
- "code": "invalid_string",
- "message": "The slug has to follow the pattern [0-9a-z] followed by multiple optional groups of -[0-9a-z]. e.g. my-slug",
- "path": [
- 0,
- "slug"
- ]
- }
-]
-`);
+ ).rejects.toThrow(
+ `Executing 1 plugin failed.\n\nError: - Plugin ${bold(
+ title,
+ )} (${bold(slug)}) produced the following error:\n - Audit output is invalid`,
+ );
});
it('should throw for one failing plugin', async () => {
diff --git a/packages/core/src/lib/implementation/read-rc-file.int.test.ts b/packages/core/src/lib/implementation/read-rc-file.int.test.ts
index b903ab41e..dae34e77a 100644
--- a/packages/core/src/lib/implementation/read-rc-file.int.test.ts
+++ b/packages/core/src/lib/implementation/read-rc-file.int.test.ts
@@ -69,12 +69,12 @@ describe('readRcByPath', () => {
it('should throw if the configuration is empty', async () => {
await expect(
readRcByPath(path.join(configDirPath, 'code-pushup.empty.config.js')),
- ).rejects.toThrow(/invalid_type/);
+ ).rejects.toThrow('Invalid input');
});
it('should throw if the configuration is invalid', async () => {
await expect(
readRcByPath(path.join(configDirPath, 'code-pushup.invalid.config.ts')),
- ).rejects.toThrow(/refs are duplicates/);
+ ).rejects.toThrow('has duplicate references');
});
});
diff --git a/packages/models/docs/models-reference.md b/packages/models/docs/models-reference.md
index 7bdd9d232..599b04b25 100644
--- a/packages/models/docs/models-reference.md
+++ b/packages/models/docs/models-reference.md
@@ -5,7 +5,7 @@
_Union of the following possible types:_
- `string` (_min length: 1_)
-- _Object with properties:_
- `command`: `string` (_min length: 1_) - Generate artifact files
- `args`: `Array`
+- _Object with properties:_- **`command`** (\*): `string` (_min length: 1_) - Generate artifact files
- `args`: `Array`
## AuditDetails
@@ -13,11 +13,11 @@ Detailed information
_Object containing the following properties:_
-| Property | Description | Type |
-| :------- | :------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `issues` | List of findings | _Array of [Issue](#issue) items_ |
-| `table` | Table of related findings | _Object with properties:_- `title`: `string` - Display title for table
- `columns`: _Array of [TableAlignment](#tablealignment) items_
- `rows`: _Array of [TableRowPrimitive](#tablerowprimitive) items_
_or_ _Object with properties:_- `title`: `string` - Display title for table
- `columns`: _Array of [TableAlignment](#tablealignment) items_ _or_ _Array of [TableColumnObject](#tablecolumnobject) items_
- `rows`: _Array of [TableRowObject](#tablerowobject) items_
|
-| `trees` | Findings in tree structure | _Array of [Tree](#tree) items_ |
+| Property | Description | Type |
+| :------- | :------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `issues` | List of findings | _Array of [Issue](#issue) items_ |
+| `table` | Table of related findings | _Object with properties:_- `title`: `string` - Display title for table
- `columns`: _Array of [TableAlignment](#tablealignment) items_
- **`rows`** (\*): _Array of [TableRowPrimitive](#tablerowprimitive) items_
_or_ _Object with properties:_- `title`: `string` - Display title for table
- `columns`: _Array of [TableAlignment](#tablealignment) items_ _or_ _Array of [TableColumnObject](#tablecolumnobject) items_
- **`rows`** (\*): _Array of [TableRowObject](#tablerowobject) items_
|
+| `trees` | Findings in tree structure | _Array of [Tree](#tree) items_ |
_All properties are optional._
@@ -25,15 +25,15 @@ _All properties are optional._
_Object containing the following properties:_
-| Property | Description | Type |
-| :----------------------- | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **`slug`** (\*) | Unique ID (human-readable, URL-safe) | `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) |
-| **`title`** (\*) | Descriptive name | `string` (_max length: 256_) |
-| `docsUrl` | Documentation site | `string` (_url_) (_optional_) _or_ `''` |
-| **`scores`** (\*) | Score comparison | _Object with properties:_- `before`: `number` (_≥0, ≤1_) - Value between 0 and 1 (source commit)
- `after`: `number` (_≥0, ≤1_) - Value between 0 and 1 (target commit)
- `diff`: `number` (_≥-1, ≤1_) - Score change (`scores.after - scores.before`)
|
-| **`plugin`** (\*) | Plugin which defines it | _Object with properties:_- `slug`: `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) - Unique plugin slug within core config
- `title`: `string` (_max length: 256_) - Descriptive name
- `docsUrl`: `string` (_url_) (_optional_) _or_ `''` - Plugin documentation site
|
-| **`values`** (\*) | Audit `value` comparison | _Object with properties:_- `before`: `number` (_≥0_) - Raw numeric value (source commit)
- `after`: `number` (_≥0_) - Raw numeric value (target commit)
- `diff`: `number` - Value change (`values.after - values.before`)
|
-| **`displayValues`** (\*) | Audit `displayValue` comparison | _Object with properties:_- `before`: `string` - Formatted value (e.g. '0.9 s', '2.1 MB') (source commit)
- `after`: `string` - Formatted value (e.g. '0.9 s', '2.1 MB') (target commit)
|
+| Property | Description | Type |
+| :----------------------- | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`slug`** (\*) | Unique ID (human-readable, URL-safe) | `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) |
+| **`title`** (\*) | Descriptive name | `string` (_max length: 256_) |
+| `docsUrl` | Documentation site | `string` (_url_) (_optional_) _or_ `''` |
+| **`scores`** (\*) | Score comparison | _Object with properties:_- **`before`** (\*): `number` (_≥0, ≤1_) - Value between 0 and 1 (source commit)
- **`after`** (\*): `number` (_≥0, ≤1_) - Value between 0 and 1 (target commit)
- **`diff`** (\*): `number` (_≥-1, ≤1_) - Score change (`scores.after - scores.before`)
|
+| **`plugin`** (\*) | Plugin which defines it | _Object with properties:_- **`slug`** (\*): `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) - Unique plugin slug within core config
- **`title`** (\*): `string` (_max length: 256_) - Descriptive name
- `docsUrl`: `string` (_url_) (_optional_) _or_ `''` - Plugin documentation site
|
+| **`values`** (\*) | Audit `value` comparison | _Object with properties:_- **`before`** (\*): `number` (_≥0_) - Raw numeric value (source commit)
- **`after`** (\*): `number` (_≥0_) - Raw numeric value (target commit)
- **`diff`** (\*): `number` - Value change (`values.after - values.before`)
|
+| **`displayValues`** (\*) | Audit `displayValue` comparison | _Object with properties:_- `before`: `string` - Formatted value (e.g. '0.9 s', '2.1 MB') (source commit)
- `after`: `string` - Formatted value (e.g. '0.9 s', '2.1 MB') (target commit)
|
_(\*) Required._
@@ -55,6 +55,8 @@ _(\*) Required._
## AuditOutputs
+List of JSON formatted audit output emitted by the runner process of a plugin
+
_Array of [AuditOutput](#auditoutput) items._
## AuditReport
@@ -79,15 +81,15 @@ _(\*) Required._
_Object containing the following properties:_
-| Property | Description | Type |
-| :---------------- | :--------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **`slug`** (\*) | Unique ID (human-readable, URL-safe) | `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) |
-| **`title`** (\*) | Descriptive name | `string` (_max length: 256_) |
-| `docsUrl` | Documentation site | `string` (_url_) (_optional_) _or_ `''` |
-| **`plugin`** (\*) | Plugin which defines it | _Object with properties:_- `slug`: `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) - Unique plugin slug within core config
- `title`: `string` (_max length: 256_) - Descriptive name
- `docsUrl`: `string` (_url_) (_optional_) _or_ `''` - Plugin documentation site
|
-| **`score`** (\*) | Value between 0 and 1 | `number` (_≥0, ≤1_) |
-| **`value`** (\*) | Raw numeric value | `number` (_≥0_) |
-| `displayValue` | Formatted value (e.g. '0.9 s', '2.1 MB') | `string` |
+| Property | Description | Type |
+| :---------------- | :--------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`slug`** (\*) | Unique ID (human-readable, URL-safe) | `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) |
+| **`title`** (\*) | Descriptive name | `string` (_max length: 256_) |
+| `docsUrl` | Documentation site | `string` (_url_) (_optional_) _or_ `''` |
+| **`plugin`** (\*) | Plugin which defines it | _Object with properties:_- **`slug`** (\*): `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) - Unique plugin slug within core config
- **`title`** (\*): `string` (_max length: 256_) - Descriptive name
- `docsUrl`: `string` (_url_) (_optional_) _or_ `''` - Plugin documentation site
|
+| **`score`** (\*) | Value between 0 and 1 | `number` (_≥0, ≤1_) |
+| **`value`** (\*) | Raw numeric value | `number` (_≥0_) |
+| `displayValue` | Formatted value (e.g. '0.9 s', '2.1 MB') | `string` |
_(\*) Required._
@@ -151,12 +153,12 @@ _(\*) Required._
_Object containing the following properties:_
-| Property | Description | Type |
-| :---------------- | :----------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **`slug`** (\*) | Unique ID (human-readable, URL-safe) | `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) |
-| **`title`** (\*) | Descriptive name | `string` (_max length: 256_) |
-| `docsUrl` | Documentation site | `string` (_url_) (_optional_) _or_ `''` |
-| **`scores`** (\*) | Score comparison | _Object with properties:_- `before`: `number` (_≥0, ≤1_) - Value between 0 and 1 (source commit)
- `after`: `number` (_≥0, ≤1_) - Value between 0 and 1 (target commit)
- `diff`: `number` (_≥-1, ≤1_) - Score change (`scores.after - scores.before`)
|
+| Property | Description | Type |
+| :---------------- | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| **`slug`** (\*) | Unique ID (human-readable, URL-safe) | `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) |
+| **`title`** (\*) | Descriptive name | `string` (_max length: 256_) |
+| `docsUrl` | Documentation site | `string` (_url_) (_optional_) _or_ `''` |
+| **`scores`** (\*) | Score comparison | _Object with properties:_- **`before`** (\*): `number` (_≥0, ≤1_) - Value between 0 and 1 (source commit)
- **`after`** (\*): `number` (_≥0, ≤1_) - Value between 0 and 1 (target commit)
- **`diff`** (\*): `number` (_≥-1, ≤1_) - Score change (`scores.after - scores.before`)
|
_(\*) Required._
@@ -196,7 +198,7 @@ _Object containing the following properties:_
| :----------------- | :------------------------------------- | :------------------------------------ |
| **`hash`** (\*) | Commit SHA (full) | `string` (_regex: `/^[\da-f]{40}$/`_) |
| **`message`** (\*) | Commit message | `string` |
-| **`date`** (\*) | Date and time when commit was authored | `Date` (_nullable_) |
+| **`date`** (\*) | Date and time when commit was authored | `Date` |
| **`author`** (\*) | Commit author name | `string` |
_(\*) Required._
@@ -210,7 +212,7 @@ _Object containing the following properties:_
| **`plugins`** (\*) | List of plugins to be used (official, community-provided, or custom) | _Array of at least 1 [PluginConfig](#pluginconfig) items_ |
| `persist` | | [PersistConfig](#persistconfig) |
| `upload` | | [UploadConfig](#uploadconfig) |
-| `categories` | | _Array of [CategoryConfig](#categoryconfig) items_ |
+| `categories` | Categorization of individual audits | _Array of [CategoryConfig](#categoryconfig) items_ |
_(\*) Required._
@@ -235,11 +237,11 @@ _(\*) Required._
_Object containing the following properties:_
-| Property | Description | Type |
-| :---------------- | :-------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **`name`** (\*) | File or folder name | `string` (_min length: 1_) |
-| **`values`** (\*) | Coverage metrics for file/folder | _Object with properties:_- `coverage`: `number` (_≥0, ≤1_) - Coverage ratio
- `missing`: _Array of [CoverageTreeMissingLOC](#coveragetreemissingloc) items_ - Uncovered lines of code
|
-| `children` | Files and folders contained in this folder (omit if file) | _Array of [CoverageTreeNode](#coveragetreenode) items_ |
+| Property | Description | Type |
+| :---------------- | :-------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`name`** (\*) | File or folder name | `string` (_min length: 1_) |
+| **`values`** (\*) | Coverage metrics for file/folder | _Object with properties:_- **`coverage`** (\*): `number` (_≥0, ≤1_) - Coverage ratio
- `missing`: _Array of [CoverageTreeMissingLOC](#coveragetreemissingloc) items_ - Uncovered lines of code
|
+| `children` | Files and folders contained in this folder (omit if file) | _Array of [CoverageTreeNode](#coveragetreenode) items_ |
_(\*) Required._
@@ -267,7 +269,7 @@ _String which has a minimum length of 1._
## Format
-_Enum string, one of the following possible values:_
+_Enum, one of the following possible values:_
- `'json'`
- `'md'`
@@ -276,13 +278,13 @@ _Enum string, one of the following possible values:_
_Object containing the following properties:_
-| Property | Description | Type |
-| :---------------- | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **`slug`** (\*) | Unique ID (human-readable, URL-safe) | `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) |
-| **`title`** (\*) | Descriptive name | `string` (_max length: 256_) |
-| `docsUrl` | Documentation site | `string` (_url_) (_optional_) _or_ `''` |
-| **`scores`** (\*) | Score comparison | _Object with properties:_- `before`: `number` (_≥0, ≤1_) - Value between 0 and 1 (source commit)
- `after`: `number` (_≥0, ≤1_) - Value between 0 and 1 (target commit)
- `diff`: `number` (_≥-1, ≤1_) - Score change (`scores.after - scores.before`)
|
-| **`plugin`** (\*) | Plugin which defines it | _Object with properties:_- `slug`: `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) - Unique plugin slug within core config
- `title`: `string` (_max length: 256_) - Descriptive name
- `docsUrl`: `string` (_url_) (_optional_) _or_ `''` - Plugin documentation site
|
+| Property | Description | Type |
+| :---------------- | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`slug`** (\*) | Unique ID (human-readable, URL-safe) | `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) |
+| **`title`** (\*) | Descriptive name | `string` (_max length: 256_) |
+| `docsUrl` | Documentation site | `string` (_url_) (_optional_) _or_ `''` |
+| **`scores`** (\*) | Score comparison | _Object with properties:_- **`before`** (\*): `number` (_≥0, ≤1_) - Value between 0 and 1 (source commit)
- **`after`** (\*): `number` (_≥0, ≤1_) - Value between 0 and 1 (target commit)
- **`diff`** (\*): `number` (_≥-1, ≤1_) - Score change (`scores.after - scores.before`)
|
+| **`plugin`** (\*) | Plugin which defines it | _Object with properties:_- **`slug`** (\*): `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) - Unique plugin slug within core config
- **`title`** (\*): `string` (_max length: 256_) - Descriptive name
- `docsUrl`: `string` (_url_) (_optional_) _or_ `''` - Plugin documentation site
|
_(\*) Required._
@@ -303,13 +305,13 @@ _(\*) Required._
_Object containing the following properties:_
-| Property | Description | Type |
-| :---------------- | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **`slug`** (\*) | Unique ID (human-readable, URL-safe) | `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) |
-| **`title`** (\*) | Descriptive name | `string` (_max length: 256_) |
-| `docsUrl` | Documentation site | `string` (_url_) (_optional_) _or_ `''` |
-| **`plugin`** (\*) | Plugin which defines it | _Object with properties:_- `slug`: `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) - Unique plugin slug within core config
- `title`: `string` (_max length: 256_) - Descriptive name
- `docsUrl`: `string` (_url_) (_optional_) _or_ `''` - Plugin documentation site
|
-| **`score`** (\*) | Value between 0 and 1 | `number` (_≥0, ≤1_) |
+| Property | Description | Type |
+| :---------------- | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`slug`** (\*) | Unique ID (human-readable, URL-safe) | `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) |
+| **`title`** (\*) | Descriptive name | `string` (_max length: 256_) |
+| `docsUrl` | Documentation site | `string` (_url_) (_optional_) _or_ `''` |
+| **`plugin`** (\*) | Plugin which defines it | _Object with properties:_- **`slug`** (\*): `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) - Unique plugin slug within core config
- **`title`** (\*): `string` (_max length: 256_) - Descriptive name
- `docsUrl`: `string` (_url_) (_optional_) _or_ `''` - Plugin documentation site
|
+| **`score`** (\*) | Value between 0 and 1 | `number` (_≥0, ≤1_) |
_(\*) Required._
@@ -346,7 +348,7 @@ _(\*) Required._
Severity level
-_Enum string, one of the following possible values:_
+_Enum, one of the following possible values:_
- `'info'`
- `'warning'`
@@ -356,7 +358,7 @@ _Enum string, one of the following possible values:_
Icon from VSCode Material Icons extension
-_Enum string, one of the following possible values:_
+_Enum, one of the following possible values:_
Expand for full list of 858 values
@@ -1260,11 +1262,22 @@ _Object containing the following properties:_
| **`slug`** (\*) | Unique plugin slug within core config | `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) |
| **`icon`** (\*) | Icon from VSCode Material Icons extension | [MaterialIcon](#materialicon) |
| **`runner`** (\*) | | [RunnerConfig](#runnerconfig) _or_ [RunnerFunction](#runnerfunction) |
-| **`audits`** (\*) | | _Array of at least 1 [Audit](#audit) items_ |
-| `groups` | | _Array of [Group](#group) items_ |
+| **`audits`** (\*) | List of audits maintained in a plugin | _Array of at least 1 [Audit](#audit) items_ |
+| `groups` | List of groups | _Array of [Group](#group) items_ |
+| `context` | Plugin-specific context data for helpers | [PluginContext](#plugincontext) |
_(\*) Required._
+## PluginContext
+
+Plugin-specific context data for helpers
+
+_Object record with dynamic keys:_
+
+- _keys of type_ `string`
+- _values of type_ `unknown`
+ (_optional_)
+
## PluginMeta
_Object containing the following properties:_
@@ -1305,17 +1318,19 @@ _(\*) Required._
## Report
+Collect output data
+
_Object containing the following properties:_
-| Property | Description | Type |
-| :--------------------- | :---------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **`packageName`** (\*) | NPM package name | `string` |
-| **`version`** (\*) | NPM version of the CLI | `string` |
-| **`date`** (\*) | Start date and time of the collect run | `string` |
-| **`duration`** (\*) | Duration of the collect run in ms | `number` |
-| **`plugins`** (\*) | | _Array of at least 1 [PluginReport](#pluginreport) items_ |
-| `categories` | | _Array of [CategoryConfig](#categoryconfig) items_ |
-| **`commit`** (\*) | Git commit for which report was collected | _Object with properties:_- `hash`: `string` (_regex: `/^[\da-f]{40}$/`_) - Commit SHA (full)
- `message`: `string` - Commit message
- `date`: `Date` (_nullable_) - Date and time when commit was authored
- `author`: `string` - Commit author name
(_nullable_) |
+| Property | Description | Type |
+| :--------------------- | :---------------------------------------- | :-------------------------------------------------------- |
+| **`packageName`** (\*) | NPM package name | `string` |
+| **`version`** (\*) | NPM version of the CLI | `string` |
+| **`date`** (\*) | Start date and time of the collect run | `string` |
+| **`duration`** (\*) | Duration of the collect run in ms | `number` |
+| **`plugins`** (\*) | | _Array of at least 1 [PluginReport](#pluginreport) items_ |
+| `categories` | | _Array of [CategoryConfig](#categoryconfig) items_ |
+| **`commit`** (\*) | Git commit for which report was collected | [Commit](#commit) (_nullable_) |
_(\*) Required._
@@ -1323,18 +1338,18 @@ _(\*) Required._
_Object containing the following properties:_
-| Property | Description | Type |
-| :--------------------- | :---------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **`commits`** (\*) | Commits identifying compared reports | _Object with properties:_- `before`: _Object with properties:_
- `hash`: `string` (_regex: `/^[\da-f]{40}$/`_) - Commit SHA (full)
- `message`: `string` - Commit message
- `date`: `Date` (_nullable_) - Date and time when commit was authored
- `author`: `string` - Commit author name
- Git commit (source commit) - `after`: _Object with properties:_
- `hash`: `string` (_regex: `/^[\da-f]{40}$/`_) - Commit SHA (full)
- `message`: `string` - Commit message
- `date`: `Date` (_nullable_) - Date and time when commit was authored
- `author`: `string` - Commit author name
- Git commit (target commit)
(_nullable_) |
-| `portalUrl` | Link to comparison page in Code PushUp portal | `string` (_url_) |
-| `label` | Label (e.g. project name) | `string` |
-| **`categories`** (\*) | Changes affecting categories | _Object with properties:_- `changed`: _Array of [CategoryDiff](#categorydiff) items_
- `unchanged`: _Array of [CategoryResult](#categoryresult) items_
- `added`: _Array of [CategoryResult](#categoryresult) items_
- `removed`: _Array of [CategoryResult](#categoryresult) items_
|
-| **`groups`** (\*) | Changes affecting groups | _Object with properties:_- `changed`: _Array of [GroupDiff](#groupdiff) items_
- `unchanged`: _Array of [GroupResult](#groupresult) items_
- `added`: _Array of [GroupResult](#groupresult) items_
- `removed`: _Array of [GroupResult](#groupresult) items_
|
-| **`audits`** (\*) | Changes affecting audits | _Object with properties:_- `changed`: _Array of [AuditDiff](#auditdiff) items_
- `unchanged`: _Array of [AuditResult](#auditresult) items_
- `added`: _Array of [AuditResult](#auditresult) items_
- `removed`: _Array of [AuditResult](#auditresult) items_
|
-| **`packageName`** (\*) | NPM package name | `string` |
-| **`version`** (\*) | NPM version of the CLI (when `compare` was run) | `string` |
-| **`date`** (\*) | Start date and time of the compare run | `string` |
-| **`duration`** (\*) | Duration of the compare run in ms | `number` |
+| Property | Description | Type |
+| :--------------------- | :---------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`commits`** (\*) | Commits identifying compared reports | _Object with properties:_- **`before`** (\*): [Commit](#commit) - Git commit (source commit)
- **`after`** (\*): [Commit](#commit) - Git commit (target commit)
(_nullable_) |
+| `portalUrl` | Link to comparison page in Code PushUp portal | `string` (_url_) |
+| `label` | Label (e.g. project name) | `string` |
+| **`categories`** (\*) | Changes affecting categories | _Object with properties:_- **`changed`** (\*): _Array of [CategoryDiff](#categorydiff) items_
- **`unchanged`** (\*): _Array of [CategoryResult](#categoryresult) items_
- **`added`** (\*): _Array of [CategoryResult](#categoryresult) items_
- **`removed`** (\*): _Array of [CategoryResult](#categoryresult) items_
|
+| **`groups`** (\*) | Changes affecting groups | _Object with properties:_- **`changed`** (\*): _Array of [GroupDiff](#groupdiff) items_
- **`unchanged`** (\*): _Array of [GroupResult](#groupresult) items_
- **`added`** (\*): _Array of [GroupResult](#groupresult) items_
- **`removed`** (\*): _Array of [GroupResult](#groupresult) items_
|
+| **`audits`** (\*) | Changes affecting audits | _Object with properties:_- **`changed`** (\*): _Array of [AuditDiff](#auditdiff) items_
- **`unchanged`** (\*): _Array of [AuditResult](#auditresult) items_
- **`added`** (\*): _Array of [AuditResult](#auditresult) items_
- **`removed`** (\*): _Array of [AuditResult](#auditresult) items_
|
+| **`packageName`** (\*) | NPM package name | `string` |
+| **`version`** (\*) | NPM version of the CLI (when `compare` was run) | `string` |
+| **`date`** (\*) | Start date and time of the compare run | `string` |
+| **`duration`** (\*) | Duration of the compare run in ms | `number` |
_(\*) Required._
@@ -1344,13 +1359,13 @@ How to execute runner
_Object containing the following properties:_
-| Property | Description | Type |
-| :-------------------- | :----------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **`command`** (\*) | Shell command to execute | `string` |
-| `args` | | `Array` |
-| **`outputFile`** (\*) | Runner output path | [FilePath](#filepath) |
-| `outputTransform` | | _Function:_
- _parameters:_
- `unknown` (_optional & nullable_)
- _returns:_ [AuditOutputs](#auditoutputs) _or_ _Promise of_ [AuditOutputs](#auditoutputs)
|
-| `configFile` | Runner config path | [FilePath](#filepath) |
+| Property | Description | Type |
+| :-------------------- | :----------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`command`** (\*) | Shell command to execute | `string` |
+| `args` | Command arguments | `Array` |
+| **`outputFile`** (\*) | Runner output path | [FilePath](#filepath) |
+| `outputTransform` | | _Function:_
- _parameters:_
- `unknown`
- _returns:_ [AuditOutputs](#auditoutputs) _or_ _Promise of_ [AuditOutputs](#auditoutputs)
|
+| `configFile` | Runner config path | [FilePath](#filepath) |
_(\*) Required._
@@ -1383,10 +1398,10 @@ Source file location
_Object containing the following properties:_
-| Property | Description | Type |
-| :-------------- | :--------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **`file`** (\*) | Relative path to source file in Git repo | [FilePath](#filepath) |
-| `position` | Location in file | _Object with properties:_- `startLine`: `number` (_int, >0_) - Start line
- `startColumn`: `number` (_int, >0_) - Start column
- `endLine`: `number` (_int, >0_) - End line
- `endColumn`: `number` (_int, >0_) - End column
|
+| Property | Description | Type |
+| :-------------- | :--------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **`file`** (\*) | Relative path to source file in Git repo | [FilePath](#filepath) |
+| `position` | Location in file | _Object with properties:_- **`startLine`** (\*): `number` (_int, >0_) - Start line
- `startColumn`: `number` (_int, >0_) - Start column
- `endLine`: `number` (_int, >0_) - End line
- `endColumn`: `number` (_int, >0_) - End column
|
_(\*) Required._
@@ -1394,7 +1409,7 @@ _(\*) Required._
Cell alignment
-_Enum string, one of the following possible values:_
+_Enum, one of the following possible values:_
- `'left'`
- `'center'`
@@ -1407,8 +1422,7 @@ _Union of the following possible types:_
- `string`
- `number`
- `boolean`
-- `null` (_nullable_)
- (_optional & nullable_)
+- `null`
_Default value:_ `null`
@@ -1428,7 +1442,7 @@ _(\*) Required._
Cell alignment
-_Enum string, one of the following possible values:_
+_Enum, one of the following possible values:_
- `'left'`
- `'center'`
@@ -1441,13 +1455,13 @@ Object row
_Object record with dynamic keys:_
- _keys of type_ `string`
-- _values of type_ [TableCellValue](#tablecellvalue) (_optional & nullable_)
+- _values of type_ [TableCellValue](#tablecellvalue)
## TableRowPrimitive
Primitive row
-_Array of [TableCellValue](#tablecellvalue) (\_optional & nullable_) items.\_
+_Array of [TableCellValue](#tablecellvalue) items._
## Tree
diff --git a/packages/models/package.json b/packages/models/package.json
index 82b81e2f8..77dfeea93 100644
--- a/packages/models/package.json
+++ b/packages/models/package.json
@@ -27,7 +27,7 @@
},
"type": "module",
"dependencies": {
- "zod": "^3.22.1",
+ "zod": "^4.0.5",
"vscode-material-icons": "^0.1.0"
}
}
diff --git a/packages/models/src/lib/audit-output.ts b/packages/models/src/lib/audit-output.ts
index 7c9b611ff..a5b431f15 100644
--- a/packages/models/src/lib/audit-output.ts
+++ b/packages/models/src/lib/audit-output.ts
@@ -1,10 +1,10 @@
import { z } from 'zod';
+import { createDuplicateSlugsCheck } from './implementation/checks.js';
import {
nonnegativeNumberSchema,
scoreSchema,
slugSchema,
} from './implementation/schemas.js';
-import { errorItems, hasDuplicateStrings } from './implementation/utils.js';
import { issueSchema } from './issue.js';
import { tableSchema } from './table.js';
import { treeSchema } from './tree.js';
@@ -12,56 +12,38 @@ import { treeSchema } from './tree.js';
export const auditValueSchema =
nonnegativeNumberSchema.describe('Raw numeric value');
export const auditDisplayValueSchema = z
- .string({ description: "Formatted value (e.g. '0.9 s', '2.1 MB')" })
- .optional();
+ .string()
+ .optional()
+ .describe("Formatted value (e.g. '0.9 s', '2.1 MB')");
-export const auditDetailsSchema = z.object(
- {
- issues: z
- .array(issueSchema, { description: 'List of findings' })
- .optional(),
+export const auditDetailsSchema = z
+ .object({
+ issues: z.array(issueSchema).describe('List of findings').optional(),
table: tableSchema('Table of related findings').optional(),
trees: z
- .array(treeSchema, { description: 'Findings in tree structure' })
+ .array(treeSchema)
+ .describe('Findings in tree structure')
.optional(),
- },
- { description: 'Detailed information' },
-);
+ })
+ .describe('Detailed information');
export type AuditDetails = z.infer;
-export const auditOutputSchema = z.object(
- {
+export const auditOutputSchema = z
+ .object({
slug: slugSchema.describe('Reference to audit'),
displayValue: auditDisplayValueSchema,
value: auditValueSchema,
score: scoreSchema,
details: auditDetailsSchema.optional(),
- },
- { description: 'Audit information' },
-);
+ })
+ .describe('Audit information');
export type AuditOutput = z.infer;
export const auditOutputsSchema = z
- .array(auditOutputSchema, {
- description:
- 'List of JSON formatted audit output emitted by the runner process of a plugin',
- })
- // audit slugs are unique
- .refine(
- audits => !getDuplicateSlugsInAudits(audits),
- audits => ({ message: duplicateSlugsInAuditsErrorMsg(audits) }),
+ .array(auditOutputSchema)
+ .check(createDuplicateSlugsCheck('Audit'))
+ .describe(
+ 'List of JSON formatted audit output emitted by the runner process of a plugin',
);
export type AuditOutputs = z.infer;
-
-// helper for validator: audit slugs are unique
-function duplicateSlugsInAuditsErrorMsg(audits: AuditOutput[]) {
- const duplicateRefs = getDuplicateSlugsInAudits(audits);
- return `In plugin audits the slugs are not unique: ${errorItems(
- duplicateRefs,
- )}`;
-}
-
-function getDuplicateSlugsInAudits(audits: AuditOutput[]) {
- return hasDuplicateStrings(audits.map(({ slug }) => slug));
-}
diff --git a/packages/models/src/lib/audit-output.unit.test.ts b/packages/models/src/lib/audit-output.unit.test.ts
index 64160ceaa..7a9515ded 100644
--- a/packages/models/src/lib/audit-output.unit.test.ts
+++ b/packages/models/src/lib/audit-output.unit.test.ts
@@ -179,6 +179,8 @@ describe('auditOutputsSchema', () => {
score: 0.75,
},
] satisfies AuditOutputs),
- ).toThrow('slugs are not unique: total-blocking-time');
+ ).toThrow(
+ String.raw`Audit slugs must be unique, but received duplicates: \"total-blocking-time\"`,
+ );
});
});
diff --git a/packages/models/src/lib/audit.ts b/packages/models/src/lib/audit.ts
index e28b9ef61..695ad5ef9 100644
--- a/packages/models/src/lib/audit.ts
+++ b/packages/models/src/lib/audit.ts
@@ -1,6 +1,6 @@
import { z } from 'zod';
+import { createDuplicateSlugsCheck } from './implementation/checks.js';
import { metaSchema, slugSchema } from './implementation/schemas.js';
-import { errorItems, hasDuplicateStrings } from './implementation/utils.js';
export const auditSchema = z
.object({
@@ -18,28 +18,7 @@ export const auditSchema = z
export type Audit = z.infer;
export const pluginAuditsSchema = z
- .array(auditSchema, {
- description: 'List of audits maintained in a plugin',
- })
+ .array(auditSchema)
.min(1)
- // audit slugs are unique
- .refine(
- auditMetadata => !getDuplicateSlugsInAudits(auditMetadata),
- auditMetadata => ({
- message: duplicateSlugsInAuditsErrorMsg(auditMetadata),
- }),
- );
-
-// =======================
-
-// helper for validator: audit slugs are unique
-function duplicateSlugsInAuditsErrorMsg(audits: Audit[]) {
- const duplicateRefs = getDuplicateSlugsInAudits(audits);
- return `In plugin audits the following slugs are not unique: ${errorItems(
- duplicateRefs,
- )}`;
-}
-
-function getDuplicateSlugsInAudits(audits: Audit[]) {
- return hasDuplicateStrings(audits.map(({ slug }) => slug));
-}
+ .check(createDuplicateSlugsCheck('Audit'))
+ .describe('List of audits maintained in a plugin');
diff --git a/packages/models/src/lib/audit.unit.test.ts b/packages/models/src/lib/audit.unit.test.ts
index 0c637ca5d..a681f6041 100644
--- a/packages/models/src/lib/audit.unit.test.ts
+++ b/packages/models/src/lib/audit.unit.test.ts
@@ -74,6 +74,8 @@ describe('pluginAuditsSchema', () => {
title: 'Jest unit tests results.',
},
] satisfies Audit[]),
- ).toThrow('slugs are not unique: jest-unit-test-results');
+ ).toThrow(
+ String.raw`Audit slugs must be unique, but received duplicates: \"jest-unit-test-results\"`,
+ );
});
});
diff --git a/packages/models/src/lib/category-config.ts b/packages/models/src/lib/category-config.ts
index 16dd04aa9..c634afced 100644
--- a/packages/models/src/lib/category-config.ts
+++ b/packages/models/src/lib/category-config.ts
@@ -1,21 +1,26 @@
import { z } from 'zod';
+import {
+ createDuplicateSlugsCheck,
+ createDuplicatesCheck,
+} from './implementation/checks.js';
import {
metaSchema,
scorableSchema,
slugSchema,
weightedRefSchema,
} from './implementation/schemas.js';
-import { errorItems, hasDuplicateStrings } from './implementation/utils.js';
+import { formatRef } from './implementation/utils.js';
export const categoryRefSchema = weightedRefSchema(
'Weighted references to audits and/or groups for the category',
'Slug of an audit or group (depending on `type`)',
).merge(
z.object({
- type: z.enum(['audit', 'group'], {
- description:
+ type: z
+ .enum(['audit', 'group'])
+ .describe(
'Discriminant for reference kind, affects where `slug` is looked up',
- }),
+ ),
plugin: slugSchema.describe(
'Plugin slug (plugin should contain referenced audit or group)',
),
@@ -26,8 +31,11 @@ export type CategoryRef = z.infer;
export const categoryConfigSchema = scorableSchema(
'Category with a score calculated from audits and groups from various plugins',
categoryRefSchema,
- getDuplicateRefsInCategoryMetrics,
- duplicateRefsInCategoryMetricsErrorMsg,
+ createDuplicatesCheck(
+ serializeCategoryRefTarget,
+ duplicates =>
+ `Category has duplicate references: ${formatSerializedCategoryRefTargets(duplicates)}`,
+ ),
)
.merge(
metaSchema({
@@ -40,49 +48,36 @@ export const categoryConfigSchema = scorableSchema(
.merge(
z.object({
isBinary: z
- .boolean({
- description:
- 'Is this a binary category (i.e. only a perfect score considered a "pass")?',
- })
+ .boolean()
+ .describe(
+ 'Is this a binary category (i.e. only a perfect score considered a "pass")?',
+ )
.optional(),
}),
);
export type CategoryConfig = z.infer;
-// helper for validator: categories have unique refs to audits or groups
-export function duplicateRefsInCategoryMetricsErrorMsg(metrics: CategoryRef[]) {
- const duplicateRefs = getDuplicateRefsInCategoryMetrics(metrics);
- return `In the categories, the following audit or group refs are duplicates: ${errorItems(
- duplicateRefs,
- )}`;
-}
+const CATEGORY_REF_SEP = '||';
-function getDuplicateRefsInCategoryMetrics(metrics: CategoryRef[]) {
- return hasDuplicateStrings(
- metrics.map(({ slug, type, plugin }) => `${type} :: ${plugin} / ${slug}`),
- );
+function serializeCategoryRefTarget(ref: CategoryRef): string {
+ return [ref.type, ref.plugin, ref.slug].join(CATEGORY_REF_SEP);
}
-export const categoriesSchema = z
- .array(categoryConfigSchema, {
- description: 'Categorization of individual audits',
- })
- .refine(
- categoryCfg => !getDuplicateSlugCategories(categoryCfg),
- categoryCfg => ({
- message: duplicateSlugCategoriesErrorMsg(categoryCfg),
- }),
- );
-
-// helper for validator: categories slugs are unique
-function duplicateSlugCategoriesErrorMsg(categories: CategoryConfig[]) {
- const duplicateStringSlugs = getDuplicateSlugCategories(categories);
- return `In the categories, the following slugs are duplicated: ${errorItems(
- duplicateStringSlugs,
- )}`;
+function formatSerializedCategoryRefTargets(keys: string[]): string {
+ return keys
+ .map(key => {
+ const [type, plugin, slug] = key.split(CATEGORY_REF_SEP) as [
+ 'group' | 'audit',
+ string,
+ string,
+ ];
+ return formatRef({ type, plugin, slug });
+ })
+ .join(', ');
}
-function getDuplicateSlugCategories(categories: CategoryConfig[]) {
- return hasDuplicateStrings(categories.map(({ slug }) => slug));
-}
+export const categoriesSchema = z
+ .array(categoryConfigSchema)
+ .check(createDuplicateSlugsCheck('Category'))
+ .describe('Categorization of individual audits');
diff --git a/packages/models/src/lib/category-config.unit.test.ts b/packages/models/src/lib/category-config.unit.test.ts
index d234c8292..bc717bb30 100644
--- a/packages/models/src/lib/category-config.unit.test.ts
+++ b/packages/models/src/lib/category-config.unit.test.ts
@@ -49,7 +49,7 @@ describe('categoryRefSchema', () => {
type: 'audit',
weight: -2,
} satisfies CategoryRef),
- ).toThrow('Number must be greater than or equal to 0');
+ ).toThrow('Too small: expected number to be >=0');
});
it('should throw for an invalid reference type', () => {
@@ -60,7 +60,7 @@ describe('categoryRefSchema', () => {
type: 'issue',
weight: 1,
}),
- ).toThrow('Invalid enum value');
+ ).toThrow(String.raw`Invalid option: expected one of \"audit\"|\"group\"`);
});
it('should throw for a missing weight', () => {
@@ -152,7 +152,9 @@ describe('categoryConfigSchema', () => {
},
],
} satisfies CategoryConfig),
- ).toThrow('audit or group refs are duplicates');
+ ).toThrow(
+ String.raw`Category has duplicate references: audit \"jest-unit-tests\" (plugin \"jest\")`,
+ );
});
it('should throw for a category with only zero-weight references', () => {
@@ -175,9 +177,7 @@ describe('categoryConfigSchema', () => {
},
],
} satisfies CategoryConfig),
- ).toThrow(
- 'In a category, there has to be at least one ref with weight > 0. Affected refs: functional/immutable-data, lighthouse-experimental',
- );
+ ).toThrow('A category must have at least 1 ref with weight > 0.');
});
});
@@ -246,7 +246,7 @@ describe('categoriesSchema', () => {
},
] satisfies CategoryConfig[]),
).toThrow(
- 'In the categories, the following slugs are duplicated: bug-prevention',
+ String.raw`Category slugs must be unique, but received duplicates: \"bug-prevention\"`,
);
});
});
diff --git a/packages/models/src/lib/commit.ts b/packages/models/src/lib/commit.ts
index 8442b8030..c92609ef2 100644
--- a/packages/models/src/lib/commit.ts
+++ b/packages/models/src/lib/commit.ts
@@ -1,24 +1,18 @@
import { z } from 'zod';
-export const commitSchema = z.object(
- {
+export const commitSchema = z
+ .object({
hash: z
- .string({ description: 'Commit SHA (full)' })
+ .string()
.regex(
/^[\da-f]{40}$/,
'Commit SHA should be a 40-character hexadecimal string',
- ),
- message: z.string({ description: 'Commit message' }),
- date: z.coerce.date({
- description: 'Date and time when commit was authored',
- }),
- author: z
- .string({
- description: 'Commit author name',
- })
- .trim(),
- },
- { description: 'Git commit' },
-);
+ )
+ .describe('Commit SHA (full)'),
+ message: z.string().describe('Commit message'),
+ date: z.coerce.date().describe('Date and time when commit was authored'),
+ author: z.string().trim().describe('Commit author name'),
+ })
+ .describe('Git commit');
export type Commit = z.infer;
diff --git a/packages/models/src/lib/configuration.ts b/packages/models/src/lib/configuration.ts
index 72eca414d..e3512d2f5 100644
--- a/packages/models/src/lib/configuration.ts
+++ b/packages/models/src/lib/configuration.ts
@@ -4,9 +4,9 @@ import { z } from 'zod';
* Generic schema for a tool command configuration, reusable across plugins.
*/
export const artifactGenerationCommandSchema = z.union([
- z.string({ description: 'Generate artifact files' }).min(1),
+ z.string().min(1).describe('Generate artifact files'),
z.object({
- command: z.string({ description: 'Generate artifact files' }).min(1),
+ command: z.string().min(1).describe('Generate artifact files'),
args: z.array(z.string()).optional(),
}),
]);
diff --git a/packages/models/src/lib/core-config.ts b/packages/models/src/lib/core-config.ts
index 4c5307557..a4e6469f7 100644
--- a/packages/models/src/lib/core-config.ts
+++ b/packages/models/src/lib/core-config.ts
@@ -1,20 +1,18 @@
import { z } from 'zod';
import { categoriesSchema } from './category-config.js';
-import {
- getMissingRefsForCategories,
- missingRefsForCategoriesErrorMsg,
-} from './implementation/utils.js';
+import { createCheck } from './implementation/checks.js';
+import { findMissingSlugsInCategoryRefs } from './implementation/utils.js';
import { persistConfigSchema } from './persist-config.js';
import { pluginConfigSchema } from './plugin-config.js';
import { uploadConfigSchema } from './upload-config.js';
export const unrefinedCoreConfigSchema = z.object({
plugins: z
- .array(pluginConfigSchema, {
- description:
- 'List of plugins to be used (official, community-provided, or custom)',
- })
- .min(1),
+ .array(pluginConfigSchema)
+ .min(1)
+ .describe(
+ 'List of plugins to be used (official, community-provided, or custom)',
+ ),
/** portal configuration for persisting results */
persist: persistConfigSchema.optional(),
/** portal configuration for uploading results */
@@ -31,13 +29,7 @@ export const coreConfigSchema = refineCoreConfig(unrefinedCoreConfigSchema);
*/
export function refineCoreConfig(schema: typeof unrefinedCoreConfigSchema) {
// categories point to existing audit or group refs
- return schema.refine(
- ({ categories, plugins }) =>
- !getMissingRefsForCategories(categories, plugins),
- ({ categories, plugins }) => ({
- message: missingRefsForCategoriesErrorMsg(categories, plugins),
- }),
- );
+ return schema.check(createCheck(findMissingSlugsInCategoryRefs));
}
export type CoreConfig = z.infer;
diff --git a/packages/models/src/lib/core-config.unit.test.ts b/packages/models/src/lib/core-config.unit.test.ts
index edfb6fa73..ddc0e0072 100644
--- a/packages/models/src/lib/core-config.unit.test.ts
+++ b/packages/models/src/lib/core-config.unit.test.ts
@@ -94,7 +94,7 @@ describe('coreConfigSchema', () => {
],
} satisfies CoreConfig),
).toThrow(
- 'category references need to point to an audit or group: vitest/unit-tests',
+ String.raw`Category references audits or groups which don't exist: audit \"unit-tests\" (plugin \"vitest\")`,
);
});
@@ -133,7 +133,7 @@ describe('coreConfigSchema', () => {
],
} satisfies CoreConfig),
).toThrow(
- 'category references need to point to an audit or group: eslint#eslint-errors (group)',
+ String.raw`Category references audits or groups which don't exist: group \"eslint-errors\" (plugin \"eslint\")`,
);
});
@@ -189,7 +189,7 @@ describe('coreConfigSchema', () => {
} satisfies CoreConfig;
expect(() => coreConfigSchema.parse(config)).toThrow(
- 'In a category, there has to be at least one ref with weight > 0. Affected refs: csp-xss',
+ 'A category must have at least 1 ref with weight > 0.',
);
});
});
diff --git a/packages/models/src/lib/group.ts b/packages/models/src/lib/group.ts
index faaa55b4e..a7b16eec0 100644
--- a/packages/models/src/lib/group.ts
+++ b/packages/models/src/lib/group.ts
@@ -1,15 +1,14 @@
import { z } from 'zod';
import {
- type WeightedRef,
+ createDuplicateSlugsCheck,
+ createDuplicatesCheck,
+} from './implementation/checks.js';
+import {
metaSchema,
scorableSchema,
weightedRefSchema,
} from './implementation/schemas.js';
-import {
- errorItems,
- exists,
- hasDuplicateStrings,
-} from './implementation/utils.js';
+import { formatSlugsList } from './implementation/utils.js';
export const groupRefSchema = weightedRefSchema(
'Weighted reference to a group',
@@ -30,48 +29,17 @@ export const groupSchema = scorableSchema(
'A group aggregates a set of audits into a single score which can be referenced from a category. ' +
'E.g. the group slug "performance" groups audits and can be referenced in a category',
groupRefSchema,
- getDuplicateRefsInGroups,
- duplicateRefsInGroupsErrorMsg,
+ createDuplicatesCheck(
+ ({ slug }) => slug,
+ duplicates =>
+ `Group has duplicate references to audits: ${formatSlugsList(duplicates)}`,
+ ),
).merge(groupMetaSchema);
export type Group = z.infer;
export const groupsSchema = z
- .array(groupSchema, {
- description: 'List of groups',
- })
+ .array(groupSchema)
+ .check(createDuplicateSlugsCheck('Group'))
.optional()
- .refine(
- groups => !getDuplicateSlugsInGroups(groups),
- groups => ({
- message: duplicateSlugsInGroupsErrorMsg(groups),
- }),
- );
-
-// ============
-
-// helper for validator: group refs are unique
-function duplicateRefsInGroupsErrorMsg(groups: WeightedRef[]) {
- const duplicateRefs = getDuplicateRefsInGroups(groups);
- return `In plugin groups the following references are not unique: ${errorItems(
- duplicateRefs,
- )}`;
-}
-
-function getDuplicateRefsInGroups(groups: WeightedRef[]) {
- return hasDuplicateStrings(groups.map(({ slug: ref }) => ref).filter(exists));
-}
-
-// helper for validator: group refs are unique
-function duplicateSlugsInGroupsErrorMsg(groups: Group[] | undefined) {
- const duplicateRefs = getDuplicateSlugsInGroups(groups);
- return `In groups the following slugs are not unique: ${errorItems(
- duplicateRefs,
- )}`;
-}
-
-function getDuplicateSlugsInGroups(groups: Group[] | undefined) {
- return Array.isArray(groups)
- ? hasDuplicateStrings(groups.map(({ slug }) => slug))
- : false;
-}
+ .describe('List of groups');
diff --git a/packages/models/src/lib/group.unit.test.ts b/packages/models/src/lib/group.unit.test.ts
index e0f9b5149..baef00fd1 100644
--- a/packages/models/src/lib/group.unit.test.ts
+++ b/packages/models/src/lib/group.unit.test.ts
@@ -82,7 +82,9 @@ describe('groupSchema', () => {
{ slug: 'lighthouse-bug-prevention', weight: 2 },
],
} satisfies Group),
- ).toThrow('following references are not unique: lighthouse-bug-prevention');
+ ).toThrow(
+ String.raw`Group has duplicate references to audits: \"lighthouse-bug-prevention\"`,
+ );
});
});
@@ -127,6 +129,8 @@ describe('groupsSchema', () => {
refs: [{ slug: 'jest-unit-tests', weight: 2 }],
},
] satisfies Group[]),
- ).toThrow('slugs are not unique: lighthouse');
+ ).toThrow(
+ String.raw`Group slugs must be unique, but received duplicates: \"lighthouse\"`,
+ );
});
});
diff --git a/packages/models/src/lib/implementation/checks.ts b/packages/models/src/lib/implementation/checks.ts
new file mode 100644
index 000000000..a3f12341f
--- /dev/null
+++ b/packages/models/src/lib/implementation/checks.ts
@@ -0,0 +1,42 @@
+import type { z } from 'zod';
+import { hasDuplicateStrings } from './utils.js';
+
+export function createCheck(
+ findErrorFn: (value: T) => false | { message: string },
+): z.core.CheckFn {
+ return ctx => {
+ const error = findErrorFn(ctx.value);
+ if (error) {
+ // eslint-disable-next-line functional/immutable-data, no-param-reassign
+ ctx.issues = [
+ ...ctx.issues,
+ {
+ code: 'custom',
+ message: error.message,
+ input: ctx.value,
+ },
+ ];
+ }
+ };
+}
+
+export function createDuplicatesCheck(
+ keyFn: (item: T) => string,
+ errorMsgFn: (duplicates: string[]) => string,
+): z.core.CheckFn {
+ return createCheck(items => {
+ const keys = items.map(keyFn);
+ const duplicates = hasDuplicateStrings(keys);
+ return duplicates && { message: errorMsgFn(duplicates) };
+ });
+}
+
+export function createDuplicateSlugsCheck(
+ name: 'Audit' | 'Plugin' | 'Category' | 'Group',
+): z.core.CheckFn {
+ return createDuplicatesCheck(
+ ({ slug }) => slug,
+ duplicates =>
+ `${name} slugs must be unique, but received duplicates: ${duplicates.map(slug => JSON.stringify(slug)).join(', ')}`,
+ );
+}
diff --git a/packages/models/src/lib/implementation/checks.unit.test.ts b/packages/models/src/lib/implementation/checks.unit.test.ts
new file mode 100644
index 000000000..802f88351
--- /dev/null
+++ b/packages/models/src/lib/implementation/checks.unit.test.ts
@@ -0,0 +1,146 @@
+import type { z } from 'zod';
+import type { Audit } from '../audit.js';
+import type { CategoryRef } from '../category-config.js';
+import {
+ createCheck,
+ createDuplicateSlugsCheck,
+ createDuplicatesCheck,
+} from './checks.js';
+
+describe('createCheck', () => {
+ it('should add issue if callback finds an error', () => {
+ const findErrorFn = vi
+ .fn<[string]>()
+ .mockReturnValue({ message: 'Something went wrong' });
+
+ const check = createCheck(findErrorFn);
+
+ expect(findErrorFn).not.toHaveBeenCalled();
+
+ const ctx: z.core.ParsePayload = { value: 'XYZ', issues: [] };
+ check(ctx);
+
+ expect(findErrorFn).toHaveBeenCalledWith('XYZ');
+ expect(ctx.issues).toEqual([
+ {
+ code: 'custom',
+ message: 'Something went wrong',
+ input: 'XYZ',
+ },
+ ]);
+ });
+
+ it('should NOT add issue if callback finds no error', () => {
+ const findErrorFn = vi.fn<[string]>().mockReturnValue(false);
+
+ const check = createCheck(findErrorFn);
+
+ expect(findErrorFn).not.toHaveBeenCalled();
+
+ const ctx: z.core.ParsePayload = { value: 'XYZ', issues: [] };
+ check(ctx);
+
+ expect(findErrorFn).toHaveBeenCalledWith('XYZ');
+ expect(ctx.issues).toEqual([]);
+ });
+});
+
+describe('createDuplicatesCheck', () => {
+ const keyFn = vi.fn(
+ ({ type, plugin, slug }: CategoryRef) => `${type} ${plugin}/${slug}`,
+ );
+ const errorMsgFn = vi.fn(
+ (duplicates: string[]) => `Duplicate refs found: ${duplicates.join(', ')}`,
+ );
+
+ it('add issue with custom message if there are duplicate keys', () => {
+ const check = createDuplicatesCheck(keyFn, errorMsgFn);
+
+ expect(keyFn).not.toHaveBeenCalled();
+ expect(errorMsgFn).not.toHaveBeenCalled();
+
+ const ctx: z.core.ParsePayload = {
+ value: [
+ { type: 'audit', plugin: 'coverage', slug: 'coverage', weight: 2 },
+ { type: 'audit', plugin: 'jsdocs', slug: 'coverage', weight: 1 },
+ { type: 'audit', plugin: 'coverage', slug: 'coverage', weight: 1 },
+ ],
+ issues: [],
+ };
+ check(ctx);
+
+ expect(keyFn).toHaveBeenCalledTimes(3);
+ expect(errorMsgFn).toHaveBeenCalledWith(['audit coverage/coverage']);
+ expect(ctx.issues).toEqual([
+ {
+ code: 'custom',
+ message: 'Duplicate refs found: audit coverage/coverage',
+ input: ctx.value,
+ },
+ ]);
+ });
+
+ it('add NOT add issue if all keys are unique', () => {
+ const check = createDuplicatesCheck(keyFn, errorMsgFn);
+
+ expect(keyFn).not.toHaveBeenCalled();
+ expect(errorMsgFn).not.toHaveBeenCalled();
+
+ const ctx: z.core.ParsePayload = {
+ value: [
+ { type: 'group', plugin: 'eslint', slug: 'errors', weight: 1 },
+ { type: 'group', plugin: 'eslint', slug: 'warnings', weight: 1 },
+ { type: 'group', plugin: 'typescript', slug: 'errors', weight: 1 },
+ ],
+ issues: [],
+ };
+ check(ctx);
+
+ expect(keyFn).toHaveBeenCalledTimes(3);
+ expect(errorMsgFn).not.toHaveBeenCalled();
+ expect(ctx.issues).toEqual([]);
+ });
+});
+
+describe('createDuplicateSlugsCheck', () => {
+ it('should add issue if there are duplicate slugs', () => {
+ const check = createDuplicateSlugsCheck('Audit');
+
+ const ctx: z.core.ParsePayload = {
+ value: [
+ { slug: 'lcp', title: 'Largest Contentful Paint' },
+ { slug: 'cls', title: 'Cumulative Layout Shift' },
+ { slug: 'fcp', title: 'First Contentful Paint' },
+ { slug: 'lcp', title: 'LCP' },
+ { slug: 'fcp', title: 'FCP' },
+ ],
+ issues: [],
+ };
+ check(ctx);
+
+ expect(ctx.issues).toEqual([
+ {
+ code: 'custom',
+ message:
+ 'Audit slugs must be unique, but received duplicates: "fcp", "lcp"',
+ input: ctx.value,
+ },
+ ]);
+ });
+
+ it('should NOT add issue if all slugs are unique', () => {
+ const check = createDuplicateSlugsCheck('Audit');
+
+ const ctx: z.core.ParsePayload = {
+ value: [
+ { slug: 'lcp', title: 'Largest Contentful Paint' },
+ { slug: 'cls', title: 'Cumulative Layout Shift' },
+ { slug: 'fcp', title: 'First Contentful Paint' },
+ ],
+ issues: [],
+ };
+ check(ctx);
+
+ expect(ctx.issues).toEqual([]);
+ });
+});
diff --git a/packages/models/src/lib/implementation/function.ts b/packages/models/src/lib/implementation/function.ts
new file mode 100644
index 000000000..af5101499
--- /dev/null
+++ b/packages/models/src/lib/implementation/function.ts
@@ -0,0 +1,60 @@
+import { z } from 'zod/v4';
+import type { $ZodFunction } from 'zod/v4/core';
+
+// https://zod.dev/v4/changelog?id=zfunction
+// https://github.com/colinhacks/zod/issues/4143#issuecomment-2931729793
+// https://github.com/matejchalk/zod2md?tab=readme-ov-file#function-schemas
+
+/**
+ * Converts Zod v4 function factory (returned by `z.function`) to Zod schema.
+ *
+ * Supports asynchronous functions. For synchronous functions, you can use {@link convertSyncZodFunctionToSchema}.
+ *
+ * @param factory `z.function({ input: [...], output: ... })`
+ * @returns Zod schema with compile-time and runtime validations.
+ */
+export function convertAsyncZodFunctionToSchema(
+ factory: T,
+) {
+ return z
+ .custom()
+ .transform((arg, ctx) => {
+ if (typeof arg !== 'function') {
+ ctx.addIssue(`Expected function, received ${typeof arg}`);
+ return z.NEVER;
+ }
+ return factory.implementAsync(arg as Parameters[0]);
+ })
+ .meta({
+ // enables zod2md to include function signature in docs
+ $ZodFunction: factory,
+ });
+}
+
+/**
+ * Converts Zod v4 function factory (returned by `z.function`) to Zod schema.
+ *
+ * **IMPORTANT!** Use for synchronous functions only. For asynchronous functions use {@link convertAsyncZodFunctionToSchema}.
+ *
+ * @throws `Encountered Promise during synchronous parse. Use .parseAsync() instead.` if used with async functions.
+ *
+ * @param factory `z.function({ input: [...], output: ... })`
+ * @returns Zod schema with compile-time and runtime validations.
+ */
+export function convertSyncZodFunctionToSchema(
+ factory: T,
+) {
+ return z
+ .custom()
+ .transform((arg, ctx) => {
+ if (typeof arg !== 'function') {
+ ctx.addIssue(`Expected function, received ${typeof arg}`);
+ return z.NEVER;
+ }
+ return factory.implement(arg as Parameters[0]);
+ })
+ .meta({
+ // enables zod2md to include function signature in docs
+ $ZodFunction: factory,
+ });
+}
diff --git a/packages/models/src/lib/implementation/function.unit.test.ts b/packages/models/src/lib/implementation/function.unit.test.ts
new file mode 100644
index 000000000..c04d4c8be
--- /dev/null
+++ b/packages/models/src/lib/implementation/function.unit.test.ts
@@ -0,0 +1,137 @@
+import { z } from 'zod/v4';
+import {
+ convertAsyncZodFunctionToSchema,
+ convertSyncZodFunctionToSchema,
+} from './function.js';
+
+describe('convertAsyncZodFunctionToSchema', () => {
+ it('should create a Zod schema', () => {
+ expect(
+ convertAsyncZodFunctionToSchema(
+ z.function({ output: z.promise(z.url()) }),
+ ),
+ ).toBeInstanceOf(z.ZodType);
+ });
+
+ it('should accept a valid function', async () => {
+ const schema = convertAsyncZodFunctionToSchema(
+ z.function({ input: [z.string()], output: z.promise(z.int()) }),
+ );
+
+ const fn = (input: string) => Promise.resolve(input.length);
+
+ expect(() => schema.parse(fn)).not.toThrow();
+ await expect(schema.parse(fn)('')).resolves.toBe(0);
+ });
+
+ it('should reject a non-function value', () => {
+ const schema = convertAsyncZodFunctionToSchema(
+ z.function({ input: [z.string()], output: z.promise(z.int()) }),
+ );
+
+ expect(() => schema.parse(123)).toThrow(
+ 'Expected function, received number',
+ );
+ });
+
+ it('should validate function arguments at runtime', async () => {
+ const schema = convertAsyncZodFunctionToSchema(
+ z.function({ input: [z.string()], output: z.promise(z.int()) }),
+ );
+
+ await expect(
+ schema.parse((input: string) => Promise.resolve(input.length))(
+ // @ts-expect-error testing invalid argument type
+ null,
+ ),
+ ).rejects.toThrow('expected string, received null');
+ });
+
+ it('should validate function return type at runtime', async () => {
+ const schema = convertAsyncZodFunctionToSchema(
+ z.function({ input: [z.string()], output: z.promise(z.int()) }),
+ );
+
+ await expect(
+ schema.parse(() => Promise.resolve(Math.random()))(''),
+ ).rejects.toThrow('expected int, received number');
+ });
+
+ it('should add $ZodFunction metadata for zod2md', () => {
+ const factory = z.function({
+ input: [z.string()],
+ output: z.promise(z.int()),
+ });
+ const schema = convertAsyncZodFunctionToSchema(factory);
+
+ expect(z.globalRegistry.get(schema)).toHaveProperty(
+ '$ZodFunction',
+ factory,
+ );
+ });
+});
+
+describe('convertSyncZodFunctionToSchema', () => {
+ it('should create a Zod schema', () => {
+ expect(
+ convertSyncZodFunctionToSchema(z.function({ output: z.url() })),
+ ).toBeInstanceOf(z.ZodType);
+ });
+
+ it('should accept a valid function', () => {
+ const schema = convertSyncZodFunctionToSchema(
+ z.function({ input: [z.string()], output: z.int() }),
+ );
+
+ const fn = (input: string) => input.length;
+
+ expect(() => schema.parse(fn)).not.toThrow();
+ expect(schema.parse(fn)('')).toBe(0);
+ });
+
+ it('should reject a non-function value', () => {
+ const schema = convertSyncZodFunctionToSchema(
+ z.function({ input: [z.string()], output: z.int() }),
+ );
+
+ expect(() => schema.parse(123)).toThrow(
+ 'Expected function, received number',
+ );
+ });
+
+ it('should validate function arguments at runtime', () => {
+ const schema = convertSyncZodFunctionToSchema(
+ z.function({ input: [z.string()], output: z.int() }),
+ );
+
+ expect(() =>
+ schema.parse((input: string) => input.length)(
+ // @ts-expect-error testing invalid argument type
+ null,
+ ),
+ ).toThrow('expected string, received null');
+ });
+
+ it('should validate function return type at runtime', () => {
+ const schema = convertSyncZodFunctionToSchema(
+ z.function({ input: [z.string()], output: z.int() }),
+ );
+
+ expect(() => schema.parse(() => Math.random())('')).toThrow(
+ 'expected int, received number',
+ );
+ });
+
+ it('should add $ZodFunction metadata for zod2md', () => {
+ const factory = z.function({
+ input: [z.string()],
+ output: z.int(),
+ });
+ const schema = convertSyncZodFunctionToSchema(factory);
+
+ expect(z.globalRegistry.get(schema)).toHaveProperty(
+ '$ZodFunction',
+ factory,
+ );
+ });
+});
diff --git a/packages/models/src/lib/implementation/schemas.ts b/packages/models/src/lib/implementation/schemas.ts
index 5e2a7b1e4..720dec5c4 100644
--- a/packages/models/src/lib/implementation/schemas.ts
+++ b/packages/models/src/lib/implementation/schemas.ts
@@ -1,5 +1,12 @@
import { MATERIAL_ICONS } from 'vscode-material-icons';
-import { type ZodObject, type ZodOptional, type ZodString, z } from 'zod';
+import {
+ ZodError,
+ type ZodIssue,
+ type ZodObject,
+ type ZodOptional,
+ type ZodString,
+ z,
+} from 'zod';
import {
MAX_DESCRIPTION_LENGTH,
MAX_SLUG_LENGTH,
@@ -25,26 +32,28 @@ export function executionMetaSchema(
},
) {
return z.object({
- date: z.string({ description: options.descriptionDate }),
- duration: z.number({ description: options.descriptionDuration }),
+ date: z.string().describe(options.descriptionDate),
+ duration: z.number().describe(options.descriptionDuration),
});
}
/** Schema for a slug of a categories, plugins or audits. */
export const slugSchema = z
- .string({ description: 'Unique ID (human-readable, URL-safe)' })
+ .string()
.regex(slugRegex, {
message:
'The slug has to follow the pattern [0-9a-z] followed by multiple optional groups of -[0-9a-z]. e.g. my-slug',
})
.max(MAX_SLUG_LENGTH, {
message: `The slug can be max ${MAX_SLUG_LENGTH} characters long`,
- });
+ })
+ .describe('Unique ID (human-readable, URL-safe)');
/** Schema for a general description property */
export const descriptionSchema = z
- .string({ description: 'Description (markdown)' })
+ .string()
.max(MAX_DESCRIPTION_LENGTH)
+ .describe('Description (markdown)')
.optional();
/* Schema for a URL */
@@ -58,29 +67,32 @@ export const docsUrlSchema = urlSchema
.catch(ctx => {
// if only URL validation fails, supress error since this metadata is optional anyway
if (
- ctx.error.errors.length === 1 &&
- ctx.error.errors[0]?.code === 'invalid_string' &&
- ctx.error.errors[0].validation === 'url'
+ ctx.issues.length === 1 &&
+ (ctx.issues[0]?.errors as ZodIssue[][])
+ .flat()
+ .some(
+ error => error.code === 'invalid_format' && error.format === 'url',
+ )
) {
- console.warn(`Ignoring invalid docsUrl: ${ctx.input}`);
+ console.warn(`Ignoring invalid docsUrl: ${ctx.value}`);
return '';
}
- throw ctx.error;
+ throw new ZodError(ctx.error.issues);
})
.describe('Documentation site');
/** Schema for a title of a plugin, category and audit */
export const titleSchema = z
- .string({ description: 'Descriptive name' })
- .max(MAX_TITLE_LENGTH);
+ .string()
+ .max(MAX_TITLE_LENGTH)
+ .describe('Descriptive name');
/** Schema for score of audit, category or group */
export const scoreSchema = z
- .number({
- description: 'Value between 0 and 1',
- })
+ .number()
.min(0)
- .max(1);
+ .max(1)
+ .describe('Value between 0 and 1');
/** Schema for a property indicating whether an entity is filtered out */
export const isSkippedSchema = z.boolean().optional();
@@ -103,23 +115,21 @@ export function metaSchema(options?: {
description,
isSkippedDescription,
} = options ?? {};
- return z.object(
- {
- title: titleDescription
- ? titleSchema.describe(titleDescription)
- : titleSchema,
- description: descriptionDescription
- ? descriptionSchema.describe(descriptionDescription)
- : descriptionSchema,
- docsUrl: docsUrlDescription
- ? docsUrlSchema.describe(docsUrlDescription)
- : docsUrlSchema,
- isSkipped: isSkippedDescription
- ? isSkippedSchema.describe(isSkippedDescription)
- : isSkippedSchema,
- },
- { description },
- );
+ const meta = z.object({
+ title: titleDescription
+ ? titleSchema.describe(titleDescription)
+ : titleSchema,
+ description: descriptionDescription
+ ? descriptionSchema.describe(descriptionDescription)
+ : descriptionSchema,
+ docsUrl: docsUrlDescription
+ ? docsUrlSchema.describe(docsUrlDescription)
+ : docsUrlSchema,
+ isSkipped: isSkippedDescription
+ ? isSkippedSchema.describe(isSkippedDescription)
+ : isSkippedSchema,
+ });
+ return description ? meta.describe(description) : meta;
}
/** Schema for a generalFilePath */
@@ -142,21 +152,21 @@ export const positiveIntSchema = z.number().int().positive();
export const nonnegativeNumberSchema = z.number().nonnegative();
-export function packageVersionSchema(options?: {
- versionDescription?: string;
- required?: TRequired;
-}) {
+export function packageVersionSchema<
+ TRequired extends boolean = false,
+>(options?: { versionDescription?: string; required?: TRequired }) {
const { versionDescription = 'NPM version of the package', required } =
options ?? {};
- const packageSchema = z.string({ description: 'NPM package name' });
- const versionSchema = z.string({ description: versionDescription });
- return z.object(
- {
+ const packageSchema = z.string().describe('NPM package name');
+ const versionSchema = z.string().describe(versionDescription);
+ return z
+ .object({
packageName: required ? packageSchema : packageSchema.optional(),
version: required ? versionSchema : versionSchema.optional(),
- },
- { description: 'NPM package name and version of a published package' },
- ) as ZodObject<{
+ })
+ .describe(
+ 'NPM package name and version of a published package',
+ ) as ZodObject<{
packageName: TRequired extends true ? ZodString : ZodOptional;
version: TRequired extends true ? ZodString : ZodOptional;
}>;
@@ -171,13 +181,12 @@ export function weightedRefSchema(
description: string,
slugDescription: string,
) {
- return z.object(
- {
+ return z
+ .object({
slug: slugSchema.describe(slugDescription),
weight: weightSchema.describe('Weight used to calculate score'),
- },
- { description },
- );
+ })
+ .describe(description);
}
export type WeightedRef = z.infer>;
@@ -185,37 +194,27 @@ export type WeightedRef = z.infer>;
export function scorableSchema>(
description: string,
refSchema: T,
- duplicateCheckFn: (metrics: z.infer[]) => false | string[],
- duplicateMessageFn: (metrics: z.infer[]) => string,
+ duplicateCheckFn: z.core.CheckFn[]>,
) {
- return z.object(
- {
+ return z
+ .object({
slug: slugSchema.describe('Human-readable unique ID, e.g. "performance"'),
refs: z
.array(refSchema)
.min(1, { message: 'In a category, there has to be at least one ref' })
// refs are unique
- .refine(
- refs => !duplicateCheckFn(refs),
- refs => ({
- message: duplicateMessageFn(refs),
- }),
- )
+ .check(duplicateCheckFn)
// category weights are correct
- .refine(hasNonZeroWeightedRef, refs => {
- const affectedRefs = refs.map(ref => ref.slug).join(', ');
- return {
- message: `In a category, there has to be at least one ref with weight > 0. Affected refs: ${affectedRefs}`,
- };
+ .refine(hasNonZeroWeightedRef, {
+ error: 'A category must have at least 1 ref with weight > 0.',
}),
- },
- { description },
- );
+ })
+ .describe(description);
}
-export const materialIconSchema = z.enum(MATERIAL_ICONS, {
- description: 'Icon from VSCode Material Icons extension',
-});
+export const materialIconSchema = z
+ .enum(MATERIAL_ICONS)
+ .describe('Icon from VSCode Material Icons extension');
export type MaterialIcon = z.infer;
type Ref = { weight: number };
@@ -224,12 +223,11 @@ function hasNonZeroWeightedRef(refs: Ref[]) {
return refs.reduce((acc, { weight }) => weight + acc, 0) !== 0;
}
-export const filePositionSchema = z.object(
- {
+export const filePositionSchema = z
+ .object({
startLine: positiveIntSchema.describe('Start line'),
startColumn: positiveIntSchema.describe('Start column').optional(),
endLine: positiveIntSchema.describe('End line').optional(),
endColumn: positiveIntSchema.describe('End column').optional(),
- },
- { description: 'Location in file' },
-);
+ })
+ .describe('Location in file');
diff --git a/packages/models/src/lib/implementation/schemas.unit.test.ts b/packages/models/src/lib/implementation/schemas.unit.test.ts
index 4370cc963..a72640350 100644
--- a/packages/models/src/lib/implementation/schemas.unit.test.ts
+++ b/packages/models/src/lib/implementation/schemas.unit.test.ts
@@ -61,6 +61,8 @@ describe('docsUrlSchema', () => {
});
it('should throw if not a string', () => {
- expect(() => docsUrlSchema.parse(false)).toThrow('invalid_type');
+ expect(() => docsUrlSchema.parse(false)).toThrow(
+ 'Invalid input: expected string, received boolean',
+ );
});
});
diff --git a/packages/models/src/lib/implementation/utils.ts b/packages/models/src/lib/implementation/utils.ts
index 2bd6142e9..250558516 100644
--- a/packages/models/src/lib/implementation/utils.ts
+++ b/packages/models/src/lib/implementation/utils.ts
@@ -43,11 +43,15 @@ export function hasMissingStrings(
return nonExisting.length === 0 ? false : nonExisting;
}
+export function formatSlugsList(slugs: string[]): string {
+ return slugs.map(slug => `"${slug}"`).join(', ');
+}
+
/**
* helper for error items
*/
export function errorItems(
- items: string[] | false,
+ items: string[],
transform: (itemArr: string[]) => string = itemArr => itemArr.join(', '),
): string {
return transform(items || []);
@@ -72,12 +76,10 @@ export function getMissingRefsForCategories(
}
const auditRefsFromCategory = categories.flatMap(({ refs }) =>
- refs
- .filter(({ type }) => type === 'audit')
- .map(({ plugin, slug }) => `${plugin}/${slug}`),
+ refs.filter(({ type }) => type === 'audit').map(formatRef),
);
- const auditRefsFromPlugins = plugins.flatMap(({ audits, slug: pluginSlug }) =>
- audits.map(({ slug }) => `${pluginSlug}/${slug}`),
+ const auditRefsFromPlugins = plugins.flatMap(({ audits, slug: plugin }) =>
+ audits.map(({ slug }) => formatRef({ type: 'audit', plugin, slug })),
);
const missingAuditRefs = hasMissingStrings(
auditRefsFromCategory,
@@ -85,15 +87,12 @@ export function getMissingRefsForCategories(
);
const groupRefsFromCategory = categories.flatMap(({ refs }) =>
- refs
- .filter(({ type }) => type === 'group')
- .map(({ plugin, slug }) => `${plugin}#${slug} (group)`),
+ refs.filter(({ type }) => type === 'group').map(formatRef),
);
- const groupRefsFromPlugins = plugins.flatMap(
- ({ groups, slug: pluginSlug }) =>
- Array.isArray(groups)
- ? groups.map(({ slug }) => `${pluginSlug}#${slug} (group)`)
- : [],
+ const groupRefsFromPlugins = plugins.flatMap(({ groups, slug: plugin }) =>
+ Array.isArray(groups)
+ ? groups.map(({ slug }) => formatRef({ type: 'group', plugin, slug }))
+ : [],
);
const missingGroupRefs = hasMissingStrings(
groupRefsFromCategory,
@@ -107,12 +106,27 @@ export function getMissingRefsForCategories(
return missingRefs.length > 0 ? missingRefs : false;
}
-export function missingRefsForCategoriesErrorMsg(
- categories: CategoryConfig[] | undefined,
- plugins: PluginConfig[] | PluginReport[],
-) {
+export function findMissingSlugsInCategoryRefs({
+ categories,
+ plugins,
+}: {
+ categories?: CategoryConfig[];
+ plugins: PluginConfig[] | PluginReport[];
+}) {
const missingRefs = getMissingRefsForCategories(categories, plugins);
- return `The following category references need to point to an audit or group: ${errorItems(
- missingRefs,
- )}`;
+ return (
+ missingRefs && {
+ message: `Category references audits or groups which don't exist: ${missingRefs.join(
+ ', ',
+ )}`,
+ }
+ );
+}
+
+export function formatRef(ref: {
+ type: 'audit' | 'group';
+ plugin: string;
+ slug: string;
+}): string {
+ return `${ref.type} "${ref.slug}" (plugin "${ref.plugin}")`;
}
diff --git a/packages/models/src/lib/issue.ts b/packages/models/src/lib/issue.ts
index c7184f140..378db354e 100644
--- a/packages/models/src/lib/issue.ts
+++ b/packages/models/src/lib/issue.ts
@@ -2,18 +2,19 @@ import { z } from 'zod';
import { MAX_ISSUE_MESSAGE_LENGTH } from './implementation/limits.js';
import { sourceFileLocationSchema } from './source.js';
-export const issueSeveritySchema = z.enum(['info', 'warning', 'error'], {
- description: 'Severity level',
-});
+export const issueSeveritySchema = z
+ .enum(['info', 'warning', 'error'])
+ .describe('Severity level');
export type IssueSeverity = z.infer;
-export const issueSchema = z.object(
- {
+
+export const issueSchema = z
+ .object({
message: z
- .string({ description: 'Descriptive error message' })
- .max(MAX_ISSUE_MESSAGE_LENGTH),
+ .string()
+ .max(MAX_ISSUE_MESSAGE_LENGTH)
+ .describe('Descriptive error message'),
severity: issueSeveritySchema,
source: sourceFileLocationSchema.optional(),
- },
- { description: 'Issue information' },
-);
+ })
+ .describe('Issue information');
export type Issue = z.infer;
diff --git a/packages/models/src/lib/issue.unit.test.ts b/packages/models/src/lib/issue.unit.test.ts
index 0ffcf9683..4a71f9250 100644
--- a/packages/models/src/lib/issue.unit.test.ts
+++ b/packages/models/src/lib/issue.unit.test.ts
@@ -39,7 +39,9 @@ describe('issueSchema', () => {
message: 'Use const instead of let.',
severity: 'critical',
}),
- ).toThrow('Invalid enum value');
+ ).toThrow(
+ String.raw`Invalid option: expected one of \"info\"|\"warning\"|\"error\"`,
+ );
});
it('should throw for invalid file position', () => {
@@ -52,6 +54,6 @@ describe('issueSchema', () => {
position: { startLine: 0, endLine: 3 },
},
} satisfies Issue),
- ).toThrow('Number must be greater than 0');
+ ).toThrow('Too small: expected number to be >0');
});
});
diff --git a/packages/models/src/lib/persist-config.unit.test.ts b/packages/models/src/lib/persist-config.unit.test.ts
index 9693217bc..77ca8157b 100644
--- a/packages/models/src/lib/persist-config.unit.test.ts
+++ b/packages/models/src/lib/persist-config.unit.test.ts
@@ -34,7 +34,7 @@ describe('persistConfigSchema', () => {
it('should throw for an invalid format', () => {
expect(() => persistConfigSchema.parse({ format: ['html'] })).toThrow(
- 'Invalid enum value',
+ String.raw`Invalid option: expected one of \"json\"|\"md\"`,
);
});
});
diff --git a/packages/models/src/lib/plugin-config.ts b/packages/models/src/lib/plugin-config.ts
index b977e7ec4..3697ed4cf 100644
--- a/packages/models/src/lib/plugin-config.ts
+++ b/packages/models/src/lib/plugin-config.ts
@@ -1,17 +1,18 @@
import { z } from 'zod';
-import { pluginAuditsSchema } from './audit.js';
-import { groupsSchema } from './group.js';
+import { type Audit, pluginAuditsSchema } from './audit.js';
+import { type Group, groupsSchema } from './group.js';
+import { createCheck } from './implementation/checks.js';
import {
materialIconSchema,
metaSchema,
packageVersionSchema,
slugSchema,
} from './implementation/schemas.js';
-import { errorItems, hasMissingStrings } from './implementation/utils.js';
+import { formatSlugsList, hasMissingStrings } from './implementation/utils.js';
import { runnerConfigSchema, runnerFunctionSchema } from './runner-config.js';
export const pluginContextSchema = z
- .record(z.unknown())
+ .record(z.string(), z.unknown())
.optional()
.describe('Plugin-specific context data for helpers');
export type PluginContext = z.infer;
@@ -39,33 +40,30 @@ export const pluginDataSchema = z.object({
groups: groupsSchema,
context: pluginContextSchema,
});
-type PluginData = z.infer;
export const pluginConfigSchema = pluginMetaSchema
.merge(pluginDataSchema)
- // every listed group ref points to an audit within the plugin
- .refine(
- pluginCfg => !getMissingRefsFromGroups(pluginCfg),
- pluginCfg => ({
- message: missingRefsFromGroupsErrorMsg(pluginCfg),
- }),
- );
+ .check(createCheck(findMissingSlugsInGroupRefs));
export type PluginConfig = z.infer;
-// helper for validator: every listed group ref points to an audit within the plugin
-function missingRefsFromGroupsErrorMsg(pluginCfg: PluginData) {
- const missingRefs = getMissingRefsFromGroups(pluginCfg);
- return `The following group references need to point to an existing audit in this plugin config: ${errorItems(
- missingRefs,
- )}`;
+// every listed group ref points to an audit within the plugin
+export function findMissingSlugsInGroupRefs<
+ T extends { audits: Audit[]; groups?: Group[] },
+>({ audits, groups = [] }: T) {
+ const missingSlugs = getAuditSlugsFromGroups(audits, groups);
+ return (
+ missingSlugs && {
+ message: `Group references audits which don't exist in this plugin: ${formatSlugsList(
+ missingSlugs,
+ )}`,
+ }
+ );
}
-function getMissingRefsFromGroups(pluginCfg: PluginData) {
+function getAuditSlugsFromGroups(audits: Audit[], groups: Group[]) {
return hasMissingStrings(
- pluginCfg.groups?.flatMap(({ refs: audits }) =>
- audits.map(({ slug: ref }) => ref),
- ) ?? [],
- pluginCfg.audits.map(({ slug }) => slug),
+ groups.flatMap(({ refs }) => refs.map(({ slug }) => slug)),
+ audits.map(({ slug }) => slug),
);
}
diff --git a/packages/models/src/lib/plugin-config.unit.test.ts b/packages/models/src/lib/plugin-config.unit.test.ts
index 56c0ddec5..f7409c92a 100644
--- a/packages/models/src/lib/plugin-config.unit.test.ts
+++ b/packages/models/src/lib/plugin-config.unit.test.ts
@@ -69,7 +69,7 @@ describe('pluginConfigSchema', () => {
],
} satisfies PluginConfig),
).toThrow(
- 'group references need to point to an existing audit in this plugin config: cyct',
+ String.raw`Group references audits which don't exist in this plugin: \"cyct\"`,
);
});
@@ -90,7 +90,7 @@ describe('pluginConfigSchema', () => {
],
} satisfies PluginConfig),
).toThrow(
- 'group references need to point to an existing audit in this plugin config: cyct',
+ String.raw`Group references audits which don't exist in this plugin: \"cyct\"`,
);
});
diff --git a/packages/models/src/lib/report.ts b/packages/models/src/lib/report.ts
index 72da71453..6042d2481 100644
--- a/packages/models/src/lib/report.ts
+++ b/packages/models/src/lib/report.ts
@@ -3,18 +3,17 @@ import { auditOutputSchema } from './audit-output.js';
import { auditSchema } from './audit.js';
import { categoryConfigSchema } from './category-config.js';
import { commitSchema } from './commit.js';
-import { type Group, groupSchema } from './group.js';
+import { groupSchema } from './group.js';
+import { createCheck } from './implementation/checks.js';
import {
executionMetaSchema,
packageVersionSchema,
} from './implementation/schemas.js';
+import { findMissingSlugsInCategoryRefs } from './implementation/utils.js';
import {
- errorItems,
- getMissingRefsForCategories,
- hasMissingStrings,
- missingRefsForCategoriesErrorMsg,
-} from './implementation/utils.js';
-import { pluginMetaSchema } from './plugin-config.js';
+ findMissingSlugsInGroupRefs,
+ pluginMetaSchema,
+} from './plugin-config.js';
export const auditReportSchema = auditSchema.merge(auditOutputSchema);
export type AuditReport = z.infer;
@@ -32,36 +31,10 @@ export const pluginReportSchema = pluginMetaSchema
groups: z.array(groupSchema).optional(),
}),
)
- .refine(
- pluginReport =>
- !getMissingRefsFromGroups(pluginReport.audits, pluginReport.groups ?? []),
- pluginReport => ({
- message: missingRefsFromGroupsErrorMsg(
- pluginReport.audits,
- pluginReport.groups ?? [],
- ),
- }),
- );
+ .check(createCheck(findMissingSlugsInGroupRefs));
export type PluginReport = z.infer;
-// every listed group ref points to an audit within the plugin report
-function missingRefsFromGroupsErrorMsg(audits: AuditReport[], groups: Group[]) {
- const missingRefs = getMissingRefsFromGroups(audits, groups);
- return `group references need to point to an existing audit in this plugin report: ${errorItems(
- missingRefs,
- )}`;
-}
-
-function getMissingRefsFromGroups(audits: AuditReport[], groups: Group[]) {
- return hasMissingStrings(
- groups.flatMap(({ refs: auditRefs }) =>
- auditRefs.map(({ slug: ref }) => ref),
- ),
- audits.map(({ slug }) => slug),
- );
-}
-
export const reportSchema = packageVersionSchema({
versionDescription: 'NPM version of the CLI',
required: true,
@@ -73,23 +46,15 @@ export const reportSchema = packageVersionSchema({
}),
)
.merge(
- z.object(
- {
- plugins: z.array(pluginReportSchema).min(1),
- categories: z.array(categoryConfigSchema).optional(),
- commit: commitSchema
- .describe('Git commit for which report was collected')
- .nullable(),
- },
- { description: 'Collect output data' },
- ),
- )
- .refine(
- ({ categories, plugins }) =>
- !getMissingRefsForCategories(categories, plugins),
- ({ categories, plugins }) => ({
- message: missingRefsForCategoriesErrorMsg(categories, plugins),
+ z.object({
+ plugins: z.array(pluginReportSchema).min(1),
+ categories: z.array(categoryConfigSchema).optional(),
+ commit: commitSchema
+ .describe('Git commit for which report was collected')
+ .nullable(),
}),
- );
+ )
+ .check(createCheck(findMissingSlugsInCategoryRefs))
+ .describe('Collect output data');
export type Report = z.infer;
diff --git a/packages/models/src/lib/report.unit.test.ts b/packages/models/src/lib/report.unit.test.ts
index 900532aad..e1ab2f916 100644
--- a/packages/models/src/lib/report.unit.test.ts
+++ b/packages/models/src/lib/report.unit.test.ts
@@ -160,7 +160,7 @@ describe('pluginReportSchema', () => {
],
} satisfies PluginReport),
).toThrow(
- 'group references need to point to an existing audit in this plugin report: perf-lighthouse',
+ String.raw`Group references audits which don't exist in this plugin: \"perf-lighthouse\"`,
);
});
});
@@ -266,7 +266,7 @@ describe('reportSchema', () => {
version: '1.0.1',
} satisfies Report),
).toThrow(
- 'category references need to point to an audit or group: vitest/vitest-unit-test',
+ String.raw`Category references audits or groups which don't exist: audit \"vitest-unit-test\" (plugin \"vitest\")`,
);
});
});
diff --git a/packages/models/src/lib/reports-diff.ts b/packages/models/src/lib/reports-diff.ts
index b02f61264..bc4802169 100644
--- a/packages/models/src/lib/reports-diff.ts
+++ b/packages/models/src/lib/reports-diff.ts
@@ -28,15 +28,14 @@ function makeArraysComparisonSchema<
TDiff extends typeof scorableDiffSchema,
TResult extends ZodTypeAny,
>(diffSchema: TDiff, resultSchema: TResult, description: string) {
- return z.object(
- {
+ return z
+ .object({
changed: z.array(diffSchema),
unchanged: z.array(resultSchema),
added: z.array(resultSchema),
removed: z.array(resultSchema),
- },
- { description },
- );
+ })
+ .describe(description);
}
const scorableMetaSchema = z.object({
diff --git a/packages/models/src/lib/runner-config.ts b/packages/models/src/lib/runner-config.ts
index 799da8063..a40a0b983 100644
--- a/packages/models/src/lib/runner-config.ts
+++ b/packages/models/src/lib/runner-config.ts
@@ -1,34 +1,33 @@
-import { z } from 'zod';
+import { z } from 'zod/v4';
import { auditOutputsSchema } from './audit-output.js';
+import { convertAsyncZodFunctionToSchema } from './implementation/function.js';
import { filePathSchema } from './implementation/schemas.js';
-export const outputTransformSchema = z
- .function()
- .args(z.unknown())
- .returns(z.union([auditOutputsSchema, z.promise(auditOutputsSchema)]));
-
+export const outputTransformSchema = convertAsyncZodFunctionToSchema(
+ z.function({
+ input: [z.unknown()],
+ output: z.union([auditOutputsSchema, z.promise(auditOutputsSchema)]),
+ }),
+);
export type OutputTransform = z.infer;
-export const runnerConfigSchema = z.object(
- {
- command: z.string({
- description: 'Shell command to execute',
- }),
- args: z.array(z.string({ description: 'Command arguments' })).optional(),
+export const runnerConfigSchema = z
+ .object({
+ command: z.string().describe('Shell command to execute'),
+ args: z.array(z.string()).describe('Command arguments').optional(),
outputFile: filePathSchema.describe('Runner output path'),
outputTransform: outputTransformSchema.optional(),
configFile: filePathSchema.describe('Runner config path').optional(),
- },
- {
- description: 'How to execute runner',
- },
-);
+ })
+ .describe('How to execute runner');
export type RunnerConfig = z.infer;
-export const runnerFunctionSchema = z
- .function()
- .returns(z.union([auditOutputsSchema, z.promise(auditOutputsSchema)]));
+export const runnerFunctionSchema = convertAsyncZodFunctionToSchema(
+ z.function({
+ output: z.union([auditOutputsSchema, z.promise(auditOutputsSchema)]),
+ }),
+);
export type RunnerFunction = z.infer;
diff --git a/packages/models/src/lib/runner-config.unit.test.ts b/packages/models/src/lib/runner-config.unit.test.ts
index 0be4b3406..747236042 100644
--- a/packages/models/src/lib/runner-config.unit.test.ts
+++ b/packages/models/src/lib/runner-config.unit.test.ts
@@ -64,7 +64,7 @@ describe('runnerFunctionSchema', () => {
it('should throw for a non-function argument', () => {
expect(() => runnerFunctionSchema.parse({ slug: 'configuration' })).toThrow(
- `Expected function,`,
+ 'Expected function, received object',
);
});
});
@@ -86,7 +86,7 @@ describe('outputTransformSchema', () => {
it('should throw for a non-function argument', () => {
expect(() => outputTransformSchema.parse('configuration')).toThrow(
- 'Expected function',
+ 'Expected function, received string',
);
});
});
diff --git a/packages/models/src/lib/source.ts b/packages/models/src/lib/source.ts
index 3b69e90ef..e1ca11e6a 100644
--- a/packages/models/src/lib/source.ts
+++ b/packages/models/src/lib/source.ts
@@ -4,12 +4,11 @@ import {
filePositionSchema,
} from './implementation/schemas.js';
-export const sourceFileLocationSchema = z.object(
- {
+export const sourceFileLocationSchema = z
+ .object({
file: filePathSchema.describe('Relative path to source file in Git repo'),
position: filePositionSchema.optional(),
- },
- { description: 'Source file location' },
-);
+ })
+ .describe('Source file location');
export type SourceFileLocation = z.infer;
diff --git a/packages/models/src/lib/table.ts b/packages/models/src/lib/table.ts
index 53d7cd1e4..80b36f3c8 100644
--- a/packages/models/src/lib/table.ts
+++ b/packages/models/src/lib/table.ts
@@ -1,9 +1,9 @@
import { z } from 'zod';
import { tableCellValueSchema } from './implementation/schemas.js';
-export const tableAlignmentSchema = z.enum(['left', 'center', 'right'], {
- description: 'Cell alignment',
-});
+export const tableAlignmentSchema = z
+ .enum(['left', 'center', 'right'])
+ .describe('Cell alignment');
export type TableAlignment = z.infer;
export const tableColumnPrimitiveSchema = tableAlignmentSchema;
@@ -16,31 +16,30 @@ export const tableColumnObjectSchema = z.object({
});
export type TableColumnObject = z.infer;
-export const tableRowObjectSchema = z.record(tableCellValueSchema, {
- description: 'Object row',
-});
+export const tableRowObjectSchema = z
+ .record(z.string(), tableCellValueSchema)
+ .describe('Object row');
export type TableRowObject = z.infer;
-export const tableRowPrimitiveSchema = z.array(tableCellValueSchema, {
- description: 'Primitive row',
-});
+export const tableRowPrimitiveSchema = z
+ .array(tableCellValueSchema)
+ .describe('Primitive row');
export type TableRowPrimitive = z.infer;
const tableSharedSchema = z.object({
title: z.string().optional().describe('Display title for table'),
});
-const tablePrimitiveSchema = tableSharedSchema.merge(
- z.object(
- {
+const tablePrimitiveSchema = tableSharedSchema
+ .merge(
+ z.object({
columns: z.array(tableAlignmentSchema).optional(),
rows: z.array(tableRowPrimitiveSchema),
- },
- { description: 'Table with primitive rows and optional alignment columns' },
- ),
-);
-const tableObjectSchema = tableSharedSchema.merge(
- z.object(
- {
+ }),
+ )
+ .describe('Table with primitive rows and optional alignment columns');
+const tableObjectSchema = tableSharedSchema
+ .merge(
+ z.object({
columns: z
.union([
z.array(tableAlignmentSchema),
@@ -48,14 +47,10 @@ const tableObjectSchema = tableSharedSchema.merge(
])
.optional(),
rows: z.array(tableRowObjectSchema),
- },
- {
- description:
- 'Table with object rows and optional alignment or object columns',
- },
- ),
-);
+ }),
+ )
+ .describe('Table with object rows and optional alignment or object columns');
export const tableSchema = (description = 'Table information') =>
- z.union([tablePrimitiveSchema, tableObjectSchema], { description });
+ z.union([tablePrimitiveSchema, tableObjectSchema]).describe(description);
export type Table = z.infer>;
diff --git a/packages/models/src/lib/table.unit.test.ts b/packages/models/src/lib/table.unit.test.ts
index 1e49a40d6..53974888f 100644
--- a/packages/models/src/lib/table.unit.test.ts
+++ b/packages/models/src/lib/table.unit.test.ts
@@ -23,7 +23,7 @@ describe('tableAlignmentSchema', () => {
it('should throw for a invalid enum', () => {
const alignment = 'crooked';
expect(() => tableAlignmentSchema.parse(alignment)).toThrow(
- 'invalid_enum_value',
+ String.raw`Invalid option: expected one of \"left\"|\"center\"|\"right\"`,
);
});
});
@@ -37,7 +37,7 @@ describe('tableColumnPrimitiveSchema', () => {
it('should throw for a invalid enum', () => {
const column = 'crooked';
expect(() => tableColumnPrimitiveSchema.parse(column)).toThrow(
- 'invalid_enum_value',
+ String.raw`Invalid option: expected one of \"left\"|\"center\"|\"right\"`,
);
});
});
@@ -63,7 +63,7 @@ describe('tableRowPrimitiveSchema', () => {
it('should throw for a invalid array', () => {
const row = [{}];
expect(() => tableRowPrimitiveSchema.parse(row)).toThrow(
- 'Expected string, received object',
+ 'Invalid input: expected string, received object',
);
});
});
@@ -115,7 +115,7 @@ describe('tableSchema', () => {
rows: [[[] as unknown as string]],
};
expect(() => tableSchema().parse(table)).toThrow(
- 'Expected string, received array',
+ 'Invalid input: expected string, received array',
);
});
@@ -124,7 +124,7 @@ describe('tableSchema', () => {
rows: [['1', { prop1: '2' }]],
};
expect(() => tableSchema().parse(table)).toThrow(
- 'Expected string, received object',
+ 'Invalid input: expected string, received object',
);
});
diff --git a/packages/models/src/lib/tree.ts b/packages/models/src/lib/tree.ts
index 45477b554..645193320 100644
--- a/packages/models/src/lib/tree.ts
+++ b/packages/models/src/lib/tree.ts
@@ -1,7 +1,10 @@
import { z } from 'zod';
import { filePositionSchema } from './implementation/schemas.js';
-const basicTreeNodeValuesSchema = z.record(z.union([z.number(), z.string()]));
+const basicTreeNodeValuesSchema = z.record(
+ z.string(),
+ z.union([z.number(), z.string()]),
+);
const basicTreeNodeDataSchema = z.object({
name: z.string().min(1).describe('Text label for node'),
values: basicTreeNodeValuesSchema
@@ -11,9 +14,12 @@ const basicTreeNodeDataSchema = z.object({
export const basicTreeNodeSchema: z.ZodType =
basicTreeNodeDataSchema.extend({
- children: z
- .lazy(() => z.array(basicTreeNodeSchema).optional())
- .describe('Direct descendants of this node (omit if leaf)'),
+ get children() {
+ return z
+ .array(basicTreeNodeSchema)
+ .optional()
+ .describe('Direct descendants of this node (omit if leaf)');
+ },
});
export type BasicTreeNode = z.infer & {
children?: BasicTreeNode[];
@@ -47,9 +53,12 @@ const coverageTreeNodeDataSchema = z.object({
export const coverageTreeNodeSchema: z.ZodType =
coverageTreeNodeDataSchema.extend({
- children: z
- .lazy(() => z.array(coverageTreeNodeSchema).optional())
- .describe('Files and folders contained in this folder (omit if file)'),
+ get children() {
+ return z
+ .array(coverageTreeNodeSchema)
+ .optional()
+ .describe('Files and folders contained in this folder (omit if file)');
+ },
});
export type CoverageTreeNode = z.infer & {
children?: CoverageTreeNode[];
diff --git a/packages/models/src/lib/upload-config.ts b/packages/models/src/lib/upload-config.ts
index cc36ac0d6..bc68d00db 100644
--- a/packages/models/src/lib/upload-config.ts
+++ b/packages/models/src/lib/upload-config.ts
@@ -3,19 +3,21 @@ import { slugSchema, urlSchema } from './implementation/schemas.js';
export const uploadConfigSchema = z.object({
server: urlSchema.describe('URL of deployed portal API'),
- apiKey: z.string({
- description:
+ apiKey: z
+ .string()
+ .describe(
'API key with write access to portal (use `process.env` for security)',
- }),
+ ),
organization: slugSchema.describe(
'Organization slug from Code PushUp portal',
),
project: slugSchema.describe('Project slug from Code PushUp portal'),
timeout: z
- .number({ description: 'Request timeout in minutes (default is 5)' })
+ .number()
.positive()
.int()
- .optional(),
+ .optional()
+ .describe('Request timeout in minutes (default is 5)'),
});
export type UploadConfig = z.infer;
diff --git a/packages/models/src/lib/upload-config.unit.test.ts b/packages/models/src/lib/upload-config.unit.test.ts
index 422ea67ac..38fd95cb5 100644
--- a/packages/models/src/lib/upload-config.unit.test.ts
+++ b/packages/models/src/lib/upload-config.unit.test.ts
@@ -21,7 +21,7 @@ describe('uploadConfigSchema', () => {
project: 'cli',
server: '-invalid-/url',
} satisfies UploadConfig),
- ).toThrow('Invalid url');
+ ).toThrow('Invalid URL');
});
it('should throw for a PascalCase organization name', () => {
diff --git a/packages/nx-plugin/package.json b/packages/nx-plugin/package.json
index 58385cd41..4e7626a3a 100644
--- a/packages/nx-plugin/package.json
+++ b/packages/nx-plugin/package.json
@@ -37,6 +37,6 @@
"@nx/devkit": "^17.0.0 || ^18.0.0 || ^19.0.0",
"ansis": "^3.3.0",
"nx": "^17.0.0 || ^18.0.0 || ^19.0.0",
- "zod": "^3.22.4"
+ "zod": "^4.0.5"
}
}
diff --git a/packages/nx-plugin/src/executors/internal/env.unit.test.ts b/packages/nx-plugin/src/executors/internal/env.unit.test.ts
index 548ef1e3b..4f5af94ab 100644
--- a/packages/nx-plugin/src/executors/internal/env.unit.test.ts
+++ b/packages/nx-plugin/src/executors/internal/env.unit.test.ts
@@ -31,10 +31,10 @@ describe('parseEnv', () => {
});
it('should throw for process.env.CP_TIMEOUT option < 0', () => {
- expect(() => parseEnv({ CP_TIMEOUT: '-1' })).toThrow('Invalid');
+ expect(() => parseEnv({ CP_TIMEOUT: '-1' })).toThrow('Invalid string');
});
it('should throw for invalid URL in process.env.CP_SERVER option', () => {
- expect(() => parseEnv({ CP_SERVER: 'httptpt' })).toThrow('Invalid url');
+ expect(() => parseEnv({ CP_SERVER: 'httptpt' })).toThrow('Invalid URL');
});
});
diff --git a/packages/plugin-coverage/package.json b/packages/plugin-coverage/package.json
index 71a23e216..7c80be6e0 100644
--- a/packages/plugin-coverage/package.json
+++ b/packages/plugin-coverage/package.json
@@ -39,7 +39,7 @@
"ansis": "^3.3.0",
"parse-lcov": "^1.0.4",
"yargs": "^17.7.2",
- "zod": "^3.22.4"
+ "zod": "^4.0.5"
},
"peerDependencies": {
"@nx/devkit": ">=17.0.0",
diff --git a/packages/plugin-coverage/src/lib/config.ts b/packages/plugin-coverage/src/lib/config.ts
index 6719939c4..4db726ed0 100644
--- a/packages/plugin-coverage/src/lib/config.ts
+++ b/packages/plugin-coverage/src/lib/config.ts
@@ -6,63 +6,57 @@ export type CoverageType = z.infer;
export const coverageResultSchema = z.union([
z.object({
resultsPath: z
- .string({
- description: 'Path to coverage results for Nx setup.',
- })
- .includes('lcov'),
+ .string()
+ .includes('lcov')
+ .describe('Path to coverage results for Nx setup.'),
pathToProject: z
- .string({
- description:
- 'Path from workspace root to project root. Necessary for LCOV reports which provide a relative path.',
- })
+ .string()
+ .describe(
+ 'Path from workspace root to project root. Necessary for LCOV reports which provide a relative path.',
+ )
.optional(),
}),
z
- .string({
- description: 'Path to coverage results for a single project setup.',
- })
- .includes('lcov'),
+ .string()
+ .includes('lcov')
+ .describe('Path to coverage results for a single project setup.'),
]);
export type CoverageResult = z.infer;
export const coveragePluginConfigSchema = z.object({
coverageToolCommand: z
.object({
- command: z
- .string({ description: 'Command to run coverage tool.' })
- .min(1),
+ command: z.string().min(1).describe('Command to run coverage tool.'),
args: z
- .array(z.string(), {
- description: 'Arguments to be passed to the coverage tool.',
- })
- .optional(),
+ .array(z.string())
+ .optional()
+ .describe('Arguments to be passed to the coverage tool.'),
})
.optional(),
continueOnCommandFail: z
- .boolean({
- description:
- 'Continue on coverage tool command failure or error. Defaults to true.',
- })
- .default(true),
+ .boolean()
+ .default(true)
+ .describe(
+ 'Continue on coverage tool command failure or error. Defaults to true.',
+ ),
coverageTypes: z
- .array(coverageTypeSchema, {
- description: 'Coverage types measured. Defaults to all available types.',
- })
+ .array(coverageTypeSchema)
.min(1)
- .default(['function', 'branch', 'line']),
+ .default(['function', 'branch', 'line'])
+ .describe('Coverage types measured. Defaults to all available types.'),
reports: z
- .array(coverageResultSchema, {
- description:
- 'Path to all code coverage report files. Only LCOV format is supported for now.',
- })
- .min(1),
+ .array(coverageResultSchema)
+ .min(1)
+ .describe(
+ 'Path to all code coverage report files. Only LCOV format is supported for now.',
+ ),
perfectScoreThreshold: z
- .number({
- description:
- 'Score will be 1 (perfect) for this coverage and above. Score range is 0 - 1.',
- })
+ .number()
.gt(0)
.max(1)
+ .describe(
+ 'Score will be 1 (perfect) for this coverage and above. Score range is 0 - 1.',
+ )
.optional(),
});
export type CoveragePluginConfig = z.input;
diff --git a/packages/plugin-coverage/src/lib/config.unit.test.ts b/packages/plugin-coverage/src/lib/config.unit.test.ts
index a5938fa19..517f8231a 100644
--- a/packages/plugin-coverage/src/lib/config.unit.test.ts
+++ b/packages/plugin-coverage/src/lib/config.unit.test.ts
@@ -71,7 +71,7 @@ describe('coveragePluginConfigSchema', () => {
coverageTypes: ['line'],
reports: ['coverage/cli/coverage-final.json'],
} satisfies CoveragePluginConfig),
- ).toThrow(/Invalid input: must include.+lcov/);
+ ).toThrow(String.raw`Invalid string: must include \"lcov\"`);
});
it('throws for missing command', () => {
diff --git a/packages/plugin-eslint/package.json b/packages/plugin-eslint/package.json
index a7aebf096..b53a77686 100644
--- a/packages/plugin-eslint/package.json
+++ b/packages/plugin-eslint/package.json
@@ -41,7 +41,7 @@
"@code-pushup/utils": "0.69.5",
"@code-pushup/models": "0.69.5",
"yargs": "^17.7.2",
- "zod": "^3.22.4"
+ "zod": "^4.0.5"
},
"peerDependencies": {
"@nx/devkit": ">=17.0.0",
diff --git a/packages/plugin-eslint/src/lib/config.ts b/packages/plugin-eslint/src/lib/config.ts
index 4a3133d2c..5f7cabeee 100644
--- a/packages/plugin-eslint/src/lib/config.ts
+++ b/packages/plugin-eslint/src/lib/config.ts
@@ -1,12 +1,13 @@
import { z } from 'zod';
import { toArray } from '@code-pushup/utils';
-const patternsSchema = z.union([z.string(), z.array(z.string()).min(1)], {
- description:
+const patternsSchema = z
+ .union([z.string(), z.array(z.string()).min(1)])
+ .describe(
'Lint target files. May contain file paths, directory paths or glob patterns',
-});
+ );
-const eslintrcSchema = z.string({ description: 'Path to ESLint config file' });
+const eslintrcSchema = z.string().describe('Path to ESLint config file');
const eslintTargetObjectSchema = z.object({
eslintrc: eslintrcSchema.optional(),
@@ -34,30 +35,26 @@ export type ESLintPluginRunnerConfig = {
slugs: string[];
};
-const customGroupRulesSchema = z.union(
- [
+const customGroupRulesSchema = z
+ .union([
z
.array(z.string())
.min(1, 'Custom group rules must contain at least 1 element'),
- z.record(z.string(), z.number()).refine(
- schema => Object.keys(schema).length > 0,
- () => ({
- code: 'too_small',
- message: 'Custom group rules must contain at least 1 element',
+ z
+ .record(z.string(), z.number())
+ .refine(schema => Object.keys(schema).length > 0, {
+ error: 'Custom group rules must contain at least 1 element',
}),
- ),
- ],
- {
- description:
- 'Array of rule IDs with equal weights or object mapping rule IDs to specific weights',
- },
-);
+ ])
+ .describe(
+ 'Array of rule IDs with equal weights or object mapping rule IDs to specific weights',
+ );
const customGroupSchema = z.object({
- slug: z.string({ description: 'Unique group identifier' }),
- title: z.string({ description: 'Group display title' }),
- description: z.string({ description: 'Group metadata' }).optional(),
- docsUrl: z.string({ description: 'Group documentation site' }).optional(),
+ slug: z.string().describe('Unique group identifier'),
+ title: z.string().describe('Group display title'),
+ description: z.string().describe('Group metadata').optional(),
+ docsUrl: z.string().describe('Group documentation site').optional(),
rules: customGroupRulesSchema,
});
export type CustomGroup = z.infer;
diff --git a/packages/plugin-eslint/src/lib/eslint-plugin.int.test.ts b/packages/plugin-eslint/src/lib/eslint-plugin.int.test.ts
index 245611811..7fee0ef8e 100644
--- a/packages/plugin-eslint/src/lib/eslint-plugin.int.test.ts
+++ b/packages/plugin-eslint/src/lib/eslint-plugin.int.test.ts
@@ -134,7 +134,7 @@ describe('eslintPlugin', () => {
groups: [{ slug: 'type-safety', title: 'Type safety', rules: [] }],
},
),
- ).rejects.toThrow(/Custom group rules must contain at least 1 element/);
+ ).rejects.toThrow('Invalid input');
await expect(
eslintPlugin(
{
@@ -145,14 +145,14 @@ describe('eslintPlugin', () => {
groups: [{ slug: 'type-safety', title: 'Type safety', rules: {} }],
},
),
- ).rejects.toThrow(/Custom group rules must contain at least 1 element/);
+ ).rejects.toThrow('Invalid input');
});
it('should throw when invalid parameters provided', async () => {
await expect(
// @ts-expect-error simulating invalid non-TS config
eslintPlugin({ eslintrc: '.eslintrc.json' }),
- ).rejects.toThrow(/Invalid input/);
+ ).rejects.toThrow('Invalid input');
});
it("should throw if eslintrc file doesn't exist", async () => {
diff --git a/packages/plugin-js-packages/package.json b/packages/plugin-js-packages/package.json
index db7c84e53..45428c1cb 100644
--- a/packages/plugin-js-packages/package.json
+++ b/packages/plugin-js-packages/package.json
@@ -42,6 +42,6 @@
"build-md": "^0.4.1",
"semver": "^7.6.0",
"yargs": "^17.7.2",
- "zod": "^3.22.4"
+ "zod": "^4.0.5"
}
}
diff --git a/packages/plugin-js-packages/src/lib/config.ts b/packages/plugin-js-packages/src/lib/config.ts
index 75d6c247b..37f79b687 100644
--- a/packages/plugin-js-packages/src/lib/config.ts
+++ b/packages/plugin-js-packages/src/lib/config.ts
@@ -53,12 +53,12 @@ export function fillAuditLevelMapping(
export const jsPackagesPluginConfigSchema = z.object({
checks: z
- .array(packageCommandSchema, {
- description:
- 'Package manager commands to be run. Defaults to both audit and outdated.',
- })
+ .array(packageCommandSchema)
.min(1)
- .default(['audit', 'outdated']),
+ .default(['audit', 'outdated'])
+ .describe(
+ 'Package manager commands to be run. Defaults to both audit and outdated.',
+ ),
packageManager: packageManagerIdSchema
.describe('Package manager to be used.')
.optional(),
@@ -67,12 +67,12 @@ export const jsPackagesPluginConfigSchema = z.object({
.min(1)
.default(['prod', 'dev']),
auditLevelMapping: z
- .record(packageAuditLevelSchema, issueSeveritySchema, {
- description:
- 'Mapping of audit levels to issue severity. Custom mapping or overrides may be entered manually, otherwise has a default preset.',
- })
+ .partialRecord(packageAuditLevelSchema, issueSeveritySchema)
.default(defaultAuditLevelMapping)
- .transform(fillAuditLevelMapping),
+ .transform(fillAuditLevelMapping)
+ .describe(
+ 'Mapping of audit levels to issue severity. Custom mapping or overrides may be entered manually, otherwise has a default preset.',
+ ),
packageJsonPath: packageJsonPathSchema,
});
diff --git a/packages/plugin-jsdocs/package.json b/packages/plugin-jsdocs/package.json
index 2a7a3fdad..9506691af 100644
--- a/packages/plugin-jsdocs/package.json
+++ b/packages/plugin-jsdocs/package.json
@@ -37,7 +37,7 @@
"dependencies": {
"@code-pushup/models": "0.69.5",
"@code-pushup/utils": "0.69.5",
- "zod": "^3.22.4",
+ "zod": "^4.0.5",
"ts-morph": "^24.0.0"
}
}
diff --git a/packages/plugin-jsdocs/src/lib/config.ts b/packages/plugin-jsdocs/src/lib/config.ts
index 2b793591d..cd139c50c 100644
--- a/packages/plugin-jsdocs/src/lib/config.ts
+++ b/packages/plugin-jsdocs/src/lib/config.ts
@@ -1,8 +1,8 @@
import { z } from 'zod';
-const patternsSchema = z.union([z.string(), z.array(z.string()).min(1)], {
- description: 'Glob pattern to match source files to evaluate.',
-});
+const patternsSchema = z
+ .union([z.string(), z.array(z.string()).min(1)])
+ .describe('Glob pattern to match source files to evaluate.');
const jsDocsTargetObjectSchema = z
.object({
diff --git a/packages/plugin-jsdocs/src/lib/config.unit.test.ts b/packages/plugin-jsdocs/src/lib/config.unit.test.ts
index 38c3ddc42..d77dd19ff 100644
--- a/packages/plugin-jsdocs/src/lib/config.unit.test.ts
+++ b/packages/plugin-jsdocs/src/lib/config.unit.test.ts
@@ -49,7 +49,7 @@ describe('JsDocsPlugin Configuration', () => {
jsDocsPluginConfigSchema.parse({
patterns: 123,
}),
- ).toThrow('Expected array');
+ ).toThrow('Invalid input');
});
});
@@ -85,7 +85,7 @@ describe('JsDocsPlugin Configuration', () => {
onlyAudits: 'functions-coverage',
patterns: ['src/**/*.ts'],
}),
- ).toThrow('Expected array');
+ ).toThrow('Invalid input');
});
it('throws for array with non-string elements', () => {
@@ -94,7 +94,7 @@ describe('JsDocsPlugin Configuration', () => {
onlyAudits: [123, true],
patterns: ['src/**/*.ts'],
}),
- ).toThrow('Expected string, received number');
+ ).toThrow('Invalid input');
});
});
@@ -130,7 +130,7 @@ describe('JsDocsPlugin Configuration', () => {
skipAudits: 'functions-coverage',
patterns: ['src/**/*.ts'],
}),
- ).toThrow('Expected array');
+ ).toThrow('Invalid input');
});
it('throws for array with non-string elements', () => {
@@ -139,7 +139,7 @@ describe('JsDocsPlugin Configuration', () => {
skipAudits: [123, true],
patterns: ['src/**/*.ts'],
}),
- ).toThrow('Expected string');
+ ).toThrow('Invalid input');
});
});
});
diff --git a/packages/plugin-jsdocs/src/lib/jsdocs-plugin.unit.test.ts b/packages/plugin-jsdocs/src/lib/jsdocs-plugin.unit.test.ts
index 1be2c11fb..e6ec0b74f 100644
--- a/packages/plugin-jsdocs/src/lib/jsdocs-plugin.unit.test.ts
+++ b/packages/plugin-jsdocs/src/lib/jsdocs-plugin.unit.test.ts
@@ -43,11 +43,9 @@ describe('jsDocsPlugin', () => {
it('should throw for invalid plugin options', () => {
expect(() =>
- jsDocsPlugin({
- // @ts-expect-error testing invalid config
- patterns: 123,
- }),
- ).toThrow('Expected array, received number');
+ // @ts-expect-error testing invalid config
+ jsDocsPlugin({ patterns: 123 }),
+ ).toThrow('Invalid input');
});
it('should filter groups', () => {
diff --git a/packages/plugin-typescript/package.json b/packages/plugin-typescript/package.json
index 323c16a4e..4ccf5c0aa 100644
--- a/packages/plugin-typescript/package.json
+++ b/packages/plugin-typescript/package.json
@@ -25,7 +25,7 @@
"dependencies": {
"@code-pushup/models": "0.69.5",
"@code-pushup/utils": "0.69.5",
- "zod": "^3.23.8"
+ "zod": "^4.0.5"
},
"peerDependencies": {
"typescript": ">=4.0.0"
diff --git a/packages/plugin-typescript/src/lib/schema.ts b/packages/plugin-typescript/src/lib/schema.ts
index db8a3a395..ceb9c8959 100644
--- a/packages/plugin-typescript/src/lib/schema.ts
+++ b/packages/plugin-typescript/src/lib/schema.ts
@@ -8,14 +8,12 @@ const auditSlugs = AUDITS.map(({ slug }) => slug) as [
];
export const typescriptPluginConfigSchema = z.object({
tsconfig: z
- .string({
- description: 'Path to a tsconfig file (default is tsconfig.json)',
- })
- .default(DEFAULT_TS_CONFIG),
+ .string()
+ .default(DEFAULT_TS_CONFIG)
+ .describe(`Path to a tsconfig file (default is ${DEFAULT_TS_CONFIG})`),
onlyAudits: z
- .array(z.enum(auditSlugs), {
- description: 'Filters TypeScript compiler errors by diagnostic codes',
- })
+ .array(z.enum(auditSlugs))
+ .describe('Filters TypeScript compiler errors by diagnostic codes')
.optional(),
});
diff --git a/packages/plugin-typescript/src/lib/schema.unit.test.ts b/packages/plugin-typescript/src/lib/schema.unit.test.ts
index b23fe6a89..c16f8fed9 100644
--- a/packages/plugin-typescript/src/lib/schema.unit.test.ts
+++ b/packages/plugin-typescript/src/lib/schema.unit.test.ts
@@ -55,7 +55,9 @@ describe('typescriptPluginConfigSchema', () => {
tsconfig,
onlyAudits: [123, true],
}),
- ).toThrow('invalid_type');
+ ).toThrow(
+ String.raw`Invalid option: expected one of \"syntax-errors\"|\"semantic-errors\"|`,
+ );
});
it('throws for unknown audit slug', () => {
@@ -64,6 +66,8 @@ describe('typescriptPluginConfigSchema', () => {
tsconfig,
onlyAudits: ['unknown-audit'],
}),
- ).toThrow(/unknown-audit/);
+ ).toThrow(
+ String.raw`Invalid option: expected one of \"syntax-errors\"|\"semantic-errors\"|`,
+ );
});
});
diff --git a/packages/utils/package.json b/packages/utils/package.json
index 10af41a00..f6d266c03 100644
--- a/packages/utils/package.json
+++ b/packages/utils/package.json
@@ -37,7 +37,6 @@
"multi-progress-bars": "^5.0.3",
"semver": "^7.6.0",
"simple-git": "^3.20.0",
- "zod": "^3.23.8",
- "zod-validation-error": "^3.4.0"
+ "zod": "^4.0.5"
}
}
diff --git a/packages/utils/src/lib/zod-validation.ts b/packages/utils/src/lib/zod-validation.ts
index 57a15c638..dec02d5d9 100644
--- a/packages/utils/src/lib/zod-validation.ts
+++ b/packages/utils/src/lib/zod-validation.ts
@@ -1,11 +1,6 @@
-import { bold, red } from 'ansis';
+import { bold } from 'ansis';
import path from 'node:path';
-import type { z } from 'zod';
-import {
- type MessageBuilder,
- fromError,
- isZodErrorLike,
-} from 'zod-validation-error';
+import { ZodError, z } from 'zod';
type SchemaValidationContext = {
schemaType: string;
@@ -15,43 +10,16 @@ type SchemaValidationContext = {
export class SchemaValidationError extends Error {
constructor(
{ schemaType, sourcePath }: SchemaValidationContext,
- error: Error,
+ error: ZodError,
) {
- const validationError = fromError(error, {
- messageBuilder: zodErrorMessageBuilder,
- });
+ const formattedError = z.prettifyError(error);
const pathDetails = sourcePath
? ` in ${bold(path.relative(process.cwd(), sourcePath))}`
: '';
- super(
- `Failed parsing ${schemaType}${pathDetails}.\n\n${validationError.message}`,
- );
+ super(`Failed parsing ${schemaType}${pathDetails}.\n\n${formattedError}`);
}
}
-export function formatErrorPath(errorPath: (string | number)[]): string {
- return errorPath
- .map((key, index) => {
- if (typeof key === 'number') {
- return `[${key}]`;
- }
- return index > 0 ? `.${key}` : key;
- })
- .join('');
-}
-
-const zodErrorMessageBuilder: MessageBuilder = issues =>
- issues
- .map(issue => {
- const formattedMessage = red(`${bold(issue.code)}: ${issue.message}`);
- const formattedPath = formatErrorPath(issue.path);
- if (formattedPath) {
- return `Validation error at ${bold(formattedPath)}\n${formattedMessage}\n`;
- }
- return `${formattedMessage}\n`;
- })
- .join('\n');
-
export function parseSchema(
schema: T,
data: z.input,
@@ -60,7 +28,7 @@ export function parseSchema(
try {
return schema.parse(data);
} catch (error) {
- if (isZodErrorLike(error)) {
+ if (error instanceof ZodError) {
throw new SchemaValidationError({ schemaType, sourcePath }, error);
}
throw error;
diff --git a/packages/utils/src/lib/zod-validation.unit.test.ts b/packages/utils/src/lib/zod-validation.unit.test.ts
deleted file mode 100644
index 2c9725a4d..000000000
--- a/packages/utils/src/lib/zod-validation.unit.test.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { formatErrorPath } from './zod-validation.js';
-
-describe('formatErrorPath', () => {
- it.each([
- [['categories', 1, 'slug'], 'categories[1].slug'],
- [['plugins', 2, 'groups', 0, 'refs'], 'plugins[2].groups[0].refs'],
- [['refs', 0, 'slug'], 'refs[0].slug'],
- [['categories'], 'categories'],
- [[], ''],
- [['path', 5], 'path[5]'],
- ])('should format error path %j as %j', (input, expected) => {
- expect(formatErrorPath(input)).toBe(expected);
- });
-});