Conversation
Migrate the JS library build from tsup to a custom tsc-build.mjs script that produces dual CJS/ESM bundles, and upgrade TypeScript to ^7.0.2 across packages. - Swap `compile`/`build:watch` scripts to invoke `tsc-build.mjs` - Remove `tsup` from devDependencies - Fix `./async` export types path (`.ts` -> `.d.ts`) - Add explicit type args to `action` in background-action - Add `rootDir: src` to testapp tsconfigs - Ignore tsc-build temp artifacts in .gitignore
There was a problem hiding this comment.
Code Review
This pull request replaces tsup with a custom dual CJS/ESM build driver script (tsc-build.mjs) based on TypeScript 7, updating package scripts, dependencies, and lockfiles accordingly. It also adjusts tsconfig.json files in testapps and refines types in background-action.ts. The review feedback highlights several critical improvements for the build script: throwing errors instead of calling process.exit directly to preserve watch mode and ensure cleanup, resolving tsc robustly in monorepos with fallbacks, fixing a bug where parent directory imports are skipped during ESM rewrites, and adding an error listener to FSWatcher to prevent crashes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| stdio: 'inherit', | ||
| cwd: pkgDir, | ||
| }); | ||
| if (res.status !== 0) process.exit(res.status ?? 1); |
There was a problem hiding this comment.
Calling process.exit directly inside runTsc will immediately terminate the entire process on any compilation error. In --watch mode, this defeats the purpose of watching for changes, as any typo or syntax error will kill the watch process. Additionally, terminating the process immediately bypasses the finally block in build(), leaving temporary build artifacts behind in the repository.
Instead, throw an error with the exit status so that the finally block can clean up the temporary files, and the watch mode can catch the error and continue running.
if (res.status !== 0) {
const err = new Error(`tsc exited with status ${res.status}`);
err.status = res.status;
throw err;
}| const tscBin = resolve( | ||
| pkgDir, | ||
| 'node_modules', | ||
| '.bin', | ||
| process.platform === 'win32' ? 'tsc.cmd' : 'tsc' | ||
| ); |
There was a problem hiding this comment.
Manually resolving tscBin relative to pkgDir can fail in monorepos where typescript is hoisted to the workspace root node_modules (or if the package manager uses a different layout).
Adding a fallback to the workspace root's node_modules and a final fallback to the system PATH makes the build script much more robust across different environments and package managers.
const localTsc = resolve(
pkgDir,
'node_modules',
'.bin',
process.platform === 'win32' ? 'tsc.cmd' : 'tsc'
);
const rootTsc = resolve(
jsRoot,
'node_modules',
'.bin',
process.platform === 'win32' ? 'tsc.cmd' : 'tsc'
);
const tscBin = existsSync(localTsc)
? localTsc
: existsSync(rootTsc)
? rootTsc
: 'tsc';| import { | ||
| cpSync, | ||
| mkdirSync, | ||
| readdirSync, | ||
| readFileSync, | ||
| rmSync, | ||
| statSync, | ||
| watch, | ||
| writeFileSync, | ||
| } from 'node:fs'; |
There was a problem hiding this comment.
Import existsSync from node:fs to support checking for the existence of the tsc binary in the package or workspace root.
| import { | |
| cpSync, | |
| mkdirSync, | |
| readdirSync, | |
| readFileSync, | |
| rmSync, | |
| statSync, | |
| watch, | |
| writeFileSync, | |
| } from 'node:fs'; | |
| import { | |
| cpSync, | |
| existsSync, | |
| mkdirSync, | |
| readdirSync, | |
| readFileSync, | |
| rmSync, | |
| statSync, | |
| watch, | |
| writeFileSync, | |
| } from 'node:fs'; |
| const lastSegment = importPath.split('/').pop() || ''; | ||
| if (lastSegment.includes('.')) return match; |
There was a problem hiding this comment.
If importPath is a parent directory import like .. or ../.., lastSegment will be . or ... Since these contain a dot, lastSegment.includes('.') will evaluate to true, causing the script to skip rewriting them. This leaves a bare directory import in the ESM output, which is invalid in standard ESM runtimes.
Updating the check to exclude . and .. ensures parent directory imports are correctly resolved and rewritten to point to their /index.mjs files.
| const lastSegment = importPath.split('/').pop() || ''; | |
| if (lastSegment.includes('.')) return match; | |
| const lastSegment = importPath.split('/').pop() || ''; | |
| if (lastSegment.includes('.') && lastSegment !== '.' && lastSegment !== '..') return match; |
| if (args.includes('--watch')) { | ||
| safeBuild(); | ||
| console.log(`tsc-build: watching ${rootDir}/ for changes...`); | ||
| let timer = null; | ||
| watch(join(pkgDir, rootDir), { recursive: true }, () => { | ||
| clearTimeout(timer); | ||
| timer = setTimeout(safeBuild, 100); | ||
| }); | ||
| } else { | ||
| build(); | ||
| } |
There was a problem hiding this comment.
In Node.js, if an error event is emitted on an FSWatcher (e.g., due to permission issues or directory deletion) and there are no listeners, the process will crash with an unhandled exception. Adding an error listener to the watcher prevents unexpected crashes.
Additionally, update the non-watch execution block to catch the thrown error and exit with the correct status code.
| if (args.includes('--watch')) { | |
| safeBuild(); | |
| console.log(`tsc-build: watching ${rootDir}/ for changes...`); | |
| let timer = null; | |
| watch(join(pkgDir, rootDir), { recursive: true }, () => { | |
| clearTimeout(timer); | |
| timer = setTimeout(safeBuild, 100); | |
| }); | |
| } else { | |
| build(); | |
| } | |
| if (args.includes('--watch')) { | |
| safeBuild(); | |
| console.log(`tsc-build: watching ${rootDir}/ for changes...`); | |
| let timer = null; | |
| const watcher = watch(join(pkgDir, rootDir), { recursive: true }, () => { | |
| clearTimeout(timer); | |
| timer = setTimeout(safeBuild, 100); | |
| }); | |
| watcher.on('error', (err) => { | |
| console.error(`tsc-build watch error: ${err.message}`); | |
| }); | |
| } else { | |
| try { | |
| build(); | |
| } catch (err) { | |
| process.exit(err.status ?? 1); | |
| } | |
| } |
Migrate the JS library build from tsup to a custom tsc-build.mjs script that produces dual CJS/ESM bundles, and upgrade TypeScript to ^7.0.2 across packages.
compile/build:watchscripts to invoketsc-build.mjstsupfrom devDependencies./asyncexport types path (.ts->.d.ts)actionin background-actionrootDir: srcto testapp tsconfigs