fix(tooling): fix stringWidth error in release-it interactive prompt (#179)

## Why

### Problem
Running `bun release -i patch` (or any interactive `release-it`
invocation) always failed with:

```
ERROR stringWidth is not a function
```

### Root Cause
`@inquirer/core` (used by `inquirer@12`, which `release-it@19` depends
on) bundles its own `wrap-ansi@6.2.0` (CJS). That package calls
`require('string-width')`, but the top-level `string-width` in this
project is v8 — pure ESM. When required via CJS, Node returns a module
namespace object instead of a function, causing the crash when
`release-it` tried to render its interactive confirmation prompt.

## What

- Added `scripts/patch-inquirer-string-width.js`: installs
CJS-compatible versions of `string-width@4`, `strip-ansi@6`,
`ansi-regex@5`, and `is-fullwidth-code-point@3` into
`node_modules/@inquirer/core/node_modules/` so `wrap-ansi@6` resolves
them correctly.
- Added a `postinstall` script to `package.json` so the patch is
re-applied automatically after every `bun install`.

## Verification

### Tests
No automated tests — this is a dev tooling fix. Verified manually by
running `node node_modules/release-it/bin/release-it.js --dry-run` and
confirming the interactive prompt renders without the `stringWidth`
error.

### Manual
1. `bun install` — postinstall script runs and outputs `Patch applied.`
2. `bun release -i patch` — interactive prompt now renders correctly.
This commit is contained in:
Víctor Falcón 2026-03-01 19:53:01 +00:00 committed by GitHub
parent 6c5961da05
commit 866f90838e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 398 additions and 274 deletions

516
bun.lock

File diff suppressed because it is too large Load Diff

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "whisper-money",
"version": "0.1.14",
"version": "0.1.15",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "whisper-money",
"version": "0.1.14",
"version": "0.1.15",
"dependencies": {
"@headlessui/react": "^2.2.0",
"@inertiajs/react": "^2.1.4",

View File

@ -1,10 +1,11 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "whisper-money",
"version": "0.1.14",
"version": "0.1.15",
"private": true,
"type": "module",
"scripts": {
"postinstall": "node scripts/patch-inquirer-string-width.js",
"build": "vite build",
"build:ssr": "vite build && vite build --ssr",
"dev": "vite",
@ -18,89 +19,89 @@
"types": "tsc --noEmit"
},
"devDependencies": {
"@eslint/js": "^9.19.0",
"@laravel/vite-plugin-wayfinder": "^0.1.3",
"@release-it/conventional-changelog": "^10.0.4",
"@sentry/vite-plugin": "^4.6.1",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
"@eslint/js": "^9.39.3",
"@laravel/vite-plugin-wayfinder": "^0.1.7",
"@release-it/conventional-changelog": "^10.0.5",
"@sentry/vite-plugin": "^4.9.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@types/json-logic-js": "^2.0.8",
"@types/node": "^22.13.5",
"@types/node": "^22.19.13",
"babel-plugin-react-compiler": "^1.0.0",
"eslint": "^9.17.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-react": "^7.37.3",
"eslint-plugin-react-hooks": "^7.0.0",
"eslint": "^9.39.3",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"jsdom": "^25.0.1",
"playwright": "^1.57.0",
"prettier": "^3.4.2",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-tailwindcss": "^0.6.11",
"playwright": "^1.58.2",
"prettier": "^3.8.1",
"prettier-plugin-organize-imports": "^4.3.0",
"prettier-plugin-tailwindcss": "^0.6.14",
"release-it": "^19.2.4",
"typescript-eslint": "^8.23.0",
"vitest": "^2.1.8"
"typescript-eslint": "^8.56.1",
"vitest": "^2.1.9"
},
"dependencies": {
"@headlessui/react": "^2.2.0",
"@inertiajs/react": "^2.1.4",
"@headlessui/react": "^2.2.9",
"@inertiajs/react": "^2.3.17",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-avatar": "^1.1.3",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-collapsible": "^1.1.3",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-navigation-menu": "^1.2.5",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-progress": "^1.1.8",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-toggle": "^1.1.2",
"@radix-ui/react-toggle-group": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.8",
"@sentry/react": "^10.32.1",
"@tailwindcss/vite": "^4.1.11",
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
"@sentry/react": "^10.40.0",
"@tailwindcss/vite": "^4.2.1",
"@tanstack/react-table": "^8.21.3",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@vitejs/plugin-react": "^5.0.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
"concurrently": "^9.0.1",
"concurrently": "^9.2.1",
"date-fns": "^4.1.0",
"dexie": "^4.2.1",
"dexie": "^4.3.0",
"dexie-react-hooks": "^4.2.0",
"facehash": "^0.1.0",
"globals": "^15.14.0",
"globals": "^15.15.0",
"input-otp": "^1.4.2",
"json-logic-js": "^2.0.5",
"laravel-vite-plugin": "^2.0",
"laravel-vite-plugin": "^2.1.0",
"lucide-react": "^0.553.0",
"motion": "^12.34.3",
"posthog-js": "^1.331.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"recharts": "^3.5.1",
"posthog-js": "^1.356.1",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"recharts": "^3.7.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.0.1",
"tailwindcss": "^4.0.0",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.2.1",
"tw-animate-css": "^1.4.0",
"typescript": "^5.7.2",
"uuidv7": "^1.0.2",
"typescript": "^5.9.3",
"uuidv7": "^1.1.0",
"vaul": "^1.1.2",
"vite": "^7.0.4",
"vite": "^7.3.1",
"xlsx": "^0.18.5"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "4.9.5",
"@rollup/rollup-win32-x64-msvc": "4.9.5",
"@tailwindcss/oxide-linux-x64-gnu": "^4.0.1",
"@tailwindcss/oxide-win32-x64-msvc": "^4.0.1",
"lightningcss-linux-x64-gnu": "^1.29.1",
"lightningcss-win32-x64-msvc": "^1.29.1"
"@tailwindcss/oxide-linux-x64-gnu": "^4.2.1",
"@tailwindcss/oxide-win32-x64-msvc": "^4.2.1",
"lightningcss-linux-x64-gnu": "^1.31.1",
"lightningcss-win32-x64-msvc": "^1.31.1"
}
}

View File

@ -0,0 +1,49 @@
/**
* Patch script to fix ESM/CJS interop issue with string-width in @inquirer/core.
*
* @inquirer/core bundles wrap-ansi@6 (CJS) which requires string-width@4 (CJS).
* However, the top-level string-width is v8 (ESM-only), causing:
* ERROR stringWidth is not a function
*
* This script installs CJS-compatible versions of string-width and its
* dependencies into @inquirer/core's nested node_modules after each install.
*/
import { execSync } from 'child_process';
import { existsSync, mkdirSync } from 'fs';
import { resolve } from 'path';
const targetDir = resolve(
import.meta.dirname,
'../node_modules/@inquirer/core/node_modules',
);
const packages = [
{ name: 'string-width', version: '4.2.3' },
{ name: 'strip-ansi', version: '6.0.1' },
{ name: 'ansi-regex', version: '5.0.1' },
{ name: 'is-fullwidth-code-point', version: '3.0.0' },
];
for (const pkg of packages) {
const dest = resolve(targetDir, pkg.name);
if (existsSync(dest)) {
continue;
}
console.log(`Patching ${pkg.name}@${pkg.version} into @inquirer/core...`);
mkdirSync(dest, { recursive: true });
const tarball = `${pkg.name}-${pkg.version}.tgz`;
execSync(`npm pack ${pkg.name}@${pkg.version} --quiet`, {
cwd: dest,
stdio: 'pipe',
});
execSync(`tar -xzf ${tarball} --strip-components=1`, {
cwd: dest,
stdio: 'pipe',
});
execSync(`rm ${tarball}`, { cwd: dest, stdio: 'pipe' });
}
console.log('Patch applied.');