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 <qwen-coder@alibabacloud.com>
This commit is contained in:
易良 2026-10-14 10:03:12 +00:00 • committed by GitHub
parent 4b6b4270d0
commit bab0e99d89
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 47 additions and 0 deletions

View file

@ -110,6 +110,8 @@ To install dependencies defined in `package.json` as well as root dependencies,
corepack pnpm install --frozen-lockfile 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): To build the entire project (all packages):
```bash ```bash

View file

@ -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 // build all workspaces/packages in dependency order
execSync('npm run generate', { stdio: 'inherit', cwd: root }); execSync('npm run generate', { stdio: 'inherit', cwd: root });

View file

@ -1048,6 +1048,31 @@ describe('package scripts', () => {
expect(readPackageJson().workspaces).toContain('packages/*'); 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', () => { it('selects the CLI dependency closure without selecting the same-named root', () => {
const buildScript = readFileSync( const buildScript = readFileSync(
path.join(root, 'scripts/build.js'), path.join(root, 'scripts/build.js'),