From bab0e99d89779840ab959e60b7c79545171c1e55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=93=E8=89=AF?= <1204183885@qq.com> Date: Wed, 14 Oct 2026 10:03:12 +0000 Subject: [PATCH] fix(build): re-apply the ink patch when install skipped lifecycle scripts (#14170) packages/cli compiles against ink frame-controller types that only patches/ink+7.0.3.patch adds; pristine ink@7.0.3 exports none of them. That patch reaches node_modules through the root postinstall, so an install that skipped lifecycle scripts (--ignore-scripts, or ignore-scripts=true in the user's npm/pnpm config) leaves pristine ink in place and the build fails with ~50 errors that all blame ink -- which sends a reporter to compare ink versions on npm instead of to the missing patch. Because noEmitOnError is unset, tsc still emitted a dist that runs with text selection broken. scripts/build.js already heals a missing node_modules; heal a missing patch the same way. frame-controller.d.ts is created by that patch and absent from pristine ink@7.0.3, so its absence is the signal. If re-applying still does not produce it, name the patch rather than letting tsc point at ink. Fixes #14110 Co-authored-by: Qwen-Coder --- CONTRIBUTING.md | 2 ++ scripts/build.js | 20 ++++++++++++++++++++ scripts/tests/package-scripts.test.js | 25 +++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f2a9b6e9a79..6dd4b29e185 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -110,6 +110,8 @@ To install dependencies defined in `package.json` as well as root dependencies, corepack pnpm install --frozen-lockfile ``` +The install also applies `patches/ink+7.0.3.patch` through the root `postinstall` (`patch-package`). `packages/cli` compiles against the `ink` frame-controller types that patch adds, and no published `ink` release exports them, so an install that skipped lifecycle scripts (`--ignore-scripts`, or `ignore-scripts=true` in your npm/pnpm config) leaves the build failing with many `Module 'ink' has no exported member ...` errors. `npm run build` re-applies the patch when it is missing; to apply it by hand, run `npm run postinstall`. + To build the entire project (all packages): ```bash diff --git a/scripts/build.js b/scripts/build.js index 9af62e04ee9..3c06f04cd04 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -33,6 +33,26 @@ if (!existsSync(join(root, 'node_modules'))) { }); } +// The root postinstall applies patches/ink+7.0.3.patch, and packages/cli +// compiles against ink types only that patch adds: an install that skipped +// lifecycle scripts fails the build with ~50 errors that all blame ink. The +// patch creates frame-controller.d.ts, which pristine ink@7.0.3 lacks. +const inkFrameController = join( + root, + 'node_modules/ink/build/frame-controller.d.ts', +); +if (!existsSync(inkFrameController)) { + execSync('npm run postinstall', { stdio: 'inherit', cwd: root }); + if (!existsSync(inkFrameController)) { + console.error( + 'build: patches/ink+7.0.3.patch is not applied to node_modules/ink, so\n' + + 'packages/cli cannot typecheck against it. Run "npm run postinstall"\n' + + 'and resolve what patch-package reports.', + ); + process.exit(1); + } +} + // build all workspaces/packages in dependency order execSync('npm run generate', { stdio: 'inherit', cwd: root }); diff --git a/scripts/tests/package-scripts.test.js b/scripts/tests/package-scripts.test.js index 30330c7e5b4..ffcbe4c72fa 100644 --- a/scripts/tests/package-scripts.test.js +++ b/scripts/tests/package-scripts.test.js @@ -1048,6 +1048,31 @@ describe('package scripts', () => { expect(readPackageJson().workspaces).toContain('packages/*'); }); + it('detects an unapplied ink patch by a file the patch creates', () => { + const buildScript = readFileSync( + path.join(root, 'scripts/build.js'), + 'utf8', + ); + const sentinel = buildScript.match( + /'node_modules\/ink\/build\/([^']+)'/, + )?.[1]; + expect(sentinel).toBeDefined(); + expect(buildScript).toContain('npm run postinstall'); + + const inkPatch = readdirSync(path.join(root, 'patches')).find((name) => + name.startsWith('ink+'), + ); + expect(inkPatch).toBeDefined(); + const created = readFileSync(path.join(root, 'patches', inkPatch), 'utf8') + .split(/^diff --git /m) + .find((file) => + file.startsWith(`a/node_modules/ink/build/${sentinel} b/node_modules/`), + ); + // The sentinel only proves the patch is missing if no published ink ships + // it, i.e. the patch must create the file rather than edit an existing one. + expect(created).toContain('new file mode'); + }); + it('selects the CLI dependency closure without selecting the same-named root', () => { const buildScript = readFileSync( path.join(root, 'scripts/build.js'),