diff --git a/package-lock.json b/package-lock.json
index d869bd98a6..22b860efa1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -153,7 +153,7 @@
"typescript": "^5.4.4",
"unist-util-remove": "^4.0.0",
"unist-util-visit-parents": "6.0.1",
- "vitest": "1.5.0",
+ "vitest": "1.6.0",
"website-scraper": "^5.3.1"
},
"engines": {
@@ -2669,9 +2669,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.3.tgz",
- "integrity": "sha512-X9alQ3XM6I9IlSlmC8ddAvMSyG1WuHk5oUnXGw+yUBs3BFoTizmG1La/Gr8fVJvDWAq+zlYTZ9DBgrlKRVY06g==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz",
+ "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==",
"cpu": [
"arm"
],
@@ -2682,9 +2682,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.3.tgz",
- "integrity": "sha512-eQK5JIi+POhFpzk+LnjKIy4Ks+pwJ+NXmPxOCSvOKSNRPONzKuUvWE+P9JxGZVxrtzm6BAYMaL50FFuPe0oWMQ==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz",
+ "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==",
"cpu": [
"arm64"
],
@@ -2695,9 +2695,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.3.tgz",
- "integrity": "sha512-Od4vE6f6CTT53yM1jgcLqNfItTsLt5zE46fdPaEmeFHvPs5SjZYlLpHrSiHEKR1+HdRfxuzXHjDOIxQyC3ptBA==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz",
+ "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==",
"cpu": [
"arm64"
],
@@ -2708,9 +2708,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.3.tgz",
- "integrity": "sha512-0IMAO21axJeNIrvS9lSe/PGthc8ZUS+zC53O0VhF5gMxfmcKAP4ESkKOCwEi6u2asUrt4mQv2rjY8QseIEb1aw==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz",
+ "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==",
"cpu": [
"x64"
],
@@ -2721,9 +2721,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.3.tgz",
- "integrity": "sha512-ge2DC7tHRHa3caVEoSbPRJpq7azhG+xYsd6u2MEnJ6XzPSzQsTKyXvh6iWjXRf7Rt9ykIUWHtl0Uz3T6yXPpKw==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz",
+ "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==",
"cpu": [
"arm"
],
@@ -2734,9 +2734,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.14.3.tgz",
- "integrity": "sha512-ljcuiDI4V3ySuc7eSk4lQ9wU8J8r8KrOUvB2U+TtK0TiW6OFDmJ+DdIjjwZHIw9CNxzbmXY39wwpzYuFDwNXuw==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz",
+ "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==",
"cpu": [
"arm"
],
@@ -2747,9 +2747,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.3.tgz",
- "integrity": "sha512-Eci2us9VTHm1eSyn5/eEpaC7eP/mp5n46gTRB3Aar3BgSvDQGJZuicyq6TsH4HngNBgVqC5sDYxOzTExSU+NjA==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz",
+ "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==",
"cpu": [
"arm64"
],
@@ -2760,9 +2760,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.3.tgz",
- "integrity": "sha512-UrBoMLCq4E92/LCqlh+blpqMz5h1tJttPIniwUgOFJyjWI1qrtrDhhpHPuFxULlUmjFHfloWdixtDhSxJt5iKw==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz",
+ "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==",
"cpu": [
"arm64"
],
@@ -2773,9 +2773,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.3.tgz",
- "integrity": "sha512-5aRjvsS8q1nWN8AoRfrq5+9IflC3P1leMoy4r2WjXyFqf3qcqsxRCfxtZIV58tCxd+Yv7WELPcO9mY9aeQyAmw==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz",
+ "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==",
"cpu": [
"ppc64"
],
@@ -2786,9 +2786,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.3.tgz",
- "integrity": "sha512-sk/Qh1j2/RJSX7FhEpJn8n0ndxy/uf0kI/9Zc4b1ELhqULVdTfN6HL31CDaTChiBAOgLcsJ1sgVZjWv8XNEsAQ==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz",
+ "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==",
"cpu": [
"riscv64"
],
@@ -2799,9 +2799,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.3.tgz",
- "integrity": "sha512-jOO/PEaDitOmY9TgkxF/TQIjXySQe5KVYB57H/8LRP/ux0ZoO8cSHCX17asMSv3ruwslXW/TLBcxyaUzGRHcqg==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz",
+ "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==",
"cpu": [
"s390x"
],
@@ -2812,9 +2812,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.3.tgz",
- "integrity": "sha512-8ybV4Xjy59xLMyWo3GCfEGqtKV5M5gCSrZlxkPGvEPCGDLNla7v48S662HSGwRd6/2cSneMQWiv+QzcttLrrOA==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz",
+ "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==",
"cpu": [
"x64"
],
@@ -2825,9 +2825,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.3.tgz",
- "integrity": "sha512-s+xf1I46trOY10OqAtZ5Rm6lzHre/UiLA1J2uOhCFXWkbZrJRkYBPO6FhvGfHmdtQ3Bx793MNa7LvoWFAm93bg==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz",
+ "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==",
"cpu": [
"x64"
],
@@ -2838,9 +2838,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.3.tgz",
- "integrity": "sha512-+4h2WrGOYsOumDQ5S2sYNyhVfrue+9tc9XcLWLh+Kw3UOxAvrfOrSMFon60KspcDdytkNDh7K2Vs6eMaYImAZg==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz",
+ "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==",
"cpu": [
"arm64"
],
@@ -2851,9 +2851,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.3.tgz",
- "integrity": "sha512-T1l7y/bCeL/kUwh9OD4PQT4aM7Bq43vX05htPJJ46RTI4r5KNt6qJRzAfNfM+OYMNEVBWQzR2Gyk+FXLZfogGw==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz",
+ "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==",
"cpu": [
"ia32"
],
@@ -2864,9 +2864,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.3.tgz",
- "integrity": "sha512-/BypzV0H1y1HzgYpxqRaXGBRqfodgoBBCcsrujT6QRcakDQdfU+Lq9PENPh5jB4I44YWq+0C2eHsHya+nZY1sA==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz",
+ "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==",
"cpu": [
"x64"
],
@@ -3682,13 +3682,13 @@
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
},
"node_modules/@vitest/expect": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.0.tgz",
- "integrity": "sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz",
+ "integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==",
"dev": true,
"dependencies": {
- "@vitest/spy": "1.5.0",
- "@vitest/utils": "1.5.0",
+ "@vitest/spy": "1.6.0",
+ "@vitest/utils": "1.6.0",
"chai": "^4.3.10"
},
"funding": {
@@ -3696,12 +3696,12 @@
}
},
"node_modules/@vitest/runner": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.0.tgz",
- "integrity": "sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz",
+ "integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==",
"dev": true,
"dependencies": {
- "@vitest/utils": "1.5.0",
+ "@vitest/utils": "1.6.0",
"p-limit": "^5.0.0",
"pathe": "^1.1.1"
},
@@ -3737,9 +3737,9 @@
}
},
"node_modules/@vitest/snapshot": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.0.tgz",
- "integrity": "sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz",
+ "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==",
"dev": true,
"dependencies": {
"magic-string": "^0.30.5",
@@ -3751,9 +3751,9 @@
}
},
"node_modules/@vitest/spy": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.0.tgz",
- "integrity": "sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz",
+ "integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==",
"dev": true,
"dependencies": {
"tinyspy": "^2.2.0"
@@ -3763,9 +3763,9 @@
}
},
"node_modules/@vitest/utils": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.0.tgz",
- "integrity": "sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz",
+ "integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==",
"dev": true,
"dependencies": {
"diff-sequences": "^29.6.3",
@@ -9266,15 +9266,12 @@
}
},
"node_modules/magic-string": {
- "version": "0.30.9",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.9.tgz",
- "integrity": "sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==",
+ "version": "0.30.10",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
+ "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
- },
- "engines": {
- "node": ">=12"
}
},
"node_modules/magic-string/node_modules/@jridgewell/sourcemap-codec": {
@@ -12588,9 +12585,9 @@
}
},
"node_modules/rollup": {
- "version": "4.14.3",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.3.tgz",
- "integrity": "sha512-ag5tTQKYsj1bhrFC9+OEWqb5O6VYgtQDO9hPDBMmIbePwhfSr+ExlcU741t8Dhw5DkPCQf6noz0jb36D6W9/hw==",
+ "version": "4.17.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz",
+ "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==",
"dev": true,
"dependencies": {
"@types/estree": "1.0.5"
@@ -12603,22 +12600,22 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.14.3",
- "@rollup/rollup-android-arm64": "4.14.3",
- "@rollup/rollup-darwin-arm64": "4.14.3",
- "@rollup/rollup-darwin-x64": "4.14.3",
- "@rollup/rollup-linux-arm-gnueabihf": "4.14.3",
- "@rollup/rollup-linux-arm-musleabihf": "4.14.3",
- "@rollup/rollup-linux-arm64-gnu": "4.14.3",
- "@rollup/rollup-linux-arm64-musl": "4.14.3",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.14.3",
- "@rollup/rollup-linux-riscv64-gnu": "4.14.3",
- "@rollup/rollup-linux-s390x-gnu": "4.14.3",
- "@rollup/rollup-linux-x64-gnu": "4.14.3",
- "@rollup/rollup-linux-x64-musl": "4.14.3",
- "@rollup/rollup-win32-arm64-msvc": "4.14.3",
- "@rollup/rollup-win32-ia32-msvc": "4.14.3",
- "@rollup/rollup-win32-x64-msvc": "4.14.3",
+ "@rollup/rollup-android-arm-eabi": "4.17.2",
+ "@rollup/rollup-android-arm64": "4.17.2",
+ "@rollup/rollup-darwin-arm64": "4.17.2",
+ "@rollup/rollup-darwin-x64": "4.17.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.17.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.17.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.17.2",
+ "@rollup/rollup-linux-arm64-musl": "4.17.2",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.17.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.17.2",
+ "@rollup/rollup-linux-x64-gnu": "4.17.2",
+ "@rollup/rollup-linux-x64-musl": "4.17.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.17.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.17.2",
+ "@rollup/rollup-win32-x64-msvc": "4.17.2",
"fsevents": "~2.3.2"
}
},
@@ -14409,9 +14406,9 @@
}
},
"node_modules/vite": {
- "version": "5.2.8",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz",
- "integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==",
+ "version": "5.2.11",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz",
+ "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==",
"dev": true,
"dependencies": {
"esbuild": "^0.20.1",
@@ -14464,9 +14461,9 @@
}
},
"node_modules/vite-node": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.0.tgz",
- "integrity": "sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz",
+ "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==",
"dev": true,
"dependencies": {
"cac": "^6.7.14",
@@ -14934,16 +14931,16 @@
}
},
"node_modules/vitest": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.0.tgz",
- "integrity": "sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz",
+ "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==",
"dev": true,
"dependencies": {
- "@vitest/expect": "1.5.0",
- "@vitest/runner": "1.5.0",
- "@vitest/snapshot": "1.5.0",
- "@vitest/spy": "1.5.0",
- "@vitest/utils": "1.5.0",
+ "@vitest/expect": "1.6.0",
+ "@vitest/runner": "1.6.0",
+ "@vitest/snapshot": "1.6.0",
+ "@vitest/spy": "1.6.0",
+ "@vitest/utils": "1.6.0",
"acorn-walk": "^8.3.2",
"chai": "^4.3.10",
"debug": "^4.3.4",
@@ -14957,7 +14954,7 @@
"tinybench": "^2.5.1",
"tinypool": "^0.8.3",
"vite": "^5.0.0",
- "vite-node": "1.5.0",
+ "vite-node": "1.6.0",
"why-is-node-running": "^2.2.2"
},
"bin": {
@@ -14972,8 +14969,8 @@
"peerDependencies": {
"@edge-runtime/vm": "*",
"@types/node": "^18.0.0 || >=20.0.0",
- "@vitest/browser": "1.5.0",
- "@vitest/ui": "1.5.0",
+ "@vitest/browser": "1.6.0",
+ "@vitest/ui": "1.6.0",
"happy-dom": "*",
"jsdom": "*"
},
diff --git a/package.json b/package.json
index dc39d52c67..2ccc5044ff 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,7 @@
"fixture-test": "cross-env ROOT=src/fixtures/fixtures npm test -- src/fixtures/tests",
"index": "tsx src/search/scripts/index/index.ts",
"index-elasticsearch": "node src/search/scripts/index-elasticsearch.js",
- "index-test-fixtures": "npm run index-elasticsearch -- -l en -l ja -V ghec -V dotcom --index-prefix tests -- src/search/tests/fixtures/search-indexes",
+ "index-test-fixtures": "./src/search/scripts/index-test-fixtures.sh",
"lint": "eslint '**/*.{js,mjs,ts,tsx}'",
"lint-content": "node src/content-linter/scripts/lint-content.js",
"lint-translation": "vitest src/content-linter/tests/lint-files.js",
@@ -339,7 +339,7 @@
"typescript": "^5.4.4",
"unist-util-remove": "^4.0.0",
"unist-util-visit-parents": "6.0.1",
- "vitest": "1.5.0",
+ "vitest": "1.6.0",
"website-scraper": "^5.3.1"
},
"overrides": {},
diff --git a/src/search/middleware/es-search.js b/src/search/middleware/es-search.js
index 928cb7696c..508af5d54e 100644
--- a/src/search/middleware/es-search.js
+++ b/src/search/middleware/es-search.js
@@ -183,6 +183,48 @@ export async function getSearchResults({
return { meta, hits }
}
+export async function getAutocompleteSearchResults({ indexName, query, size }) {
+ const client = getClient()
+
+ const matchQueries = getAutocompleteMatchQueries(query.trim(), {
+ fuzzy: {
+ minLength: 3,
+ maxLength: 20,
+ },
+ })
+ const matchQuery = {
+ bool: {
+ should: matchQueries,
+ },
+ }
+
+ const highlight = getHighlightConfiguration(query, ['term'])
+
+ const searchQuery = {
+ index: indexName,
+ highlight,
+ size,
+ query: matchQuery,
+ // Send absolutely minimal from Elasticsearch to here. Less data => faster.
+ _source_includes: ['term'],
+ }
+ const result = await client.search(searchQuery)
+
+ const hitsAll = result.hits
+ const hits = hitsAll.hits.map((hit) => {
+ return {
+ term: hit._source.term,
+ highlights: (hit.highlight && hit.highlight.term) || [],
+ }
+ })
+
+ const meta = {
+ found: hitsAll.total,
+ }
+
+ return { meta, hits }
+}
+
function getMatchQueries(query, { usePrefixSearch, fuzzy }) {
const BOOST_PHRASE = 10.0
const BOOST_TITLE = 4.0
@@ -371,6 +413,46 @@ function getMatchQueries(query, { usePrefixSearch, fuzzy }) {
return matchQueries
}
+function getAutocompleteMatchQueries(query, { fuzzy }) {
+ const BOOST_PHRASE = 4.0
+ const BOOST_REGULAR = 2.0
+ const BOOST_FUZZY = 0.1 // make it always last in ranking
+ const matchQueries = []
+
+ // If the query input is multiple words, it's good to know because you can
+ // make the query do `match_phrase` and you can make `match` query
+ // with the `AND` operator (`OR` is the default).
+ const isMultiWordQuery = query.includes(' ') || query.includes('-')
+
+ if (isMultiWordQuery) {
+ matchQueries.push({
+ match_phrase_prefix: {
+ term: {
+ query,
+ boost: BOOST_PHRASE,
+ },
+ },
+ })
+ }
+ matchQueries.push({
+ match_bool_prefix: {
+ term: {
+ query,
+ boost: BOOST_REGULAR,
+ },
+ },
+ })
+ if (query.length > fuzzy.minLength && query.length < fuzzy.maxLength) {
+ matchQueries.push({
+ fuzzy: {
+ term: { value: query, boost: BOOST_FUZZY, fuzziness: 'AUTO' },
+ },
+ })
+ }
+
+ return matchQueries
+}
+
function getHits(hits, { indexName, debug, includeTopics, highlightFields, include }) {
return hits.map((hit) => {
// Return `hit.highlights[...]` based on the highlight fields requested.
@@ -464,7 +546,16 @@ function getHighlightConfiguration(query, highlights) {
},
}
}
-
+ if (highlights.includes('term')) {
+ fields.term = {
+ // Fast Vector Highlighter
+ // Using this requires that you first index these fields
+ // with {term_vector: 'with_positions_offsets'}
+ type: 'fvh',
+ // fragment_size: 200,
+ // number_of_fragments: 1,
+ }
+ }
return {
pre_tags: [''],
post_tags: [''],
diff --git a/src/search/middleware/get-search-request.js b/src/search/middleware/get-search-request.js
index 3e4aa03dd0..2573f9dc65 100644
--- a/src/search/middleware/get-search-request.js
+++ b/src/search/middleware/get-search-request.js
@@ -5,7 +5,9 @@ import { allVersions } from '#src/versions/lib/all-versions.js'
import { POSSIBLE_HIGHLIGHT_FIELDS, DEFAULT_HIGHLIGHT_FIELDS } from './es-search.js'
const DEFAULT_SIZE = 10
+const DEFAULT_AUTOCOMPLETE_SIZE = 8
const MAX_SIZE = 50 // How much you return has a strong impact on performance
+const MAX_AUTOCOMPLETE_SIZE = 10
const DEFAULT_PAGE = 1
const POSSIBLE_SORTS = ['best', 'relevance']
const DEFAULT_SORT = POSSIBLE_SORTS[0]
@@ -23,6 +25,7 @@ const V1_ADDITIONAL_INCLUDES = ['intro', 'headings']
// In some distant future we can clean up any client enough that this
// aliasing won't be necessary.
const versionAliases = {}
+const prefixVersionAliases = {}
Object.values(allVersions).forEach((info) => {
if (info.hasNumberedReleases) {
versionAliases[info.currentRelease] = info.miscVersionName
@@ -30,6 +33,11 @@ Object.values(allVersions).forEach((info) => {
versionAliases[info.version] = info.miscVersionName
versionAliases[info.miscVersionName] = info.miscVersionName
}
+ // This makes it so you can search for `?version=enterprise-server`
+ // and that actually means `?version=ghes` because there's an index
+ // called `github-autocomplete-en-ghes`.
+ prefixVersionAliases[info.plan] = info.shortName
+ prefixVersionAliases[info.shortName] = info.shortName
})
function getIndexPrefix() {
@@ -102,11 +110,44 @@ const PARAMS = [
},
]
-export function getSearchFromRequest(req, force = {}) {
+const AUTOCOMPLETE_PARAMS = [
+ { key: 'query' },
+ { key: 'language', default_: 'en', validate: (v) => v in languages },
+ {
+ key: 'version',
+ default_: 'free-pro-team',
+ validate: (v) => {
+ if (prefixVersionAliases[v] || allVersions[v]) return true
+ if (Object.values(prefixVersionAliases).includes(v)) return true
+ const valid = [
+ ...Object.keys(prefixVersionAliases),
+ ...Object.values(prefixVersionAliases),
+ ...Object.keys(allVersions),
+ ]
+ throw new ValidationError(`'${v}' not in ${valid.join(', ')}`)
+ },
+ },
+ {
+ key: 'size',
+ default_: DEFAULT_AUTOCOMPLETE_SIZE,
+ cast: (v) => parseInt(v, 10),
+ validate: (v) => v >= 0 && v <= MAX_AUTOCOMPLETE_SIZE,
+ },
+]
+export function getAutocompleteSearchFromRequest(req, force = {}) {
+ const { search, validationErrors } = getSearchFromRequest(req, {}, AUTOCOMPLETE_PARAMS)
+ if (validationErrors.length === 0) {
+ const version = prefixVersionAliases[search.version] || allVersions[search.version].shortName
+ search.indexName = `${getIndexPrefix()}github-autocomplete-${search.language}-${version}`
+ }
+ return { search, validationErrors }
+}
+
+export function getSearchFromRequest(req, force = {}, params = PARAMS) {
const search = {}
const validationErrors = []
- for (const { key, default_, cast, validate, multiple } of PARAMS) {
+ for (const { key, default_, cast, validate, multiple } of params) {
// This is necessary because when the version or language comes from
// the pathname, we don't want pick these up from the query string.
// This function gets used by /$locale/$version/search
@@ -153,7 +194,10 @@ export function getSearchFromRequest(req, force = {}) {
}
if (!validationErrors.length) {
- const version = versionAliases[search.version] || allVersions[search.version].miscVersionName
+ const version =
+ prefixVersionAliases[search.version] ||
+ versionAliases[search.version] ||
+ allVersions[search.version].miscVersionName
search.indexName = `${getIndexPrefix()}github-docs-${version}-${search.language}` // github-docs-ghes-3.5-en
}
diff --git a/src/search/middleware/search.js b/src/search/middleware/search.js
index cde294d8aa..a2b84100db 100644
--- a/src/search/middleware/search.js
+++ b/src/search/middleware/search.js
@@ -7,8 +7,8 @@ import {
setFastlySurrogateKey,
SURROGATE_ENUMS,
} from '#src/frame/middleware/set-fastly-surrogate-key.js'
-import { getSearchResults } from './es-search.js'
-import { getSearchFromRequest } from './get-search-request.js'
+import { getAutocompleteSearchResults, getSearchResults } from './es-search.js'
+import { getAutocompleteSearchFromRequest, getSearchFromRequest } from './get-search-request.js'
const router = express.Router()
@@ -69,6 +69,52 @@ router.get(
}),
)
+export const autocompleteValidationMiddleware = (req, res, next) => {
+ const { search, validationErrors } = getAutocompleteSearchFromRequest(req)
+ if (validationErrors.length) {
+ // There might be multiple things bad about the query parameters,
+ // but we send a 400 on the first possible one in the API.
+ return res.status(400).json(validationErrors[0])
+ }
+
+ req.search = search
+ return next()
+}
+
+router.get(
+ '/autocomplete/v1',
+ autocompleteValidationMiddleware,
+ catchMiddlewareError(async (req, res) => {
+ const { indexName, query, size } = req.search
+
+ const options = {
+ indexName,
+ query,
+ size,
+ }
+ try {
+ const { meta, hits } = await getAutocompleteSearchResults(options)
+
+ if (process.env.NODE_ENV !== 'development') {
+ searchCacheControl(res)
+ // We can cache this without purging it after every deploy
+ // because the API search is only used as a proxy for local
+ // and preview environments.
+ setFastlySurrogateKey(res, SURROGATE_ENUMS.MANUAL)
+ }
+
+ // The v1 version of the output matches perfectly what comes out
+ // of the getSearchResults() function.
+ res.status(200).json({ meta, hits })
+ } catch (error) {
+ // If getSearchResult() throws an error that might be 404 inside
+ // elasticsearch, if we don't capture that here, it will propagate
+ // to the next middleware.
+ await handleGetSearchResultsError(req, res, error, options)
+ }
+ }),
+)
+
// We have more than one place where we do `try{...} catch error( THIS )`
// which is slightly different depending on the "sub-version" (e.g. /legacy)
// This function is a single place to take care of all of these error handlings
@@ -93,4 +139,9 @@ router.get('/', (req, res) => {
res.redirect(307, req.originalUrl.replace('/search', '/search/v1'))
})
+// Alias for the latest autocomplete version
+router.get('/autocomplete', (req, res) => {
+ res.redirect(307, req.originalUrl.replace('/search/autocomplete', '/search/autocomplete/v1'))
+})
+
export default router
diff --git a/src/search/scripts/index-test-fixtures.sh b/src/search/scripts/index-test-fixtures.sh
new file mode 100755
index 0000000000..235eb915d2
--- /dev/null
+++ b/src/search/scripts/index-test-fixtures.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# This exists as a bash script because the commands are a bit too long
+# and complex to express inside `package.json`.
+
+set -e
+
+# For general site-search
+npm run index-elasticsearch -- -l en -l ja -V ghec -V dotcom --index-prefix tests -- src/search/tests/fixtures/search-indexes
+
+# For autocomplete search
+npm run index -- autocomplete src/search/tests/fixtures/data -l en -l ja -v fpt -v ghec --index-prefix tests
diff --git a/src/search/scripts/index/index-autocomplete.ts b/src/search/scripts/index/index-autocomplete.ts
index 3660f4f7a5..2c88c3a50d 100644
--- a/src/search/scripts/index/index-autocomplete.ts
+++ b/src/search/scripts/index/index-autocomplete.ts
@@ -24,6 +24,7 @@ type Options = {
retries?: number
sleepTime?: number
verbose?: boolean
+ indexPrefix?: string
}
export async function indexAutocomplete(options: Options) {
@@ -38,7 +39,12 @@ export async function indexAutocomplete(options: Options) {
for (const language of languages) {
for (const version of versions) {
const records = loadRecords({ version, language, dataRepoRoot })
- const { alias, name } = await createIndex(client, language, version)
+ const { alias, name } = await createIndex(
+ client,
+ language,
+ version,
+ options.indexPrefix || '',
+ )
await populate(client, records, {
alias,
name,
@@ -109,7 +115,12 @@ type IndexInfo = {
name: string
}
-async function createIndex(client: Client, language: string, version: Version): Promise {
+async function createIndex(
+ client: Client,
+ language: string,
+ version: Version,
+ indexPrefix: string,
+): Promise {
const settings: estypes.IndicesIndexSettings = {
analysis: {
analyzer: {
@@ -126,7 +137,11 @@ async function createIndex(client: Client, language: string, version: Version):
// XXX SNOWBALL?
}
- const indexName = `github-autocomplete-${language}-${shortVersionNames[version] || version}`
+ if (indexPrefix && !indexPrefix.endsWith('_')) {
+ indexPrefix += '_'
+ }
+
+ const indexName = `${indexPrefix}github-autocomplete-${language}-${shortVersionNames[version] || version}`
const thisAlias = `${indexName}__${utcTimestamp()}`
const mappings: estypes.MappingTypeMapping = {
diff --git a/src/search/scripts/index/index.ts b/src/search/scripts/index/index.ts
index b5b488301b..4b46ae05c1 100644
--- a/src/search/scripts/index/index.ts
+++ b/src/search/scripts/index/index.ts
@@ -25,6 +25,7 @@ program
]),
)
.option('--verbose', 'Verbose output')
+ .option('--index-prefix ', 'Prefix for the index names', '')
.argument('', 'path to the docs-internal-data repo')
.action((root: string, options) => {
const languages = options.language ? options.language : languageKeys
@@ -36,7 +37,8 @@ program
versions.push(v)
}
}
- return indexAutocomplete({ dataRepoRoot: root, languages, versions })
+ const indexPrefix = options.indexPrefix || ''
+ return indexAutocomplete({ dataRepoRoot: root, languages, versions, indexPrefix })
})
program.parse(process.argv)
diff --git a/src/search/tests/api-autocomplete-search.js b/src/search/tests/api-autocomplete-search.js
new file mode 100644
index 0000000000..2d9448aa51
--- /dev/null
+++ b/src/search/tests/api-autocomplete-search.js
@@ -0,0 +1,136 @@
+/**
+ * To be able to run these tests you need to index the fixtures!
+ * And you need to have an Elasticsearch URL to connect to for the server.
+ *
+ * To index the fixtures, run:
+ *
+ * ELASTICSEARCH_URL=http://localhost:9200 npm run index-test-fixtures
+ *
+ * This will replace any "real" Elasticsearch indexes you might have so
+ * once you're done working on vitest tests you need to index real
+ * content again.
+ */
+
+import { expect, test, vi } from 'vitest'
+
+import { describeIfElasticsearchURL } from '#src/tests/helpers/conditional-runs.js'
+import { get } from '#src/tests/helpers/e2etest.js'
+
+if (!process.env.ELASTICSEARCH_URL) {
+ console.warn(
+ 'None of the API search middleware tests are run because ' +
+ "the environment variable 'ELASTICSEARCH_URL' is currently not set.",
+ )
+}
+
+// This suite only runs if $ELASTICSEARCH_URL is set.
+describeIfElasticsearchURL('search/autocomplete v1 middleware', () => {
+ vi.setConfig({ testTimeout: 60 * 1000 })
+
+ test('basic search', async () => {
+ const sp = new URLSearchParams()
+ // To see why this will work,
+ // see src/search/tests/fixtures/data
+ sp.set('query', 'fo')
+ const res = await get('/api/search/autocomplete/v1?' + sp)
+ expect(res.statusCode).toBe(200)
+ const results = JSON.parse(res.body)
+
+ expect(results.meta).toBeTruthy()
+ expect(results.meta.found.value).toBeGreaterThanOrEqual(1)
+ expect(results.meta.found.relation).toBeTruthy()
+
+ // Might be empty but at least an array
+ expect(results.hits).toBeTruthy()
+ // The work "fork" matches "fo"
+ const hit = results.hits[0]
+ expect(hit.term).toBe('fork')
+ expect(hit.highlights).toBeTruthy()
+ expect(hit.highlights[0]).toBe('fork')
+
+ // Check that it can be cached at the CDN
+ expect(res.headers['set-cookie']).toBeUndefined()
+ expect(res.headers['cache-control']).toContain('public')
+ expect(res.headers['cache-control']).toMatch(/max-age=[1-9]/)
+ expect(res.headers['surrogate-control']).toContain('public')
+ expect(res.headers['surrogate-control']).toMatch(/max-age=[1-9]/)
+ expect(res.headers['surrogate-key']).toBe('manual-purge')
+ })
+
+ test('invalid version', async () => {
+ const sp = new URLSearchParams()
+ sp.set('query', 'fo')
+ sp.set('version', 'never-heard-of')
+ const res = await get('/api/search/autocomplete/v1?' + sp)
+ expect(res.statusCode).toBe(400)
+ expect(JSON.parse(res.body).error).toBeTruthy()
+ })
+
+ test('variations on version name', async () => {
+ const sp = new URLSearchParams()
+ sp.set('query', 'fo')
+ sp.set('version', 'enterprise-cloud')
+ {
+ const res = await get('/api/search/autocomplete/v1?' + sp)
+ expect(res.statusCode).toBe(200)
+ }
+ sp.set('version', 'ghec')
+ {
+ const res = await get('/api/search/autocomplete/v1?' + sp)
+ expect(res.statusCode).toBe(200)
+ }
+ sp.set('version', 'fpt')
+ {
+ const res = await get('/api/search/autocomplete/v1?' + sp)
+ expect(res.statusCode).toBe(200)
+ }
+ sp.set('version', 'free-pro-team@latest')
+ {
+ const res = await get('/api/search/autocomplete/v1?' + sp)
+ expect(res.statusCode).toBe(200)
+ }
+ })
+
+ test('invalid language', async () => {
+ const sp = new URLSearchParams()
+ sp.set('query', 'fo')
+ sp.set('language', 'xx')
+ const res = await get('/api/search/autocomplete/v1?' + sp)
+ expect(res.statusCode).toBe(400)
+ expect(JSON.parse(res.body).error).toBeTruthy()
+ })
+
+ test('fuzzy autocomplete search', async () => {
+ const sp = new URLSearchParams()
+ sp.set('query', 'forc')
+ const res = await get('/api/search/autocomplete/v1?' + sp)
+ expect(res.statusCode).toBe(200)
+ const results = JSON.parse(res.body)
+ // The work "fork" matches "fo"
+ const hit = results.hits[0]
+ expect(hit.term).toBe('fork')
+ expect(hit.highlights).toBeTruthy()
+ expect(hit.highlights[0]).toBe('fork')
+ })
+
+ test('invalid query', async () => {
+ const sp = new URLSearchParams()
+ // No query at all
+ {
+ const res = await get('/api/search/autocomplete/v1?' + sp)
+ expect(res.statusCode).toBe(400)
+ }
+ // Empty query
+ {
+ sp.set('query', '')
+ const res = await get('/api/search/autocomplete/v1?' + sp)
+ expect(res.statusCode).toBe(400)
+ }
+ // Empty when trimmed
+ {
+ sp.set('query', ' ')
+ const res = await get('/api/search/autocomplete/v1?' + sp)
+ expect(res.statusCode).toBe(400)
+ }
+ })
+})
diff --git a/src/search/tests/fixtures/data/all-documents/terms/en/enterprise-cloud/terms.json b/src/search/tests/fixtures/data/all-documents/terms/en/enterprise-cloud/terms.json
new file mode 100644
index 0000000000..eb5acc4887
--- /dev/null
+++ b/src/search/tests/fixtures/data/all-documents/terms/en/enterprise-cloud/terms.json
@@ -0,0 +1,26 @@
+{
+ "github": 631,
+ "api": 213,
+ "rest": 198,
+ "rest api": 196,
+ "endpoints": 176,
+ "api endpoints": 172,
+ "rest api endpoints": 172,
+ "managing": 165,
+ "organization": 155,
+ "repository": 144,
+ "using": 126,
+ "code": 81,
+ "app": 79,
+ "actions": 76,
+ "creating": 76,
+ "security": 75,
+ "codespaces": 70,
+ "pull": 70,
+ "configuring": 64,
+ "project": 61,
+ "copilot": 58,
+ "github actions": 57,
+ "access": 56,
+ "codeql": 54
+}
diff --git a/src/search/tests/fixtures/data/all-documents/terms/en/free-pro-team/terms.json b/src/search/tests/fixtures/data/all-documents/terms/en/free-pro-team/terms.json
new file mode 100644
index 0000000000..eb5acc4887
--- /dev/null
+++ b/src/search/tests/fixtures/data/all-documents/terms/en/free-pro-team/terms.json
@@ -0,0 +1,26 @@
+{
+ "github": 631,
+ "api": 213,
+ "rest": 198,
+ "rest api": 196,
+ "endpoints": 176,
+ "api endpoints": 172,
+ "rest api endpoints": 172,
+ "managing": 165,
+ "organization": 155,
+ "repository": 144,
+ "using": 126,
+ "code": 81,
+ "app": 79,
+ "actions": 76,
+ "creating": 76,
+ "security": 75,
+ "codespaces": 70,
+ "pull": 70,
+ "configuring": 64,
+ "project": 61,
+ "copilot": 58,
+ "github actions": 57,
+ "access": 56,
+ "codeql": 54
+}
diff --git a/src/search/tests/fixtures/data/all-documents/terms/ja/enterprise-cloud/terms.json b/src/search/tests/fixtures/data/all-documents/terms/ja/enterprise-cloud/terms.json
new file mode 100644
index 0000000000..647975fd98
--- /dev/null
+++ b/src/search/tests/fixtures/data/all-documents/terms/ja/enterprise-cloud/terms.json
@@ -0,0 +1,52 @@
+{
+ "github": 608,
+ "api": 204,
+ "rest": 192,
+ "rest api": 192,
+ "api \u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8": 169,
+ "rest api \u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8": 169,
+ "\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8": 169,
+ "organization": 110,
+ "codespaces": 58,
+ "codeql": 54,
+ "copilot": 51,
+ "actions": 49,
+ "github codespaces": 46,
+ "\u306b\u3064\u3044\u3066": 46,
+ "github actions": 45,
+ "github copilot": 42,
+ "app": 41,
+ "git": 41,
+ "classic": 36,
+ "project": 36,
+ "github app": 35,
+ "issue": 33,
+ "codespace": 31,
+ "dependabot": 31,
+ "oauth": 31,
+ "cli": 30,
+ "code": 30,
+ "desktop": 30,
+ "github desktop": 30,
+ "enterprise": 29,
+ "projects": 28,
+ "pull": 28,
+ "github enterprise": 26,
+ "\u3067\u306e": 26,
+ "pages": 25,
+ "pull request": 25,
+ "request": 25,
+ "github pages": 23,
+ "project classic": 23,
+ "azure": 21,
+ "importer": 20,
+ "database": 19,
+ "webhook": 19,
+ "team": 18,
+ "\u30ea\u30dd\u30b8\u30c8\u30ea": 18,
+ "marketplace": 17,
+ "\u306e\u4f7f\u7528": 17,
+ "github marketplace": 16,
+ "ssh": 16,
+ "\u30ab\u30b9\u30bf\u30e0": 16
+}
diff --git a/src/search/tests/fixtures/data/all-documents/terms/ja/free-pro-team/terms.json b/src/search/tests/fixtures/data/all-documents/terms/ja/free-pro-team/terms.json
new file mode 100644
index 0000000000..647975fd98
--- /dev/null
+++ b/src/search/tests/fixtures/data/all-documents/terms/ja/free-pro-team/terms.json
@@ -0,0 +1,52 @@
+{
+ "github": 608,
+ "api": 204,
+ "rest": 192,
+ "rest api": 192,
+ "api \u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8": 169,
+ "rest api \u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8": 169,
+ "\u30a8\u30f3\u30c9\u30dd\u30a4\u30f3\u30c8": 169,
+ "organization": 110,
+ "codespaces": 58,
+ "codeql": 54,
+ "copilot": 51,
+ "actions": 49,
+ "github codespaces": 46,
+ "\u306b\u3064\u3044\u3066": 46,
+ "github actions": 45,
+ "github copilot": 42,
+ "app": 41,
+ "git": 41,
+ "classic": 36,
+ "project": 36,
+ "github app": 35,
+ "issue": 33,
+ "codespace": 31,
+ "dependabot": 31,
+ "oauth": 31,
+ "cli": 30,
+ "code": 30,
+ "desktop": 30,
+ "github desktop": 30,
+ "enterprise": 29,
+ "projects": 28,
+ "pull": 28,
+ "github enterprise": 26,
+ "\u3067\u306e": 26,
+ "pages": 25,
+ "pull request": 25,
+ "request": 25,
+ "github pages": 23,
+ "project classic": 23,
+ "azure": 21,
+ "importer": 20,
+ "database": 19,
+ "webhook": 19,
+ "team": 18,
+ "\u30ea\u30dd\u30b8\u30c8\u30ea": 18,
+ "marketplace": 17,
+ "\u306e\u4f7f\u7528": 17,
+ "github marketplace": 16,
+ "ssh": 16,
+ "\u30ab\u30b9\u30bf\u30e0": 16
+}
diff --git a/src/search/tests/fixtures/data/hydro/rollups/user-searches/en/enterprise-cloud/rollup.json b/src/search/tests/fixtures/data/hydro/rollups/user-searches/en/enterprise-cloud/rollup.json
new file mode 100644
index 0000000000..2351efead5
--- /dev/null
+++ b/src/search/tests/fixtures/data/hydro/rollups/user-searches/en/enterprise-cloud/rollup.json
@@ -0,0 +1,51 @@
+{
+ "copilot": 141.3,
+ "inputs": 98.3,
+ "codeowners": 79.9,
+ "personal access token": 73.7,
+ "scim": 73.7,
+ "emu": 67.6,
+ "google": 61.4,
+ "ssh": 55.3,
+ "enterprise managed users": 55.3,
+ "enterprise": 49.1,
+ "audit log": 49.1,
+ "octocat": 49.1,
+ "rulesets": 43.0,
+ "teams": 43.0,
+ "saml": 43.0,
+ "oidc": 43.0,
+ "copilot api": 36.9,
+ "input": 36.9,
+ "release": 36.9,
+ "ssh key": 36.9,
+ "actions": 31.4,
+ "public code": 30.7,
+ "copilot enterprise": 30.7,
+ "license": 30.7,
+ "outside collaborators": 30.7,
+ "enterprise managed user": 30.7,
+ "github actions": 24.6,
+ "invite": 24.6,
+ "merge queue": 24.6,
+ "repository roles": 24.6,
+ "api": 24.6,
+ "workflow_dispatch": 24.6,
+ "github app": 24.6,
+ "oauth": 24.6,
+ "support": 24.6,
+ "dependabot": 24.6,
+ "personal access tokens": 24.6,
+ "secrets": 24.6,
+ "graphql": 24.6,
+ "token": 24.6,
+ "rest api": 24.6,
+ "audit logs": 24.6,
+ "pages": 18.4,
+ "security manager": 18.4,
+ "fork": 18.4,
+ "push": 18.4,
+ "enterprise policy": 18.4,
+ "environments": 18.4,
+ "okta": 18.4
+}
diff --git a/src/search/tests/fixtures/data/hydro/rollups/user-searches/en/free-pro-team/rollup.json b/src/search/tests/fixtures/data/hydro/rollups/user-searches/en/free-pro-team/rollup.json
new file mode 100644
index 0000000000..2351efead5
--- /dev/null
+++ b/src/search/tests/fixtures/data/hydro/rollups/user-searches/en/free-pro-team/rollup.json
@@ -0,0 +1,51 @@
+{
+ "copilot": 141.3,
+ "inputs": 98.3,
+ "codeowners": 79.9,
+ "personal access token": 73.7,
+ "scim": 73.7,
+ "emu": 67.6,
+ "google": 61.4,
+ "ssh": 55.3,
+ "enterprise managed users": 55.3,
+ "enterprise": 49.1,
+ "audit log": 49.1,
+ "octocat": 49.1,
+ "rulesets": 43.0,
+ "teams": 43.0,
+ "saml": 43.0,
+ "oidc": 43.0,
+ "copilot api": 36.9,
+ "input": 36.9,
+ "release": 36.9,
+ "ssh key": 36.9,
+ "actions": 31.4,
+ "public code": 30.7,
+ "copilot enterprise": 30.7,
+ "license": 30.7,
+ "outside collaborators": 30.7,
+ "enterprise managed user": 30.7,
+ "github actions": 24.6,
+ "invite": 24.6,
+ "merge queue": 24.6,
+ "repository roles": 24.6,
+ "api": 24.6,
+ "workflow_dispatch": 24.6,
+ "github app": 24.6,
+ "oauth": 24.6,
+ "support": 24.6,
+ "dependabot": 24.6,
+ "personal access tokens": 24.6,
+ "secrets": 24.6,
+ "graphql": 24.6,
+ "token": 24.6,
+ "rest api": 24.6,
+ "audit logs": 24.6,
+ "pages": 18.4,
+ "security manager": 18.4,
+ "fork": 18.4,
+ "push": 18.4,
+ "enterprise policy": 18.4,
+ "environments": 18.4,
+ "okta": 18.4
+}
diff --git a/src/search/tests/fixtures/data/hydro/rollups/user-searches/ja/enterprise-cloud/rollup.json b/src/search/tests/fixtures/data/hydro/rollups/user-searches/ja/enterprise-cloud/rollup.json
new file mode 100644
index 0000000000..39db9428d2
--- /dev/null
+++ b/src/search/tests/fixtures/data/hydro/rollups/user-searches/ja/enterprise-cloud/rollup.json
@@ -0,0 +1,23 @@
+{
+ "youtube": 43.0,
+ "ssh": 20.6,
+ "copilot": 18.4,
+ "google": 14.0,
+ "\u30ea\u30fc\u30b8\u30e7\u30f3": 13.7,
+ "workflow_run": 13.7,
+ "\u65e5\u672c\u8a9e": 13.7,
+ "\u691c\u7d22": 13.7,
+ "workflow_dispatch": 13.4,
+ "\u8a00\u8a9e": 13.4,
+ "\u5909\u6570": 13.4,
+ "uses": 12.6,
+ "\u30c8\u30fc\u30af\u30f3": 12.6,
+ "fork": 12.6,
+ "japanese": 12.6,
+ "permissions": 12.3,
+ "\u5bb9\u91cf": 12.3,
+ "actions/checkout": 12.3,
+ "github.event": 12.3,
+ "homebrew": 12.3,
+ "copilot workspace": 12.3
+}
\ No newline at end of file
diff --git a/src/search/tests/fixtures/data/hydro/rollups/user-searches/ja/free-pro-team/rollup.json b/src/search/tests/fixtures/data/hydro/rollups/user-searches/ja/free-pro-team/rollup.json
new file mode 100644
index 0000000000..39db9428d2
--- /dev/null
+++ b/src/search/tests/fixtures/data/hydro/rollups/user-searches/ja/free-pro-team/rollup.json
@@ -0,0 +1,23 @@
+{
+ "youtube": 43.0,
+ "ssh": 20.6,
+ "copilot": 18.4,
+ "google": 14.0,
+ "\u30ea\u30fc\u30b8\u30e7\u30f3": 13.7,
+ "workflow_run": 13.7,
+ "\u65e5\u672c\u8a9e": 13.7,
+ "\u691c\u7d22": 13.7,
+ "workflow_dispatch": 13.4,
+ "\u8a00\u8a9e": 13.4,
+ "\u5909\u6570": 13.4,
+ "uses": 12.6,
+ "\u30c8\u30fc\u30af\u30f3": 12.6,
+ "fork": 12.6,
+ "japanese": 12.6,
+ "permissions": 12.3,
+ "\u5bb9\u91cf": 12.3,
+ "actions/checkout": 12.3,
+ "github.event": 12.3,
+ "homebrew": 12.3,
+ "copilot workspace": 12.3
+}
\ No newline at end of file