From 179412c707c8dc76a67feda16b10f90b649d6ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20=C4=90=C3=ACnh=20Huy?= Date: Sun, 23 Aug 2026 14:27:00 +0700 Subject: [PATCH 1/2] feat(cli): support Bun package manager in init --- .changeset/quick-buns-install.md | 6 +++ docusaurus/docs/installation.md | 18 ++++++- packages/cli/package.json | 3 +- packages/cli/src/commands/init.ts | 19 ++++++- packages/cli/src/templates/reassure-tests-bun | 19 +++++++ packages/cli/tests/init.test.ts | 51 +++++++++++++++++++ packages/cli/tests/jest.config.js | 12 +++++ packages/reassure/README.md | 14 +++++ 8 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 .changeset/quick-buns-install.md create mode 100755 packages/cli/src/templates/reassure-tests-bun create mode 100644 packages/cli/tests/init.test.ts create mode 100644 packages/cli/tests/jest.config.js diff --git a/.changeset/quick-buns-install.md b/.changeset/quick-buns-install.md new file mode 100644 index 000000000..65aac315b --- /dev/null +++ b/.changeset/quick-buns-install.md @@ -0,0 +1,6 @@ +--- +'reassure': minor +'@callstack/reassure-cli': minor +--- + +Generate Bun-compatible CI scripts for projects that use Bun as their package manager. diff --git a/docusaurus/docs/installation.md b/docusaurus/docs/installation.md index 6446ae980..5397e39a9 100644 --- a/docusaurus/docs/installation.md +++ b/docusaurus/docs/installation.md @@ -20,6 +20,11 @@ npm install --save-dev reassure yarn add --dev reassure ``` + +```sh +bun add --dev reassure +``` + You will also need a working [Jest](https://jestjs.io/docs/getting-started) setup as well as one of either [React Native Testing Library](https://github.com/callstack/react-native-testing-library#installation) or [React Testing Library](https://testing-library.com/docs/react-testing-library/intro). @@ -138,9 +143,20 @@ To make setting up Reassure more convenient we have prepared a CLI command which Simply run: -```bash + + +```sh yarn reassure init ``` + + +```sh +bun run reassure init +``` + + + +When the project declares Bun in the `packageManager` field or contains a `bun.lock`/`bun.lockb` file, the generated CI script uses `bun install` and `bun run reassure`. This changes only the package manager commands; Reassure continues to run performance tests with Jest. This will generate the following file structure diff --git a/packages/cli/package.json b/packages/cli/package.json index 29b4993de..2c201c59d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -51,7 +51,8 @@ }, "scripts": { "build": "bob build", - "clean": "del lib" + "clean": "del lib", + "test": "jest --config tests/jest.config.js" }, "dependencies": { "@callstack/reassure-compare": "1.6.0", diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 6aab9b1c1..e560ff24b 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -56,12 +56,29 @@ function setUpCiScript() { return; } - copyFileSync(path.join(TEMPLATE_PATH, 'reassure-tests'), CI_SCRIPT); + const template = usesBun() ? 'reassure-tests-bun' : 'reassure-tests'; + copyFileSync(path.join(TEMPLATE_PATH, template), CI_SCRIPT); logger.clearLine(); logger.log(`✅ CI Script: created`); logger.log(`🔗 ${path.resolve(CI_SCRIPT)}`); } +function usesBun(): boolean { + if (existsSync('package.json')) { + try { + const packageManager = JSON.parse(readFileSync('package.json', 'utf8')).packageManager; + + if (typeof packageManager === 'string') { + return packageManager === 'bun' || packageManager.startsWith('bun@'); + } + } catch { + // Fall back to lockfile detection for malformed package manifests. + } + } + + return existsSync('bun.lock') || existsSync('bun.lockb'); +} + function setUpDangerFile() { const [existingFile, fallbackFile] = queryDangerfile(); diff --git a/packages/cli/src/templates/reassure-tests-bun b/packages/cli/src/templates/reassure-tests-bun new file mode 100755 index 000000000..015fa8ead --- /dev/null +++ b/packages/cli/src/templates/reassure-tests-bun @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -e + +BASELINE_BRANCH=${GITHUB_BASE_REF:="main"} + +# Required for `git switch` on CI +git fetch origin + +# Gather baseline perf measurements +git switch "$BASELINE_BRANCH" + +bun install +bun run reassure --baseline + +# Gather current perf measurements & compare results +git switch --detach - + +bun install +bun run reassure --branch diff --git a/packages/cli/tests/init.test.ts b/packages/cli/tests/init.test.ts new file mode 100644 index 000000000..ac668b5df --- /dev/null +++ b/packages/cli/tests/init.test.ts @@ -0,0 +1,51 @@ +import { mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { run } from '../src/commands/init'; + +describe('reassure init package manager support', () => { + const originalCwd = process.cwd(); + let testDirectory: string; + + beforeEach(() => { + testDirectory = mkdtempSync(path.join(tmpdir(), 'reassure-init-')); + process.chdir(testDirectory); + }); + + afterEach(() => { + process.chdir(originalCwd); + rmSync(testDirectory, { force: true, recursive: true }); + }); + + it('uses Bun commands when packageManager declares Bun', () => { + writeFileSync('package.json', JSON.stringify({ packageManager: 'bun@1.2.0' })); + + run({ silent: true, verbose: false }); + + const script = readFileSync('reassure-tests.sh', 'utf8'); + expect(script).toContain('bun install'); + expect(script).toContain('bun run reassure --baseline'); + expect(script).toContain('bun run reassure --branch'); + expect(script).not.toContain('yarn '); + expect(statSync('reassure-tests.sh').mode & 0o111).not.toBe(0); + }); + + it.each(['bun.lock', 'bun.lockb'])('uses Bun commands when %s exists', (lockfile) => { + writeFileSync(lockfile, ''); + + run({ silent: true, verbose: false }); + + const script = readFileSync('reassure-tests.sh', 'utf8'); + expect(script).toContain('bun install'); + expect(script).toContain('bun run reassure --baseline'); + }); + + it('keeps Yarn commands when Bun is not configured', () => { + run({ silent: true, verbose: false }); + + const script = readFileSync('reassure-tests.sh', 'utf8'); + expect(script).toContain('yarn install'); + expect(script).toContain('yarn reassure --baseline'); + expect(script).toContain('yarn reassure --branch'); + }); +}); diff --git a/packages/cli/tests/jest.config.js b/packages/cli/tests/jest.config.js new file mode 100644 index 000000000..0b701adf8 --- /dev/null +++ b/packages/cli/tests/jest.config.js @@ -0,0 +1,12 @@ +module.exports = { + rootDir: '..', + testMatch: ['/tests/**/*.test.ts'], + transform: { + '^.+\\.tsx?$': [ + 'babel-jest', + { + presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], + }, + ], + }, +}; diff --git a/packages/reassure/README.md b/packages/reassure/README.md index 429d432cb..0167eef81 100644 --- a/packages/reassure/README.md +++ b/packages/reassure/README.md @@ -85,6 +85,12 @@ Using npm npm install --save-dev reassure ``` +Using Bun + +```sh +bun add --dev reassure +``` + You will also need a working [Jest](https://jestjs.io/docs/getting-started) setup as well as one of either [React Native Testing Library](https://github.com/callstack/react-native-testing-library#installation) or [React Testing Library](https://testing-library.com/docs/react-testing-library/intro). See [Installation guide](https://callstack.github.io/reassure/docs/installation). @@ -219,6 +225,14 @@ Simply run: yarn reassure init ``` +Using Bun: + +```bash +bun run reassure init +``` + +When the project declares Bun in the `packageManager` field or contains a `bun.lock`/`bun.lockb` file, the generated CI script uses `bun install` and `bun run reassure`. This changes only the package manager commands; Reassure continues to run performance tests with Jest. + This will generate the following file structure ``` From 44fda7d314fc1b2be14b3bd89502233ed6955e5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20=C4=90=C3=ACnh=20Huy?= Date: Mon, 24 Aug 2026 04:28:39 +0700 Subject: [PATCH 2/2] feat(cli): generate package-manager-aware CI script --- .changeset/quick-buns-install.md | 2 +- docusaurus/docs/installation.md | 7 ++- packages/cli/src/commands/init.ts | 55 +++++++++++++++++-- packages/cli/src/templates/reassure-tests | 10 ++-- packages/cli/src/templates/reassure-tests-bun | 19 ------- packages/cli/tests/init.test.ts | 41 ++++++++++---- packages/cli/tests/jest.config.js | 1 + packages/reassure/README.md | 10 +++- 8 files changed, 102 insertions(+), 43 deletions(-) delete mode 100755 packages/cli/src/templates/reassure-tests-bun diff --git a/.changeset/quick-buns-install.md b/.changeset/quick-buns-install.md index 65aac315b..d665f45e7 100644 --- a/.changeset/quick-buns-install.md +++ b/.changeset/quick-buns-install.md @@ -3,4 +3,4 @@ '@callstack/reassure-cli': minor --- -Generate Bun-compatible CI scripts for projects that use Bun as their package manager. +Generate package-manager-aware CI scripts for projects using npm, Yarn, or Bun. diff --git a/docusaurus/docs/installation.md b/docusaurus/docs/installation.md index 5397e39a9..ab30da662 100644 --- a/docusaurus/docs/installation.md +++ b/docusaurus/docs/installation.md @@ -144,6 +144,11 @@ To make setting up Reassure more convenient we have prepared a CLI command which Simply run: + +```sh +npm exec -- reassure init +``` + ```sh yarn reassure init @@ -156,7 +161,7 @@ bun run reassure init -When the project declares Bun in the `packageManager` field or contains a `bun.lock`/`bun.lockb` file, the generated CI script uses `bun install` and `bun run reassure`. This changes only the package manager commands; Reassure continues to run performance tests with Jest. +The generated CI script detects npm, Yarn, or Bun from the `packageManager` field or the corresponding lockfile. Yarn remains the default when no supported package manager is detected. This changes only the package manager commands; Reassure continues to run performance tests with Jest. This will generate the following file structure diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index e560ff24b..7b6405e9b 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -1,4 +1,4 @@ -import { copyFileSync, existsSync, readFileSync, appendFileSync } from 'node:fs'; +import { appendFileSync, copyFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs'; import * as path from 'node:path'; import * as logger from '@callstack/reassure-logger'; import type { CommandModule } from 'yargs'; @@ -16,6 +16,23 @@ import { configureLoggerOptions } from '../utils/logger'; const TEMPLATE_PATH = path.join(__dirname, '..', 'templates'); +const PACKAGE_MANAGER_COMMANDS = { + npm: { + install: 'npm install', + reassure: 'npm exec -- reassure', + }, + yarn: { + install: 'yarn install', + reassure: 'yarn reassure', + }, + bun: { + install: 'bun install', + reassure: 'bun run reassure', + }, +} as const; + +type SupportedPackageManager = keyof typeof PACKAGE_MANAGER_COMMANDS; + /** * Generate requred Reassure files. */ @@ -56,27 +73,53 @@ function setUpCiScript() { return; } - const template = usesBun() ? 'reassure-tests-bun' : 'reassure-tests'; - copyFileSync(path.join(TEMPLATE_PATH, template), CI_SCRIPT); + const packageManager = detectPackageManager(); + const commands = PACKAGE_MANAGER_COMMANDS[packageManager]; + const template = readFileSync(path.join(TEMPLATE_PATH, 'reassure-tests'), 'utf8'); + const script = template + .split('{{INSTALL_COMMAND}}') + .join(commands.install) + .split('{{REASSURE_COMMAND}}') + .join(commands.reassure); + + writeFileSync(CI_SCRIPT, script, { mode: 0o755 }); logger.clearLine(); logger.log(`✅ CI Script: created`); logger.log(`🔗 ${path.resolve(CI_SCRIPT)}`); } -function usesBun(): boolean { +function detectPackageManager(): SupportedPackageManager { if (existsSync('package.json')) { try { const packageManager = JSON.parse(readFileSync('package.json', 'utf8')).packageManager; if (typeof packageManager === 'string') { - return packageManager === 'bun' || packageManager.startsWith('bun@'); + if (packageManager === 'npm' || packageManager.startsWith('npm@')) { + return 'npm'; + } + if (packageManager === 'yarn' || packageManager.startsWith('yarn@')) { + return 'yarn'; + } + if (packageManager === 'bun' || packageManager.startsWith('bun@')) { + return 'bun'; + } } } catch { // Fall back to lockfile detection for malformed package manifests. } } - return existsSync('bun.lock') || existsSync('bun.lockb'); + if (existsSync('bun.lock') || existsSync('bun.lockb')) { + return 'bun'; + } + if (existsSync('yarn.lock')) { + return 'yarn'; + } + if (existsSync('package-lock.json') || existsSync('npm-shrinkwrap.json')) { + return 'npm'; + } + + return 'yarn'; } function setUpDangerFile() { diff --git a/packages/cli/src/templates/reassure-tests b/packages/cli/src/templates/reassure-tests index 74290fdc4..3c9445200 100755 --- a/packages/cli/src/templates/reassure-tests +++ b/packages/cli/src/templates/reassure-tests @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -e +set -e BASELINE_BRANCH=${GITHUB_BASE_REF:="main"} @@ -9,11 +9,11 @@ git fetch origin # Gather baseline perf measurements git switch "$BASELINE_BRANCH" -yarn install -yarn reassure --baseline +{{INSTALL_COMMAND}} +{{REASSURE_COMMAND}} --baseline # Gather current perf measurements & compare results git switch --detach - -yarn install -yarn reassure --branch +{{INSTALL_COMMAND}} +{{REASSURE_COMMAND}} --branch diff --git a/packages/cli/src/templates/reassure-tests-bun b/packages/cli/src/templates/reassure-tests-bun deleted file mode 100755 index 015fa8ead..000000000 --- a/packages/cli/src/templates/reassure-tests-bun +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash -set -e - -BASELINE_BRANCH=${GITHUB_BASE_REF:="main"} - -# Required for `git switch` on CI -git fetch origin - -# Gather baseline perf measurements -git switch "$BASELINE_BRANCH" - -bun install -bun run reassure --baseline - -# Gather current perf measurements & compare results -git switch --detach - - -bun install -bun run reassure --branch diff --git a/packages/cli/tests/init.test.ts b/packages/cli/tests/init.test.ts index ac668b5df..4c3e2e2a3 100644 --- a/packages/cli/tests/init.test.ts +++ b/packages/cli/tests/init.test.ts @@ -17,30 +17,51 @@ describe('reassure init package manager support', () => { rmSync(testDirectory, { force: true, recursive: true }); }); - it('uses Bun commands when packageManager declares Bun', () => { - writeFileSync('package.json', JSON.stringify({ packageManager: 'bun@1.2.0' })); + it.each([ + ['npm@11.4.2', 'npm install', 'npm exec -- reassure'], + ['yarn@4.11.0', 'yarn install', 'yarn reassure'], + ['bun@1.2.0', 'bun install', 'bun run reassure'], + ])('uses %s commands when packageManager declares it', (packageManager, installCommand, reassureCommand) => { + writeFileSync('package.json', JSON.stringify({ packageManager })); run({ silent: true, verbose: false }); const script = readFileSync('reassure-tests.sh', 'utf8'); - expect(script).toContain('bun install'); - expect(script).toContain('bun run reassure --baseline'); - expect(script).toContain('bun run reassure --branch'); - expect(script).not.toContain('yarn '); + expect(script).toContain(installCommand); + expect(script).toContain(`${reassureCommand} --baseline`); + expect(script).toContain(`${reassureCommand} --branch`); + expect(script).not.toContain('{{'); expect(statSync('reassure-tests.sh').mode & 0o111).not.toBe(0); }); - it.each(['bun.lock', 'bun.lockb'])('uses Bun commands when %s exists', (lockfile) => { + it.each([ + ['package-lock.json', 'npm install', 'npm exec -- reassure'], + ['npm-shrinkwrap.json', 'npm install', 'npm exec -- reassure'], + ['yarn.lock', 'yarn install', 'yarn reassure'], + ['bun.lock', 'bun install', 'bun run reassure'], + ['bun.lockb', 'bun install', 'bun run reassure'], + ])('uses the matching commands when %s exists', (lockfile, installCommand, reassureCommand) => { writeFileSync(lockfile, ''); run({ silent: true, verbose: false }); const script = readFileSync('reassure-tests.sh', 'utf8'); - expect(script).toContain('bun install'); - expect(script).toContain('bun run reassure --baseline'); + expect(script).toContain(installCommand); + expect(script).toContain(`${reassureCommand} --baseline`); }); - it('keeps Yarn commands when Bun is not configured', () => { + it('prefers packageManager over stale lockfiles', () => { + writeFileSync('package.json', JSON.stringify({ packageManager: 'npm@11.4.2' })); + writeFileSync('bun.lock', ''); + + run({ silent: true, verbose: false }); + + const script = readFileSync('reassure-tests.sh', 'utf8'); + expect(script).toContain('npm install'); + expect(script).not.toContain('bun install'); + }); + + it('defaults to Yarn when no supported package manager is configured', () => { run({ silent: true, verbose: false }); const script = readFileSync('reassure-tests.sh', 'utf8'); diff --git a/packages/cli/tests/jest.config.js b/packages/cli/tests/jest.config.js index 0b701adf8..2384a2d45 100644 --- a/packages/cli/tests/jest.config.js +++ b/packages/cli/tests/jest.config.js @@ -1,5 +1,6 @@ module.exports = { rootDir: '..', + modulePathIgnorePatterns: ['/lib/'], testMatch: ['/tests/**/*.test.ts'], transform: { '^.+\\.tsx?$': [ diff --git a/packages/reassure/README.md b/packages/reassure/README.md index 0167eef81..6d7e88d7f 100644 --- a/packages/reassure/README.md +++ b/packages/reassure/README.md @@ -221,6 +221,14 @@ To make setting up the CI integration and all prerequisites more convenient, we Simply run: +Using npm: + +```bash +npm exec -- reassure init +``` + +Using Yarn: + ```bash yarn reassure init ``` @@ -231,7 +239,7 @@ Using Bun: bun run reassure init ``` -When the project declares Bun in the `packageManager` field or contains a `bun.lock`/`bun.lockb` file, the generated CI script uses `bun install` and `bun run reassure`. This changes only the package manager commands; Reassure continues to run performance tests with Jest. +The generated CI script detects npm, Yarn, or Bun from the `packageManager` field or the corresponding lockfile. Yarn remains the default when no supported package manager is detected. This changes only the package manager commands; Reassure continues to run performance tests with Jest. This will generate the following file structure