diff --git a/.github/workflows/algolia.yml b/.github/workflows/algolia.yml new file mode 100644 index 00000000000..72f8b2f95c5 --- /dev/null +++ b/.github/workflows/algolia.yml @@ -0,0 +1,27 @@ +name: Update Algolia Index + +on: [workflow_dispatch] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: checkout + uses: actions/checkout@v3 + + - name: Enable PNPM + uses: pnpm/action-setup@v2 + + - name: Set node version to 20 + uses: actions/setup-node@v3 + with: + node-version: "20" + + - name: Install + run: pnpm install --frozen-lockfile + + - name: Update Algolia Index + run: pnpm run algolia + env: + ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_ADMIN_KEY }} diff --git a/.github/workflows/cron-run.yml b/.github/workflows/cron-run.yml index e2cc23d6b04..275b6534cad 100644 --- a/.github/workflows/cron-run.yml +++ b/.github/workflows/cron-run.yml @@ -54,11 +54,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Trigger DB updates - uses: fjogeleit/http-request-action@v1 - with: - url: "https://fontsource.org/actions/update" - method: "POST" - bearerToken: ${{ secrets.WEBSITE_UPDATE_TOKEN }} - data: '{"fonts": true, "algolia": true, "download": true, "axisRegistry": true, "docs": true}' - preventFailureOnNoResponse: true + - name: Update Algolia Index + run: pnpm run algolia + env: + ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_ADMIN_KEY }} diff --git a/.github/workflows/manual-run.yml b/.github/workflows/manual-run.yml index 0aa8c8f837c..0bb68ca3f22 100644 --- a/.github/workflows/manual-run.yml +++ b/.github/workflows/manual-run.yml @@ -55,11 +55,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Trigger DB updates - uses: fjogeleit/http-request-action@v1 - with: - url: "https://fontsource.org/actions/update" - method: "POST" - bearerToken: ${{ secrets.WEBSITE_UPDATE_TOKEN }} - data: '{"fonts": true, "algolia": true, "download": true, "axisRegistry": true, "docs": true}' - preventFailureOnNoResponse: true + - name: Update Algolia Index + run: pnpm run algolia + env: + ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_ADMIN_KEY }} diff --git a/package.json b/package.json index 8c901fa611f..70321d519cc 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "packageManager": "pnpm@8.3.1", "private": true, "scripts": { + "algolia": "tsx scripts/algolia.ts", "check-duplicates": "tsx scripts/check-duplicates.ts", "fontlist": "tsx scripts/fontlist.ts", "gfm-metadata": "tsx scripts/gfm-metadata.ts", @@ -18,6 +19,7 @@ "@fontsource-utils/cli": "0.4.1", "@fontsource-utils/publish": "^0.2.8", "@types/node": "^20.5.7", + "algoliasearch": "^4.22.1", "consola": "^3.2.3", "google-font-metadata": "^5.2.1", "json-stringify-pretty-compact": "^4.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b5fb98b3c6..cf4475f885c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ dependencies: '@types/node': specifier: ^20.5.7 version: 20.5.7 + algoliasearch: + specifier: ^4.22.1 + version: 4.22.1 consola: specifier: ^3.2.3 version: 3.2.3 @@ -32,6 +35,96 @@ dependencies: packages: + /@algolia/cache-browser-local-storage@4.22.1: + resolution: {integrity: sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g==} + dependencies: + '@algolia/cache-common': 4.22.1 + dev: false + + /@algolia/cache-common@4.22.1: + resolution: {integrity: sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA==} + dev: false + + /@algolia/cache-in-memory@4.22.1: + resolution: {integrity: sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw==} + dependencies: + '@algolia/cache-common': 4.22.1 + dev: false + + /@algolia/client-account@4.22.1: + resolution: {integrity: sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw==} + dependencies: + '@algolia/client-common': 4.22.1 + '@algolia/client-search': 4.22.1 + '@algolia/transporter': 4.22.1 + dev: false + + /@algolia/client-analytics@4.22.1: + resolution: {integrity: sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg==} + dependencies: + '@algolia/client-common': 4.22.1 + '@algolia/client-search': 4.22.1 + '@algolia/requester-common': 4.22.1 + '@algolia/transporter': 4.22.1 + dev: false + + /@algolia/client-common@4.22.1: + resolution: {integrity: sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ==} + dependencies: + '@algolia/requester-common': 4.22.1 + '@algolia/transporter': 4.22.1 + dev: false + + /@algolia/client-personalization@4.22.1: + resolution: {integrity: sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ==} + dependencies: + '@algolia/client-common': 4.22.1 + '@algolia/requester-common': 4.22.1 + '@algolia/transporter': 4.22.1 + dev: false + + /@algolia/client-search@4.22.1: + resolution: {integrity: sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA==} + dependencies: + '@algolia/client-common': 4.22.1 + '@algolia/requester-common': 4.22.1 + '@algolia/transporter': 4.22.1 + dev: false + + /@algolia/logger-common@4.22.1: + resolution: {integrity: sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg==} + dev: false + + /@algolia/logger-console@4.22.1: + resolution: {integrity: sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA==} + dependencies: + '@algolia/logger-common': 4.22.1 + dev: false + + /@algolia/requester-browser-xhr@4.22.1: + resolution: {integrity: sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw==} + dependencies: + '@algolia/requester-common': 4.22.1 + dev: false + + /@algolia/requester-common@4.22.1: + resolution: {integrity: sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg==} + dev: false + + /@algolia/requester-node-http@4.22.1: + resolution: {integrity: sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA==} + dependencies: + '@algolia/requester-common': 4.22.1 + dev: false + + /@algolia/transporter@4.22.1: + resolution: {integrity: sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ==} + dependencies: + '@algolia/cache-common': 4.22.1 + '@algolia/logger-common': 4.22.1 + '@algolia/requester-common': 4.22.1 + dev: false + /@babel/code-frame@7.23.5: resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} engines: {node: '>=6.9.0'} @@ -517,6 +610,25 @@ packages: - supports-color dev: false + /algoliasearch@4.22.1: + resolution: {integrity: sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg==} + dependencies: + '@algolia/cache-browser-local-storage': 4.22.1 + '@algolia/cache-common': 4.22.1 + '@algolia/cache-in-memory': 4.22.1 + '@algolia/client-account': 4.22.1 + '@algolia/client-analytics': 4.22.1 + '@algolia/client-common': 4.22.1 + '@algolia/client-personalization': 4.22.1 + '@algolia/client-search': 4.22.1 + '@algolia/logger-common': 4.22.1 + '@algolia/logger-console': 4.22.1 + '@algolia/requester-browser-xhr': 4.22.1 + '@algolia/requester-common': 4.22.1 + '@algolia/requester-node-http': 4.22.1 + '@algolia/transporter': 4.22.1 + dev: false + /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} diff --git a/scripts/algolia.ts b/scripts/algolia.ts new file mode 100644 index 00000000000..cf457fdd647 --- /dev/null +++ b/scripts/algolia.ts @@ -0,0 +1,88 @@ +import algoliasearch from 'algoliasearch'; +import metadataImport from '../metadata/fontsource.json'; + +interface AlgoliaMetadata { + objectID: string; + family: string; + subsets: string[]; + weights: number[]; + styles: string[]; + defSubset: string; + category: string; + variable: boolean; + lastModified: number; + downloadMonth: number; + randomIndex: number; +} + +// eslint-disable-next-line @typescript-eslint/no-non-null-assertion +const client = algoliasearch('WNATE69PVR', process.env.ALGOLIA_ADMIN_KEY!); + +const shuffleArray = (size: number) => { + // Generate array of numbers from 0 to size + const arr: number[] = [...Array.from({ length: size }).keys()]; + + // Durstenfeld shuffle to randomly sort array + for (let i = arr.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [arr[i], arr[j]] = [arr[j], arr[i]]; + } + return arr; +}; + +const updateAlgoliaIndex = async (force?: boolean) => { + try { + // Get font list + const list = Object.keys(metadataImport); + const indexArray: AlgoliaMetadata[] = []; + + // For the random shuffle, we need a presorted index + // as Algolia does not support random sorting natively + const randomIndexArr = shuffleArray(list.length); + + let index = 0; + for (const id of list) { + const metadata = metadataImport[id]; + if (!metadata) + console.warn(`No metadata found for ${id} when updating Algolia index`); + + const stats = ( + await fetch(`https://api.fontsource.org/v1/stats/${id}`) + ).json() as any; + const downloadCountMonthly = stats?.total?.npmDownloadMonthly; + + const obj = { + objectID: id, + family: metadata.family, + subsets: metadata.subsets, + weights: metadata.weights, + styles: metadata.styles, + category: metadata.category, + defSubset: metadata.defSubset, + variable: metadata.variable, + // Algolia sorts date using a unix timestamp instead + lastModified: Math.floor( + new Date(metadata.lastModified).getTime() / 1000 + ), + downloadMonth: downloadCountMonthly ?? 0, + randomIndex: randomIndexArr[index], + }; + + indexArray.push(obj); + index++; + } + + const searchIndex = client.initIndex('prod_NAME'); + if (force) { + await searchIndex.replaceAllObjects(indexArray); + console.log('Replaced Algolia index'); + } else { + await searchIndex.saveObjects(indexArray); + console.log('Updated Algolia index'); + } + } catch (error) { + console.error(error); + } +}; + +updateAlgoliaIndex();