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'),