1
0
Fork 0

chore: migrate to from yarn, lerna and shipjs to bun & changesets (#2827)

This commit is contained in:
Dylan Tientcheu 2026-03-02 18:52:01 +01:00 committed by GitHub
parent c94eafbbee
commit 0210db2f7d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 5881 additions and 28630 deletions

18
.changeset/README.md Normal file
View file

@ -0,0 +1,18 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in the changesets repo](https://github.com/changesets/changesets).
## How to contribute a changeset
Before merging your PR, you should add a changeset if your change affects any of the published packages.
1. Run `bun run changeset` in the root of the repository
2. Select the packages that are affected by your change
3. Choose the type of version bump (major, minor, or patch)
4. Write a summary of your changes that will be included in the changelog
All published `@docsearch/*` packages use a single version (fixed versioning). This means when one package is bumped, all packages get the same version.
## Releasing
When changesets are present in the `main` branch, the CI will create a "Version Packages" PR that bumps versions and updates changelogs. Merging that PR will trigger the publish workflow.

26
.changeset/config.json Normal file
View file

@ -0,0 +1,26 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [
[
"@docsearch/css",
"@docsearch/core",
"@docsearch/js",
"@docsearch/modal",
"@docsearch/react",
"@docsearch/sidepanel",
"@docsearch/sidepanel-js",
"@docsearch/docusaurus-adapter"
]
],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [
"@docsearch/website",
"@docsearch/react-example",
"@docsearch/js-example"
]
}

View file

@ -1,21 +1,25 @@
version: 2.1
aliases:
- &restore_yarn_cache
name: Restore Yarn cache
- &install_bun
name: Install Bun
command: |
npm install -g bun
- &restore_bun_cache
name: Restore Bun cache
keys:
- yarn-packages-{{ checksum "yarn.lock" }}
- bun-packages-{{ checksum "bun.lock" }}
- &save_yarn_cache
name: Save Yarn cache
key: yarn-packages-{{ checksum "yarn.lock" }}
- &save_bun_cache
name: Save Bun cache
key: bun-packages-{{ checksum "bun.lock" }}
paths:
- .yarn/cache
- .yarn/unplugged
- ~/.bun/install/cache
- &run_yarn_install
- &run_bun_install
name: Install dependencies
command: yarn install --immutable
command: bun install --frozen-lockfile
- &restore_dist_folders
name: Restore dist folders
@ -29,6 +33,7 @@ aliases:
mkdir -p packages/docsearch-modal/dist
mkdir -p packages/docsearch-sidepanel/dist
mkdir -p packages/docsearch-sidepanel-js/dist
mkdir -p adapters/docusaurus-theme-search-algolia/lib
cp -R /tmp/workspace/packages/docsearch-react/dist packages/docsearch-react
cp -R /tmp/workspace/packages/docsearch-js/dist packages/docsearch-js
@ -37,16 +42,17 @@ aliases:
cp -R /tmp/workspace/packages/docsearch-modal/dist packages/docsearch-modal
cp -R /tmp/workspace/packages/docsearch-sidepanel/dist packages/docsearch-sidepanel
cp -R /tmp/workspace/packages/docsearch-sidepanel-js/dist packages/docsearch-sidepanel-js
cp -R /tmp/workspace/adapters/docusaurus-theme-search-algolia/lib adapters/docusaurus-theme-search-algolia
defaults: &defaults
working_directory: ~/docsearch
docker:
- image: cimg/node:22.15.0
cypress: &cypress
playwright: &playwright
working_directory: ~/docsearch
docker:
- image: cypress/browsers:node-22.15.0-chrome-136.0.7103.113-1-ff-138.0.3-edge-136.0.3240.64-1
- image: mcr.microsoft.com/playwright:v1.49.1-noble
environment:
## this enables colors in the output
TERM: xterm
@ -62,13 +68,14 @@ jobs:
<<: *defaults
steps:
- checkout
- restore_cache: *restore_yarn_cache
- run: *run_yarn_install
- save_cache: *save_yarn_cache
- run: *install_bun
- restore_cache: *restore_bun_cache
- run: *run_bun_install
- save_cache: *save_bun_cache
- run:
name: Build and Size
command: |
yarn run build
bun run build
- run:
name: Move dist folders to workspace
command: |
@ -81,6 +88,7 @@ jobs:
mkdir -p /tmp/workspace/packages/docsearch-modal/dist
mkdir -p /tmp/workspace/packages/docsearch-sidepanel/dist
mkdir -p /tmp/workspace/packages/docsearch-sidepanel-js/dist
mkdir -p /tmp/workspace/adapters/docusaurus-theme-search-algolia/lib
cp -R packages/docsearch-react/dist /tmp/workspace/packages/docsearch-react
cp -R packages/docsearch-js/dist /tmp/workspace/packages/docsearch-js
@ -89,66 +97,73 @@ jobs:
cp -R packages/docsearch-modal/dist /tmp/workspace/packages/docsearch-modal
cp -R packages/docsearch-sidepanel/dist /tmp/workspace/packages/docsearch-sidepanel
cp -R packages/docsearch-sidepanel-js/dist /tmp/workspace/packages/docsearch-sidepanel-js
cp -R adapters/docusaurus-theme-search-algolia/lib /tmp/workspace/adapters/docusaurus-theme-search-algolia
- persist_to_workspace:
root: *workspace_root
paths:
- packages
- adapters
test_lint:
<<: *defaults
steps:
- checkout
- *attach_workspace
- restore_cache: *restore_yarn_cache
- run: *run_yarn_install
- save_cache: *save_yarn_cache
- run: *install_bun
- restore_cache: *restore_bun_cache
- run: *run_bun_install
- save_cache: *save_bun_cache
- run: *restore_dist_folders
- run:
name: Linting
command: yarn run lint
command: bun run lint
test_types:
<<: *defaults
steps:
- checkout
- *attach_workspace
- restore_cache: *restore_yarn_cache
- run: *run_yarn_install
- save_cache: *save_yarn_cache
- run: *install_bun
- restore_cache: *restore_bun_cache
- run: *run_bun_install
- save_cache: *save_bun_cache
- run: *restore_dist_folders
- run:
name: Type checking
command: yarn run test:types
command: bun run test:types
test_size:
<<: *defaults
steps:
- checkout
- *attach_workspace
- restore_cache: *restore_yarn_cache
- run: *run_yarn_install
- save_cache: *save_yarn_cache
- run: *install_bun
- restore_cache: *restore_bun_cache
- run: *run_bun_install
- save_cache: *save_bun_cache
- run: *restore_dist_folders
- run:
name: Size checking
command: yarn run test:size
command: bun run test:size
test_unit:
<<: *defaults
steps:
- checkout
- *attach_workspace
- restore_cache: *restore_yarn_cache
- run: *run_yarn_install
- save_cache: *save_yarn_cache
- run: *install_bun
- restore_cache: *restore_bun_cache
- run: *run_bun_install
- save_cache: *save_bun_cache
- run: *restore_dist_folders
- run:
name: Unit tests
command: yarn run test
command: bun run test
release:
<<: *defaults
steps:
- checkout
- *attach_workspace
- restore_cache: *restore_yarn_cache
- run: *run_yarn_install
- save_cache: *save_yarn_cache
- run: *install_bun
- restore_cache: *restore_bun_cache
- run: *run_bun_install
- save_cache: *save_bun_cache
- run: *restore_dist_folders
- run:
name: Setup git user
@ -157,19 +172,26 @@ jobs:
git config --global user.name "algolia-bot"
- run:
name: Release if needed
command: yarn run shipjs trigger
test_cypress:
<<: *cypress
command: bun run release
test_playwright:
<<: *playwright
steps:
- checkout
- *attach_workspace
- restore_cache: *restore_yarn_cache
- run: *run_yarn_install
- save_cache: *save_yarn_cache
- run: *install_bun
- restore_cache: *restore_bun_cache
- run: *run_bun_install
- save_cache: *save_bun_cache
- run: *restore_dist_folders
- run:
name: Cypress test Actions
command: yarn run cy:run
name: Playwright E2E tests
command: bun run pw:run
- store_artifacts:
path: playwright-report
destination: playwright-report
- store_artifacts:
path: test-results
destination: test-results
workflows:
version: 2
@ -188,7 +210,7 @@ workflows:
- test_unit:
requires:
- build
- test_cypress:
- test_playwright:
requires:
- build
- release:

6
.gitignore vendored
View file

@ -21,9 +21,9 @@ dist/
# Environment files
.env
# Cypress Video and Screenshots output
cypress/screenshots/
cypress/videos/
# Playwright output
playwright-report/
test-results/
**/.yarn/*
!**/.yarn/releases

View file

@ -2,3 +2,4 @@ node_modules
dist
build
.cache
adapters/docusaurus-theme-search-algolia/lib

File diff suppressed because one or more lines are too long

View file

@ -1,5 +0,0 @@
nodeLinker: node-modules
enableGlobalCache: false
yarnPath: .yarn/releases/yarn-4.6.0.cjs

View file

@ -4,7 +4,7 @@ This document provides guidelines for AI agents working on the DocSearch codebas
## Project Overview
DocSearch is an Algolia-powered search widget for documentation sites. It's a TypeScript/React monorepo using Yarn workspaces with Lerna for orchestration.
DocSearch is an Algolia-powered search widget for documentation sites. It's a TypeScript/React monorepo using Bun workspaces.
### Packages
@ -21,62 +21,62 @@ DocSearch is an Algolia-powered search widget for documentation sites. It's a Ty
```bash
# Install dependencies
yarn install
bun install
# Build all packages
yarn build
bun run build
# Build specific package
yarn workspace @docsearch/react build
bun --filter @docsearch/react build
# Watch mode (all packages)
yarn watch
bun run watch
# Clean builds
yarn build:clean
bun run build:clean
```
## Test Commands
```bash
# Run all unit tests
yarn test
bun run test
# Run a single test file
yarn test packages/docsearch-react/src/__tests__/utils.test.ts
bun run test packages/docsearch-react/src/__tests__/utils.test.ts
# Run tests matching a pattern
yarn test --testNamePattern="extractLinksFromText"
bun run test --testNamePattern="extractLinksFromText"
# Run tests in watch mode
yarn test --watch
bun run test --watch
# Type checking
yarn test:types
bun run test:types
# Bundle size check
yarn test:size
bun run test:size
```
## Lint Commands
```bash
# Run ESLint
yarn lint
bun run lint
# Run CSS linting
yarn lint:css
bun run lint:css
```
## E2E Testing (Cypress)
```bash
# Run Cypress tests
yarn cy:run
bun run cy:run
# Run with specific browser
yarn cy:run:chrome
yarn cy:run:firefox
bun run cy:run:chrome
bun run cy:run:firefox
```
## Code Style Guidelines

View file

@ -60,12 +60,43 @@ Some examples of valid commit messages (used as first lines):
To run this project, you will need:
- Node.js ≥ 18 [nvm](https://github.com/nvm-sh/nvm#install-script) is recommended
- [Yarn](https://yarnpkg.com)
- [Bun](https://bun.sh) our package manager and build tool
## Getting started
```sh
# Install dependencies
bun install
# Build all packages
bun run build
# Run tests
bun run test
# Start development mode (watch + playground)
bun run dev
```
## Changesets
This project uses [Changesets](https://github.com/changesets/changesets) for versioning and publishing.
If your PR includes changes that should be released, add a changeset:
```sh
bun run changeset
```
Select the packages affected by your change, choose the version bump type (major/minor/patch), and write a summary that will appear in the changelog.
## Release
```sh
yarn run release
```
When changesets are merged to `main`, a "Version Packages" PR will be automatically created (or updated). Merging that PR triggers the release workflow:
It will create a pull request for the next release. When it's reviewed, approved and merged, then CircleCI will automatically publish it to npm.
1. Package versions are bumped
2. CHANGELOGs are updated
3. Packages are published to npm
4. Git tags are created
All `@docsearch/*` packages share the same version number (fixed versioning).

View file

@ -33,7 +33,7 @@ DocSearch crawls your documentation, pushes the content to an Algolia index and
#### Installation
```sh
yarn add @docsearch/js@4
bun add @docsearch/js@4
# or
npm install @docsearch/js@4
```
@ -74,7 +74,7 @@ docsearch({
#### Installation
```bash
yarn add @docsearch/react@4
bun add @docsearch/react@4
# or
npm install @docsearch/react@4
```

View file

@ -27,14 +27,15 @@
},
"license": "MIT",
"scripts": {
"build": "yarn exec tsc --build --force && node ./scripts/copy-assets.mjs && node ./scripts/format-theme.mjs",
"build:clean": "yarn clean && yarn build",
"build": "bun x tsc --build --force && node ./scripts/copy-assets.mjs && node ./scripts/format-theme.mjs",
"build:clean": "bun run clean && bun run build",
"clean": "rm -rf lib tsconfig.*.tsbuildinfo",
"watch": "run-p -c copy:watch build:watch",
"build:watch": "yarn exec tsc --build --watch",
"build:watch": "bun x tsc --build --watch",
"copy:watch": "node ./scripts/copy-assets.mjs --watch"
},
"dependencies": {
"@algolia/autocomplete-core": "1.19.2",
"@docsearch/react": "4.6.0",
"@docusaurus/core": "^3.9.2",
"@docusaurus/plugin-content-docs": "^3.9.2",
@ -54,6 +55,7 @@
"@docusaurus/core": "^3.9.2",
"@docusaurus/module-type-aliases": "3.9.2",
"@docusaurus/theme-classic": "3.9.2",
"@docusaurus/types": "^3.9.2",
"@types/fs-extra": "^11.0.4",
"@types/lodash": "^4.17.10",
"glob": "^13.0.6",

5305
bun.lock Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,9 +0,0 @@
import { defineConfig } from 'cypress';
export default defineConfig({
projectId: 'nf9rdc',
e2e: {
baseUrl: 'http://localhost:3000',
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
},
});

Binary file not shown.

View file

@ -1,48 +0,0 @@
/// <reference path="../support/commands.d.ts" />
describe.skip('Ask AI', () => {
beforeEach(() => {
cy.visit(Cypress.config().baseUrl!);
cy.openModal();
});
it('Results are displayed after a query', () => {
cy.typeQueryMatching();
cy.get('.DocSearch-Hits').should('be.visible');
});
it('Shows Ask AI as a hit on search', () => {
cy.typeQueryMatching();
cy.get('.DocSearch-AskAi-Section').should('be.visible');
});
it('Opens Ask AI on enter key', () => {
cy.typeQueryMatching();
cy.get('.DocSearch-Input').type('{enter}');
cy.get('.DocSearch-AskAiScreen').should('be.visible');
});
it('Opens Ask AI on click', () => {
cy.typeQueryMatching();
cy.get('#docsearch-askAI-item-0').click();
cy.get('.DocSearch-AskAiScreen').should('be.visible');
});
it('Streams response after query', () => {
cy.goToAskAi();
cy.get('.DocSearch-AskAiScreen-Query').should('be.visible');
cy.get('.DocSearch-AskAiScreen-Message').should('be.visible');
});
it('Copy button copies the response to the clipboard', () => {
cy.goToAskAi();
cy.get('.DocSearch-AskAiScreen-Response').should('be.visible');
cy.window().its('navigator.permissions').invoke('query', { name: 'clipboard-read' }).its('state').then(cy.log);
cy.window()
.its('navigator.clipboard')
.invoke('readText')
.then((text) => {
expect(text.length).to.be.greaterThan(0);
});
});
});

View file

@ -1,153 +0,0 @@
/// <reference path="../support/commands.d.ts" />
describe('Start', () => {
beforeEach(() => {
cy.visit(Cypress.config().baseUrl!);
cy.waitLoad();
});
it('Open modal on search button click', () => {
cy.openModal();
cy.modalIsVisibleAndFocused();
// check that the scrollbar offset is compensated
cy.get('body').should('have.css', 'overflow', 'hidden');
cy.get('.DocSearch-Modal').should('be.visible');
});
it('Open modal with key shortcut on Windows/Linux', () => {
cy.get('body').type('{ctrl}k');
cy.modalIsVisibleAndFocused();
});
it('Open modal with key shortcut on Windows/Linux when caps lock is on', () => {
cy.get('body').type('{ctrl}K');
cy.modalIsVisibleAndFocused();
});
it('Open modal with key shortcut on macOS', () => {
cy.get('body').type('{meta}k');
cy.modalIsVisibleAndFocused();
});
it('Open modal with key shortcut on macOS when caps lock is on', () => {
cy.get('body').type('{meta}K');
cy.modalIsVisibleAndFocused();
});
it('Open modal with forward slash key shortcut', () => {
cy.get('body').wait(1000).type('/');
cy.modalIsVisibleAndFocused();
});
});
describe('End', () => {
beforeEach(() => {
cy.visit(Cypress.config().baseUrl!);
cy.openModal();
});
it('Close modal with Esc key', () => {
cy.closeModal();
cy.modalIsNotVisible();
});
it('Close modal by clicking outside its container', () => {
cy.get('body').click(0, 0);
cy.modalIsNotVisible();
});
it('Close modal with key shortcut on Windows/Linux', () => {
cy.get('body').type('{ctrl}k');
cy.modalIsNotVisible();
});
it('Close modal with key shortcut on macOS', () => {
cy.get('body').type('{meta}k');
cy.modalIsNotVisible();
});
});
describe('Search', () => {
beforeEach(() => {
cy.visit(Cypress.config().baseUrl!);
cy.openModal();
});
it('Results are displayed after a query', () => {
cy.typeQueryMatching();
cy.get('.DocSearch-Hits').should('be.visible');
});
it('Query can be cleared', () => {
cy.typeQueryMatching();
cy.get('.DocSearch-Clear').click();
cy.get('.DocSearch-Hits').should('not.exist');
});
it('Keyboard navigation leads to result', () => {
const currentURL = cy.url();
cy.typeQueryMatching();
cy.get('.DocSearch-Input').type('{downArrow}{downArrow}{upArrow}');
cy.get('.DocSearch-Input').type('{enter}');
cy.on('url:changed', (newUrl) => {
expect(newUrl).not.equal(currentURL);
});
});
it('Pointer navigation leads to result', () => {
const currentURL = cy.url();
cy.typeQueryMatching();
cy.get('#docsearch-hits_docsearch_0-item-1 > a').click({
force: true,
});
cy.on('url:changed', (newUrl) => {
expect(newUrl).not.equal(currentURL);
});
});
it("No results are displayed if query doesn't match", () => {
cy.typeQueryNotMatching();
cy.contains('No results found for').should('be.visible');
});
it('Should not refer to Recent/Favorite in aria-controls', () => {
cy.get('.DocSearch-Input').should('not.have.attr', 'aria-controls');
});
});
describe('Recent and Favorites', () => {
beforeEach(() => {
cy.visit(Cypress.config().baseUrl!);
cy.openModal();
cy.typeQueryMatching();
cy.get('#docsearch-hits_docsearch_0-item-1 > a').click({ force: true }).wait(1000);
cy.openModal();
cy.contains('Recent').should('be.visible');
});
it('Recent search is displayed after visiting a result', () => {
cy.clearSearch();
cy.get('#docsearch-recentSearches-item-0').should('be.visible');
});
it('Recent search can be deleted', () => {
cy.get('#docsearch-recentSearches-item-0').find('[title="Remove this search from history"]').trigger('click');
cy.get('.DocSearch-Hits').should('not.exist');
});
it('Recent search can be favorited', () => {
cy.get('#docsearch-recentSearches-item-0').find('[title="Save this search"]').trigger('click');
cy.contains('Favorite').should('be.visible');
cy.get('#docsearch-favoriteSearches-item-0').should('be.visible');
});
it('Favorite can be deleted', () => {
cy.get('#docsearch-recentSearches-item-0').find('[title="Save this search"]').trigger('click');
cy.contains('Favorite').should('be.visible');
cy.get('#docsearch-favoriteSearches-item-0').find('[title="Remove this search from favorites"]').trigger('click');
cy.get('.DocSearch-Hits').should('not.exist');
});
});

View file

@ -1,55 +0,0 @@
/// <reference types="cypress" />
declare namespace Cypress {
interface Chainable {
/**
* Ensures the modal is visible and focused.
*/
modalIsVisibleAndFocused: () => void;
/**
* Ensures the modal not visible.
*/
modalIsNotVisible: () => void;
/**
* Toggles the dark mode on the preview website.
*/
darkmode: () => void;
/**
* Wait for the page to load.
*/
waitLoad: () => void;
/**
* Opens the DocSearch modal.
*/
openModal: () => void;
/**
* Closes the DocSearch modal.
*/
closeModal: () => void;
/**
* Search for a given query.
*/
search: (query: string) => void;
/**
* Types a query that returns results.
*/
typeQueryMatching: () => void;
/**
* Clears the search input.
*/
clearSearch: () => void;
/**
* Types a query that returns no results.
*/
typeQueryNotMatching: () => void;
/**
* Returns to the keyword search screen.
*/
returnToSearch: () => void;
/**
* Navigates to the Ask AI screen.
*/
goToAskAi: () => void;
}
}

View file

@ -1,54 +0,0 @@
Cypress.Commands.add('modalIsVisibleAndFocused', () => {
cy.get('.DocSearch-Modal', { timeout: 10000 }).should('be.visible');
cy.get('.DocSearch-Input').should('be.focus');
});
Cypress.Commands.add('modalIsNotVisible', () => {
cy.get('body').should('not.have.class', 'DocSearch--active');
cy.get('.DocSearch-Modal').should('not.exist');
});
Cypress.Commands.add('darkmode', () => {
cy.get('.react-toggle').click({ force: true });
cy.get('.react-toggle-screenreader-only').blur();
cy.get('html.dark').should('be.visible');
});
Cypress.Commands.add('waitLoad', () => {
cy.get('.DocSearch-Button', { timeout: 10000 }).should('be.visible');
});
Cypress.Commands.add('openModal', () => {
cy.get('.DocSearch-Button').should('be.visible').click();
cy.modalIsVisibleAndFocused();
});
Cypress.Commands.add('closeModal', () => {
cy.get('body').type('{esc}');
cy.modalIsNotVisible();
});
Cypress.Commands.add('search', (query: string) => {
cy.get('.DocSearch-Input').should('be.visible').type(query);
});
Cypress.Commands.add('typeQueryMatching', () => {
cy.search('g');
});
Cypress.Commands.add('typeQueryNotMatching', () => {
cy.search('zzz');
});
Cypress.Commands.add('clearSearch', () => {
cy.get('.DocSearch-Input').clear();
});
Cypress.Commands.add('returnToSearch', () => {
cy.get('.DocSearch-Input').type('{esc}');
});
Cypress.Commands.add('goToAskAi', () => {
cy.typeQueryMatching();
cy.get('#docsearch-AskAi-Section').click();
});

View file

@ -1,20 +0,0 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands';
// Alternatively you can use CommonJS syntax:
// require('./commands')

View file

@ -1,7 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"types": ["cypress"]
},
"exclude": []
}

47
e2e/askai.spec.ts Normal file
View file

@ -0,0 +1,47 @@
import { test, expect } from './fixtures';
test.describe.skip('Ask AI', () => {
test.beforeEach(async ({ docSearch }) => {
await docSearch.goto();
await docSearch.openModal();
});
test('Results are displayed after a query', async ({ docSearch }) => {
await docSearch.typeQueryMatching();
await expect(docSearch.hits).toBeVisible();
});
test('Shows Ask AI as a hit on search', async ({ docSearch, page }) => {
await docSearch.typeQueryMatching();
await expect(page.locator('.DocSearch-AskAi-Section')).toBeVisible();
});
test('Opens Ask AI on enter key', async ({ docSearch, page }) => {
await docSearch.typeQueryMatching();
await page.keyboard.press('Enter');
await expect(page.locator('.DocSearch-AskAiScreen')).toBeVisible();
});
test('Opens Ask AI on click', async ({ docSearch, page }) => {
await docSearch.typeQueryMatching();
await page.locator('#docsearch-askAI-item-0').click();
await expect(page.locator('.DocSearch-AskAiScreen')).toBeVisible();
});
test('Streams response after query', async ({ docSearch, page }) => {
await docSearch.goToAskAi();
await expect(page.locator('.DocSearch-AskAiScreen-Query')).toBeVisible();
await expect(page.locator('.DocSearch-AskAiScreen-Message')).toBeVisible();
});
test('Copy button copies the response to the clipboard', async ({ docSearch, page, context }) => {
// Grant clipboard permissions
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
await docSearch.goToAskAi();
await expect(page.locator('.DocSearch-AskAiScreen-Response')).toBeVisible();
const clipboardText = await page.evaluate(() => navigator.clipboard.readText());
expect(clipboardText.length).toBeGreaterThan(0);
});
});

84
e2e/fixtures.ts Normal file
View file

@ -0,0 +1,84 @@
import { test as base, expect, type Page, type Locator } from '@playwright/test';
export class DocSearchPage {
readonly page: Page;
readonly searchButton: Locator;
readonly modal: Locator;
readonly input: Locator;
readonly hits: Locator;
readonly clearButton: Locator;
constructor(page: Page) {
this.page = page;
this.searchButton = page.locator('.DocSearch-Button');
this.modal = page.locator('.DocSearch-Modal');
this.input = page.locator('.DocSearch-Input');
this.hits = page.locator('.DocSearch-Hits').first();
this.clearButton = page.locator('.DocSearch-Clear');
}
async goto(): Promise<void> {
await this.page.goto('/');
}
async waitForLoad(): Promise<void> {
await expect(this.searchButton).toBeVisible({ timeout: 10000 });
}
async openModal(): Promise<void> {
await this.searchButton.click();
await this.expectModalVisibleAndFocused();
}
async closeModal(): Promise<void> {
await this.page.keyboard.press('Escape');
await this.expectModalNotVisible();
}
async expectModalVisibleAndFocused(): Promise<void> {
await expect(this.modal).toBeVisible({ timeout: 10000 });
await expect(this.input).toBeFocused();
}
async expectModalNotVisible(): Promise<void> {
await expect(this.page.locator('body')).not.toHaveClass(/DocSearch--active/);
await expect(this.modal).not.toBeVisible();
}
async search(query: string): Promise<void> {
await this.input.fill(query);
}
async typeQueryMatching(): Promise<void> {
await this.search('g');
}
async typeQueryNotMatching(): Promise<void> {
await this.search('zzz');
}
async clearSearch(): Promise<void> {
await this.input.clear();
}
async enableDarkMode(): Promise<void> {
await this.page.locator('.react-toggle').click({ force: true });
await this.page.locator('.react-toggle-screenreader-only').blur();
await expect(this.page.locator('html.dark')).toBeVisible();
}
async goToAskAi(): Promise<void> {
await this.typeQueryMatching();
await this.page.locator('#docsearch-AskAi-Section').click();
}
}
export const test = base.extend<{ docSearch: DocSearchPage }>({
docSearch: async ({ page }, use) => {
const docSearch = new DocSearchPage(page);
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(docSearch);
},
});
export { expect };

155
e2e/search.spec.ts Normal file
View file

@ -0,0 +1,155 @@
import { test, expect } from './fixtures';
test.describe('Start', () => {
test.beforeEach(async ({ docSearch }) => {
await docSearch.goto();
await docSearch.waitForLoad();
});
test('Open modal on search button click', async ({ docSearch, page }) => {
await docSearch.openModal();
// check that the scrollbar offset is compensated
await expect(page.locator('body')).toHaveCSS('overflow', 'hidden');
await expect(docSearch.modal).toBeVisible();
});
test('Open modal with key shortcut on Windows/Linux', async ({ docSearch, page }) => {
await page.keyboard.press('Control+k');
await docSearch.expectModalVisibleAndFocused();
});
test('Open modal with key shortcut on Windows/Linux when caps lock is on', async ({ docSearch, page }) => {
await page.keyboard.press('Control+K');
await docSearch.expectModalVisibleAndFocused();
});
test('Open modal with key shortcut on macOS', async ({ docSearch, page }) => {
await page.keyboard.press('Meta+k');
await docSearch.expectModalVisibleAndFocused();
});
test('Open modal with key shortcut on macOS when caps lock is on', async ({ docSearch, page }) => {
await page.keyboard.press('Meta+K');
await docSearch.expectModalVisibleAndFocused();
});
test('Open modal with forward slash key shortcut', async ({ docSearch, page }) => {
await page.waitForTimeout(1000);
await page.keyboard.press('/');
await docSearch.expectModalVisibleAndFocused();
});
});
test.describe('End', () => {
test.beforeEach(async ({ docSearch }) => {
await docSearch.goto();
await docSearch.openModal();
});
test('Close modal with Esc key', async ({ docSearch }) => {
await docSearch.closeModal();
});
test('Close modal by clicking outside its container', async ({ docSearch, page }) => {
await page.mouse.click(0, 0);
await docSearch.expectModalNotVisible();
});
test('Close modal with key shortcut on Windows/Linux', async ({ docSearch, page }) => {
await page.keyboard.press('Control+k');
await docSearch.expectModalNotVisible();
});
test('Close modal with key shortcut on macOS', async ({ docSearch, page }) => {
await page.keyboard.press('Meta+k');
await docSearch.expectModalNotVisible();
});
});
test.describe('Search', () => {
test.beforeEach(async ({ docSearch }) => {
await docSearch.goto();
await docSearch.openModal();
});
test('Results are displayed after a query', async ({ docSearch }) => {
await docSearch.typeQueryMatching();
await expect(docSearch.hits).toBeVisible();
});
test('Query can be cleared', async ({ docSearch }) => {
await docSearch.typeQueryMatching();
await docSearch.clearButton.click();
await expect(docSearch.hits).not.toBeVisible();
});
test('Keyboard navigation leads to result', async ({ docSearch, page }) => {
const initialURL = page.url();
await docSearch.typeQueryMatching();
await expect(docSearch.hits).toBeVisible();
await page.keyboard.press('ArrowDown');
await page.keyboard.press('ArrowDown');
await page.keyboard.press('ArrowUp');
await page.keyboard.press('Enter');
await expect(page).not.toHaveURL(initialURL, { timeout: 10000 });
});
test('Pointer navigation leads to result', async ({ docSearch, page }) => {
const initialURL = page.url();
await docSearch.typeQueryMatching();
await page.locator('#docsearch-hits_docsearch_0-item-1 > a').click({ force: true });
await expect(page).not.toHaveURL(initialURL);
});
test("No results are displayed if query doesn't match", async ({ docSearch, page }) => {
await docSearch.typeQueryNotMatching();
await expect(page.getByText('No results found for')).toBeVisible();
});
test('Should not refer to Recent/Favorite in aria-controls', async ({ docSearch }) => {
await expect(docSearch.input).not.toHaveAttribute('aria-controls');
});
});
test.describe('Recent and Favorites', () => {
test.beforeEach(async ({ docSearch, page }) => {
await docSearch.goto();
await docSearch.openModal();
await docSearch.typeQueryMatching();
await page.locator('#docsearch-hits_docsearch_0-item-1 > a').click({ force: true });
await page.waitForTimeout(1000);
await docSearch.openModal();
await expect(page.getByText('Recent')).toBeVisible();
});
test('Recent search is displayed after visiting a result', async ({ docSearch, page }) => {
await docSearch.clearSearch();
await expect(page.locator('#docsearch-recentSearches-item-0')).toBeVisible();
});
test('Recent search can be deleted', async ({ docSearch, page }) => {
await page.locator('#docsearch-recentSearches-item-0').locator('[title="Remove this search from history"]').click();
await expect(docSearch.hits).not.toBeVisible();
});
test('Recent search can be favorited', async ({ page }) => {
await page.locator('#docsearch-recentSearches-item-0').locator('[title="Save this search"]').click();
await expect(page.getByText('Favorite')).toBeVisible();
await expect(page.locator('#docsearch-favoriteSearches-item-0')).toBeVisible();
});
test('Favorite can be deleted', async ({ docSearch, page }) => {
await page.locator('#docsearch-recentSearches-item-0').locator('[title="Save this search"]').click();
await expect(page.getByText('Favorite')).toBeVisible();
await page
.locator('#docsearch-favoriteSearches-item-0')
.locator('[title="Remove this search from favorites"]')
.click();
await expect(docSearch.hits).not.toBeVisible();
});
});

View file

@ -1,7 +1,6 @@
import algolia from 'eslint-config-algolia/flat/base.js';
import algoliaReact from 'eslint-config-algolia/flat/react.js';
import algoliaTypescript from 'eslint-config-algolia/flat/typescript.js';
import cypresss from 'eslint-plugin-cypress/flat'; // eslint-disable-line import/no-unresolved
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint'; // eslint-disable-line import/no-unresolved
@ -10,7 +9,16 @@ export default [
...algoliaReact,
...algoliaTypescript,
{
ignores: ['**/node_modules/', '**/dist/', '**/build/', '.yarn/', '**/.docusaurus'],
ignores: [
'**/node_modules/',
'**/dist/',
'**/build/',
'.yarn/',
'**/.docusaurus',
'scratchpad/**',
'adapters/**/lib/**',
'**/rollup.config-*.cjs',
],
},
{
plugins: {
@ -74,12 +82,9 @@ export default [
},
tseslint.configs.disableTypeChecked,
{
files: ['cypress/**/*'],
plugins: {
cypresss,
},
files: ['e2e/**/*'],
rules: {
'@typescript-eslint/triple-slash-reference': 0,
'@typescript-eslint/no-floating-promises': 0,
},
},
{

View file

@ -1,9 +0,0 @@
{
"packages": [
"adapters/*",
"packages/*",
"examples/*"
],
"version": "4.6.0",
"npmClient": "yarn"
}

View file

@ -7,32 +7,49 @@
"examples/*"
],
"scripts": {
"build": "lerna run build",
"cy:clean": "rm -rf cypress/screenshots",
"cy:info": "cypress info",
"cy:run:chrome": "yarn run cy:run --browser chrome",
"cy:run:edge": "yarn run cy:run --browser edge",
"cy:run:firefox": "yarn run cy:run --browser firefox",
"cy:run": "start-server-and-test 'yarn workspace @docsearch/docusaurus-adapter build && yarn website:test' http://localhost:3000 'cypress run --headless'",
"cy:verify": "cypress verify",
"build": "bun run --sequential build:stage:base build:stage:react build:stage:consumers build:stage:adapter website:build",
"build:all": "bun run --sequential build website:build",
"build:pkg:core": "bun --filter @docsearch/core build",
"build:pkg:css": "bun --filter @docsearch/css build",
"build:pkg:react": "bun --filter @docsearch/react build",
"build:pkg:js": "bun --filter @docsearch/js build",
"build:pkg:sidepanel": "bun --filter @docsearch/sidepanel build",
"build:pkg:sidepanel-js": "bun --filter @docsearch/sidepanel-js build",
"build:pkg:modal": "bun --filter @docsearch/modal build",
"build:pkg:adapter": "bun --filter @docsearch/docusaurus-adapter build",
"build:stage:base": "bun run --parallel build:pkg:core build:pkg:css",
"build:stage:react": "bun run build:pkg:react",
"build:stage:consumers": "bun run --parallel build:pkg:js build:pkg:sidepanel build:pkg:sidepanel-js build:pkg:modal",
"build:stage:adapter": "bun run build:pkg:adapter",
"build:examples": "bun --filter './examples/*' build",
"pw:clean": "rm -rf playwright-report test-results",
"pw:run": "start-server-and-test 'bun run website:test' http://localhost:3000 'playwright test'",
"pw:run:chromium": "bun run pw:run -- --project=chromium",
"pw:run:firefox": "bun run pw:run -- --project=firefox",
"pw:run:webkit": "bun run pw:run -- --project=webkit",
"pw:ui": "start-server-and-test 'bun run website:test' http://localhost:3000 'playwright test --ui'",
"lint:css": "stylelint **/src/**/*.css",
"lint": "eslint .",
"playground:build": "yarn workspace @docsearch/react-example build",
"playground:start": "yarn workspace @docsearch/react-example dev --host",
"playground-js:start": "yarn workspace @docsearch/js-example dev --host",
"release": "shipjs prepare",
"start": "yarn run watch",
"dev": "yarn start & yarn playground:start",
"playground:build": "bun --filter @docsearch/react-example build",
"playground:start": "bun --filter @docsearch/react-example dev -- --host",
"playground-js:start": "bun --filter @docsearch/js-example dev -- --host",
"start": "bun run watch",
"dev": "bun run start & bun run playground:start",
"test:size": "bundlesize",
"test:types": "tsc --noEmit",
"test": "vitest",
"watch": "lerna run watch --parallel",
"website:build": "yarn workspace @docsearch/website build",
"website:start": "yarn workspace @docsearch/website start --host 0.0.0.0",
"website:serve": "yarn workspace @docsearch/website serve --host 0.0.0.0",
"website:test": "yarn workspace @docsearch/website start --no-open"
"watch": "bun --filter './packages/*' watch",
"website:build": "bun --filter @docsearch/website build",
"website:start": "bun --filter @docsearch/website start -- --host 0.0.0.0",
"website:serve": "bun --filter @docsearch/website serve -- --host 0.0.0.0",
"website:test": "bun --filter @docsearch/website start -- --no-open",
"changeset": "changeset",
"version": "changeset version && bun run sync:docsearch-version",
"release": "changeset publish",
"sync:docsearch-version": "bun scripts/updateDocsearchVersion.ts"
},
"devDependencies": {
"@changesets/cli": "^2.29.7",
"@eslint-community/eslint-plugin-eslint-comments": "4.4.1",
"@stylistic/eslint-plugin": "2.13.0",
"@testing-library/dom": "10.4.0",
@ -42,11 +59,10 @@
"@typescript-eslint/parser": "8.20.0",
"bundlesize2": "0.0.32",
"cssnano": "7.0.6",
"cypress": "13.17.0",
"@playwright/test": "1.49.1",
"eslint": "8.57.1",
"eslint-config-algolia": "23.2.1",
"eslint-config-prettier": "10.0.1",
"eslint-plugin-cypress": "4.1.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jsdoc": "50.6.2",
"eslint-plugin-jsx-a11y": "6.10.2",
@ -55,13 +71,11 @@
"eslint-plugin-react-hooks": "5.1.0",
"eslint-plugin-react-refresh": "0.4.18",
"jsdom": "26.0.0",
"lerna": "8.1.9",
"postcss": "8.5.1",
"prettier": "3.4.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"rolldown": "1.0.0-beta.57",
"shipjs": "0.27.0",
"start-server-and-test": "2.0.10",
"stylelint": "16.13.2",
"stylelint-config-sass-guidelines": "12.1.0",
@ -73,6 +87,5 @@
"typescript-eslint": "8.20.0",
"vitest": "3.0.2",
"watch": "1.0.2"
},
"packageManager": "yarn@4.6.0"
}
}

View file

@ -42,6 +42,7 @@
"devDependencies": {
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.2.0",
"preact": "11.0.0-beta.0",
"tsdown": "^0.18.3",
"typescript": "5.7.3",
"vitest": "3.0.2"

View file

@ -5,8 +5,6 @@ Style package for [DocSearch](http://docsearch.algolia.com/), the best search ex
## Installation
```bash
yarn add @docsearch/css@4
# or
npm install @docsearch/css@4
```

View file

@ -22,8 +22,8 @@
"scripts": {
"build:clean": "rm -rf ./dist",
"build:css": "node build-css.js",
"build": "yarn build:clean && mkdir dist && yarn build:css",
"watch": "nodemon --watch 'src/**/*.css' --exec 'yarn build:css' --ext css"
"build": "bun run build:clean && mkdir dist && bun run build:css",
"watch": "nodemon --watch 'src/**/*.css' --exec 'bun run build:css' --ext css"
},
"devDependencies": {
"nodemon": "^3.1.9"

View file

@ -5,8 +5,6 @@ JavaScript package for [DocSearch](http://docsearch.algolia.com/), the best sear
## Installation
```sh
yarn add @docsearch/js@4
# or
npm install @docsearch/js@4
```

View file

@ -31,7 +31,7 @@
"devDependencies": {
"@docsearch/react": "4.6.0",
"htm": "3.1.1",
"preact": "^10.0.0",
"preact": "11.0.0-beta.0",
"tsdown": "^0.18.3",
"typescript": "5.7.3"
},

View file

@ -41,8 +41,6 @@
"@docsearch/react": "4.6.0"
},
"devDependencies": {
"@docsearch/core": "4.5.3",
"@docsearch/react": "4.5.3",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.2.0",
"tsdown": "^0.18.3",

View file

@ -5,8 +5,6 @@ React package for [DocSearch](http://docsearch.algolia.com/), the best search ex
## Installation
```bash
yarn add @docsearch/react@4
# or
npm install @docsearch/react@4
```

View file

@ -60,6 +60,7 @@
"ai": "^5.0.30",
"algoliasearch": "^5.28.0",
"marked": "^16.3.0",
"preact": "11.0.0-beta.0",
"tsdown": "^0.18.3",
"typescript": "5.7.3",
"vitest": "3.0.2",

View file

@ -5,8 +5,6 @@ JavaScript package for [DocSearch Sidepanel](http://docsearch.algolia.com/), a s
## Installation
```bash
yarn add @docsearch/sidepanel-js
# or
npm install @docsearch/sidepanel-js
```

View file

@ -33,7 +33,7 @@
},
"devDependencies": {
"@docsearch/react": "4.6.0",
"preact": "^11.0.0-beta.0",
"preact": "11.0.0-beta.0",
"tsdown": "^0.18.3",
"typescript": "5.7.3"
},

View file

@ -5,7 +5,6 @@ React package for [DocSearch Sidepanel](http://docsearch.algolia.com/), a standa
## Installation
```bash
yarn add @docsearch/core@4 @docsearch/sidepanel@4 @docsearch/css@4
# or
npm install @docsearch/core@4 @docsearch/sidepanel@4 @docsearch/css@4
```

View file

@ -46,8 +46,6 @@
"@docsearch/react": "4.6.0"
},
"devDependencies": {
"@docsearch/core": "4.5.3",
"@docsearch/react": "4.5.3",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.2.0",
"tsdown": "^0.18.3",

View file

@ -8,8 +8,8 @@ Check out our [website][2] for a complete explanation and documentation.
# Installation
1. In the root of this repository (two level above this directory), do `yarn install` and `yarn build`
1. In this directory, do `yarn start`.
1. In the root of this repository (two levels above this directory), do `bun install` and `bun run build`
1. In this directory, do `bun run start`.
1. A browser window will open up, pointing to the docs.
# Deployment

View file

@ -30,8 +30,6 @@ If your docs site is powered by Docusaurus, use [`@docsearch/docusaurus-adapter`
<TabItem value="js">
```bash
yarn add @docsearch/js@4
# or with npm
npm install @docsearch/js@4
```
@ -53,7 +51,7 @@ And the JavaScript at the end of your `<body>`:
<TabItem value="react">
```bash
yarn add @docsearch/react@4
bun add @docsearch/react@4
# or
npm install @docsearch/react@4
```

View file

@ -21,8 +21,6 @@ This package is a dependency of [`@docsearch/js`][1] and [`@docsearch/react`][1]
## Installation
```bash
yarn add @docsearch/css@4
# or
npm install @docsearch/css@4
```

View file

@ -19,10 +19,12 @@
"@docsearch/core": "workspace:*",
"@docsearch/css": "workspace:*",
"@docsearch/docusaurus-adapter": "workspace:*",
"@docsearch/modal": "workspace:*",
"@docsearch/react": "workspace:*",
"@docsearch/sidepanel": "workspace:*",
"@docusaurus/core": "3.9.2",
"@docusaurus/preset-classic": "3.9.2",
"@docusaurus/theme-common": "3.9.2",
"@mdx-js/react": "^3.1.0",
"@tabler/icons-react": "^3.34.0",
"class-variance-authority": "^0.7.1",

28
playwright.config.ts Normal file
View file

@ -0,0 +1,28 @@
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: Boolean(process.env.CI),
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI ? 'html' : 'list',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
/* {
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
}, */
],
});

View file

@ -0,0 +1,25 @@
import { readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
const root = process.cwd();
const packageJsonPath = join(root, 'packages', 'docsearch-react', 'package.json');
const versionFilePath = join(root, 'packages', 'docsearch-react', 'src', 'version.ts');
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
const packageVersion = packageJson.version;
if (typeof packageVersion !== 'string') {
throw new Error('Unable to determine @docsearch/react version.');
}
const nextContent = `export const version = '${packageVersion}';\n`;
const currentContent = readFileSync(versionFilePath, 'utf8');
if (currentContent === nextContent) {
// eslint-disable-next-line no-console
console.info('DocSearch version file already up to date.');
} else {
writeFileSync(versionFilePath, nextContent, 'utf8');
// eslint-disable-next-line no-console
console.info(`Updated docsearch-react version to ${packageVersion}.`);
}

View file

@ -1,38 +0,0 @@
import fs from 'fs';
import path from 'path';
const packages = [
'adapters/docusaurus-theme-search-algolia',
'packages/docsearch-css',
'packages/docsearch-react',
'packages/docsearch-js',
'packages/docsearch-core',
'packages/docsearch-modal',
'packages/docsearch-sidepanel',
'packages/docsearch-sidepanel-js',
];
export default {
monorepo: {
mainVersionFile: 'lerna.json',
// We rely on Lerna to bump our dependencies.
packagesToBump: [],
packagesToPublish: packages,
},
publishCommand({ tag }) {
return `npm publish --tag ${tag} --access public`;
},
versionUpdated({ exec, dir, version }) {
// Update package dependencies
exec(`yarn lerna version ${version} --exact --no-git-tag-version --no-push --yes`);
// Ship.js reads JSON and writes with `fs.writeFileSync(JSON.stringify(json, null, 2))`
// which causes a lint error in the `lerna.json` file.
exec('yarn eslint lerna.json --fix');
fs.writeFileSync(
path.resolve(dir, 'packages', 'docsearch-react', 'src', 'version.ts'),
`export const version = '${version}';\n`,
);
},
};

View file

@ -19,7 +19,7 @@
"**/__mocks__/**/*",
"**/dist",
"**/node_modules",
"cypress",
"e2e",
"examples"
]
}

View file

@ -4,5 +4,6 @@ export default defineConfig({
test: {
environment: 'jsdom',
setupFiles: ['./scripts/setupTests.ts'],
include: ['packages/**/src/**/*.test.ts', 'tests/**/*.test.ts'],
}
});

27167
yarn.lock

File diff suppressed because it is too large Load diff