fix: Upgrade node-plop to 0.32.3 (#11756)
## Summary
Upgrades `node-plop` from 0.26.3 to 0.32.3 in `@turbo/gen`, resolving
security vulnerabilities in transitive dependencies (`tmp`, `semver`,
`lodash-es`). This is the dependency upgrade that was [attempted
previously](https://github.com/vercel/turborepo/pull/10847) and reverted
due to [#10879](https://github.com/vercel/turborepo/issues/10879).
## Why node-plop 0.32 is hard to upgrade
node-plop 0.32 switched from `require()` to `import()` for loading user
config files. In `"type": "commonjs"` projects (common in Next.js
monorepos), `import()` of `.ts` files routes through the CJS loader
where ESM syntax (`export default`) is a syntax error. This is why the
previous upgrade attempt broke `turbo gen` for users with TypeScript
generator configs.
## How this PR solves it
Three layered changes:
1. **Switch `@turbo/utils` and `@turbo/gen` to ESM** — `@turbo/utils` is
a private, unbundled internal package consumed only by other workspace
packages that bundle with tsdown. `@turbo/gen` is consumed as a CLI
binary and type-only import. Neither change is user-facing.
2. **Precompile `.ts` configs to `.mjs`** — Before passing config paths
to node-plop, `.ts` files are compiled to temporary `.mjs` files using
esbuild. The `.mjs` extension forces Node.js to treat them as ESM
regardless of the project's `"type"` field, eliminating the CJS/ESM
mismatch.
3. **Replace Jest with `node:test`** — The ESM migration made Jest
incompatible (ts-jest can't parse `import.meta`). Tests are rewritten
using Node's native test runner with `mock.module()` for ESM module
mocking.
## Testing
All combinations of project type and config format were tested:
| Project Type | config.ts | config.ts + PlopTypes | config.js (CJS) |
config.js (ESM) | config.cjs |
|---|---|---|---|---|---|
| no type field | Pass | Pass | Pass | Pass | Pass |
| `"type": "commonjs"` | Pass | Pass | Pass | N/A* | Pass |
| `"type": "module"` | Pass | Pass | N/A* | Pass | Pass |
\* These are fundamental Node.js mismatches (wrong module syntax for the
file extension), not regressions.
Additionally verified:
- All `@turbo/utils` consumer packages build (`create-turbo`,
`eslint-plugin-turbo`, `turbo-codemod`, `turbo-ignore`,
`turbo-workspaces`)
- `@turbo/utils` test suite passes (9 suites, 66 tests)
- `@turbo/gen` test suite passes (9 tests)
- `attw` type checks pass
- CLI `--version`, `raw` command (Rust CLI invocation), workspace
generators, root plopfiles, `--config` flag, `turbo.paths` injection,
append actions all verified
Closes #10879