diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c154680fa..8927226dd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,6 +12,17 @@ on: - main jobs: + meilisearch-version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.get-version-step.outputs.meilisearch_version }} + steps: + - uses: actions/checkout@v5 + - name: Get required version of meilisearch + id: get-version-step + run: | + MEILISEARCH_VERSION=$(node -p "require('./package.json').meilisearchTargetVersion") + echo "meilisearch_version=$MEILISEARCH_VERSION" >> $GITHUB_OUTPUT integration_tests: # Will not run if the event is a PR to bump-meilisearch-v* (so a pre-release PR) # Will not run if the event is a PR to pre-release-beta/* @@ -25,9 +36,10 @@ jobs: !startsWith(github.base_ref, 'prototype-beta/') && !startsWith(github.head_ref, 'pre-release-beta/') runs-on: ubuntu-latest + needs: meilisearch-version services: meilisearch: - image: getmeili/meilisearch:latest + image: getmeili/meilisearch:v${{ needs.meilisearch-version.outputs.version }} env: MEILI_MASTER_KEY: 'masterKey' MEILI_NO_ANALYTICS: 'true' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f69ec2bfe..b1c5a8fe7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,17 +34,24 @@ First of all, thank you for contributing to Meilisearch! The goal of this docume To run this project, you will need: -- Node >= v20 and Node <= 22 -- Yarn v1.x +- [Node.js v20 or newer](https://nodejs.org/en/download) +- [Docker](https://www.docker.com/products/docker-desktop/) ### Setup -You can set up your local environment natively or using `docker`, check out the [`docker-compose.yml`](/docker-compose.yml). +To enable [corepack](https://github.com/nodejs/corepack) for +[Yarn](https://classic.yarnpkg.com/en/) to work: -Example of running all the checks with docker: +```bash +corepack enable +``` + +To run Meilisearch for testing: ```bash -docker-compose run --rm package bash -c "yarn install && yarn test && yarn lint" +yarn docker +# or if you wish to run it in the background in detached mode +yarn docker -d ``` To install dependencies: diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index cb0c9c7dc..000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,22 +0,0 @@ -services: - package: - image: node:22 - tty: true - stdin_open: true - working_dir: /home/package - environment: - - MEILISEARCH_URL=http://meilisearch:7700 - depends_on: - - meilisearch - links: - - meilisearch - volumes: - - ./:/home/package - - meilisearch: - image: getmeili/meilisearch - ports: - - "7700:7700" - environment: - - MEILI_MASTER_KEY=masterKey - - MEILI_NO_ANALYTICS=true diff --git a/package.json b/package.json index 61452ca4d..f8925baf3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "meilisearch", "version": "0.53.0", + "meilisearchTargetVersion": "1.18.0", "description": "The Meilisearch JS client for Node.js and the browser.", "keywords": [ "meilisearch", @@ -61,7 +62,8 @@ "lint:fix": "eslint --fix", "style": "yarn fmt && yarn lint", "style:fix": "yarn fmt:fix && yarn lint:fix", - "prepare": "husky" + "prepare": "husky", + "docker": "node scripts/docker-meilisearch.js" }, "files": [ "src", diff --git a/src/meilisearch.ts b/src/meilisearch.ts index b2e7d7527..35422a8b5 100644 --- a/src/meilisearch.ts +++ b/src/meilisearch.ts @@ -473,8 +473,14 @@ export class MeiliSearch { * * @returns Promise returning an object with health details */ - async health(): Promise { - return await this.httpRequest.get({ path: "health" }); + async health( + // TODO: Need to do this for all other methods: https://github.com/meilisearch/meilisearch-js/issues/1476 + extraRequestInit?: ExtraRequestInit, + ): Promise { + return await this.httpRequest.get({ + path: "health", + extraRequestInit, + }); } /** diff --git a/tests/errors.test.ts b/tests/errors.test.ts index f9596f98a..88ebb0a54 100644 --- a/tests/errors.test.ts +++ b/tests/errors.test.ts @@ -1,13 +1,12 @@ -import { test, describe, beforeEach, vi } from "vitest"; +import { test, describe, vi, beforeAll } from "vitest"; import { MeiliSearch, assert } from "./utils/meilisearch-test-utils.js"; import { MeiliSearchRequestError } from "../src/index.js"; const mockedFetch = vi.fn(); -globalThis.fetch = mockedFetch; describe("Test on updates", () => { - beforeEach(() => { - mockedFetch.mockReset(); + beforeAll(() => { + globalThis.fetch = mockedFetch; }); test(`Throw MeilisearchRequestError when thrown error is not MeiliSearchApiError`, async () => { diff --git a/tests/setup.ts b/tests/setup.ts new file mode 100644 index 000000000..618f74b39 --- /dev/null +++ b/tests/setup.ts @@ -0,0 +1,141 @@ +import { spawnSync } from "node:child_process"; +import { MeiliSearch } from "../src/meilisearch.js"; +import pkg from "../package.json" with { type: "json" }; + +const POLL_INTERVAL = 250; +const CONTAINER_NAME = "meilisearch"; +const TIMEOUT = 15_000; +const TIMEOUT_ID = Symbol(); + +const ms = new MeiliSearch({ + host: "http://127.0.0.1:7700", + apiKey: "masterKey", +}); + +function removeIfExistsMeilisearchDockerService(): void { + spawnSync( + "docker", + + // https://docs.docker.com/reference/cli/docker/container/rm/ + ["container", "rm", "-f", CONTAINER_NAME], + + // TODO: prefix output + { stdio: "inherit" }, + ); +} + +function startMeilisearchDockerService(meilisearchVersion: string): void { + spawnSync( + "docker", + [ + // https://docs.docker.com/reference/cli/docker/container/run + "run", + + // https://docs.docker.com/reference/cli/docker/container/run/#rm + "--rm", + + // https://docs.docker.com/reference/cli/docker/container/run/#detach + "-d", + + // https://docs.docker.com/reference/cli/docker/container/run/#name + "--name", + CONTAINER_NAME, + + // https://docs.docker.com/reference/cli/docker/container/run/#publish + "-p", + "7700:7700", + + // https://docs.docker.com/reference/cli/docker/container/run/#env + "-e", + "MEILI_MASTER_KEY=masterKey", + "-e", + "MEILI_NO_ANALYTICS=true", + + // https://hub.docker.com/r/getmeili/meilisearch + `getmeili/meilisearch:v${meilisearchVersion}`, + ], + + // TODO: prefix output + { stdio: "inherit" }, + ); +} + +/** Poll Meilisearch until its reachable. */ +async function waitForMeiliSearch(): Promise { + let lastError; + + const ac = new AbortController(); + + const toId = setTimeout(() => void ac.abort(TIMEOUT_ID), TIMEOUT); + + for (;;) { + try { + await ms.health({ signal: ac.signal }); + + clearTimeout(toId); + + break; + } catch (error) { + if (Object.is((error as Error).cause, TIMEOUT_ID)) { + throw new Error( + `connection unsuccessful to meilisearch after ${TIMEOUT}ms`, + { cause: lastError }, + ); + } + + lastError = error; + } + + await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL)); + } +} + +/** + * In case there is a connection, return Meilisearch version, and + * `null`otherwise. + */ +async function checkConnectionAndVersion(): Promise { + try { + const { pkgVersion } = await ms.getVersion(); + return pkgVersion; + } catch { + return null; + } +} + +// TODO: could use docker image save/load and https://github.com/actions/cache?tab=readme-ov-file +// instead of github workflows services + +/** + * If there is no connection to Meilisearch, create a docker service of it, and + * wait for connection. + * + * {@link https://vitest.dev/config/#globalsetup} + */ +export default async function () { + const { meilisearchTargetVersion } = pkg; + + const meilisearchVersion = await checkConnectionAndVersion(); + if (meilisearchVersion !== null) { + if (meilisearchVersion !== meilisearchTargetVersion) { + throw new Error( + "Meilisearch is reachable but it is the wrong version " + + `(expected ${meilisearchTargetVersion}, got ${meilisearchVersion})`, + ); + } + + return; + } + + try { + removeIfExistsMeilisearchDockerService(); + startMeilisearchDockerService(meilisearchTargetVersion); + await waitForMeiliSearch(); + + return removeIfExistsMeilisearchDockerService; + } catch (error) { + removeIfExistsMeilisearchDockerService(); + + throw error; + } +} diff --git a/vite.config.ts b/vite.config.ts index 9e2244eda..a94d82238 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -54,6 +54,7 @@ export default defineConfig(({ mode }) => { : undefined, }, test: { + globalSetup: "tests/setup.ts", include: ["tests/**/*.test.ts"], exclude: ["tests/env/**"], fileParallelism: false,