Describe the bug
detectZisiBuilder determines whether the project's package.json has "type": "module" like this:
|
// @ts-expect-error(serhalp) -- We seem to be incorrectly using this function, but it seems to work... Investigate. |
|
const packageJson = await readPackageUp(func.mainFile) |
|
const hasTypeModule = packageJson?.packageJson.type === 'module' |
// @ts-expect-error(serhalp) -- We seem to be incorrectly using this function, but it seems to work... Investigate.
const packageJson = await readPackageUp(func.mainFile)
const hasTypeModule = packageJson?.packageJson.type === 'module'
read-package-up expects an options object ({ cwd }), not a file path string. The string is silently ignored (find-up reads options.cwd, which is undefined), so the lookup always starts from process.cwd() instead of the function's directory. The @ts-expect-error TODO on that line is exactly this bug — it only "seems to work" because process.cwd() is usually inside the project.
It stops working when netlify dev is run with --cwd <project> from a directory outside the project: the CLI honors --cwd for config resolution but never calls process.chdir(), so readPackageUp searches upward from the invocation directory. If no package.json exists there or in its ancestors, hasTypeModule is false, and buildFunction skips writing the {"type":"commonjs"} marker into each function's .netlify/functions-serve/<name>/ directory:
|
// some projects include a package.json with "type=module", forcing Node to interpret every descending file |
|
// as ESM. ZISI outputs CJS, so we emit an overriding directive into the output directory. |
|
if (hasTypeModule) { |
|
await writeFile( |
|
path.join(functionPath, 'package.json'), |
|
JSON.stringify({ |
|
type: 'commonjs', |
|
}), |
|
) |
|
} |
In a project whose root package.json has "type": "module", Node then parses every zisi/esbuild CJS function bundle as ESM, and every function invocation 500s with:
ReferenceError: module is not defined in ES module scope
Running the identical command from inside the project works (the buggy fallback happens to resolve the right package.json), which is presumably why this is rarely reported. Editors/wrappers that spawn netlify dev --cwd <project> with a different working directory hit it every time.
A secondary effect of the same line: mustUseEsbuild = hasTypeModule || mustTranspile is also computed from the wrong package.json, so a "type": "module" project with plain .js functions and no explicit node_bundler wouldn't get esbuild auto-selected either.
Suggested fix
const packageJson = await readPackageUp({ cwd: path.dirname(func.mainFile) })
This is exactly what @netlify/functions-dev already does in its equivalent code path (its dist/main.js: readPackageUp({ cwd: path.dirname(func.mainFile) })), and it lets you drop the @ts-expect-error. Happy to open a PR.
Steps to reproduce
- Create a project with:
- root
package.json containing "type": "module"
netlify.toml with [functions] node_bundler = "esbuild" and a v1 TypeScript function (e.g. netlify/functions/hello.ts)
- From a directory outside the project that has no
package.json in itself or any ancestor (e.g. C:\ or a fresh temp dir), run:
netlify dev --cwd /path/to/project
curl http://localhost:8888/.netlify/functions/hello → 500, terminal shows ReferenceError: module is not defined in ES module scope, and .netlify/functions-serve/hello/ contains no package.json marker.
- Run the same
netlify dev from inside the project directory → the function works and the {"type":"commonjs"} marker is written.
Verified on netlify-cli 26.0.1; the code is unchanged in v27.1.1 and current main (85c0113). Patching the installed dist/lib/functions/runtimes/js/builders/zisi.js with the one-line fix above resolves it.
Configuration
[build]
publish = "dist"
[functions]
node_bundler = "esbuild"
Environment
System:
OS: Windows 11 10.0.26200
CPU: (8) x64 Intel(R) Core(TM) Ultra 7 258V
Memory: 8.57 GB / 31.49 GB
Binaries:
Node: 24.15.0 - C:\Program Files\nodejs\node.EXE
npm: 11.12.1 - C:\Program Files\nodejs\npm.CMD
npmGlobalPackages:
netlify-cli: 26.0.1
(Reproduced on 26.0.1; bug confirmed present in 27.1.1 and main by source inspection. Not OS-specific — the lookup falls back to process.cwd() on any platform.)
Describe the bug
detectZisiBuilderdetermines whether the project'spackage.jsonhas"type": "module"like this:cli/src/lib/functions/runtimes/js/builders/zisi.ts
Lines 189 to 191 in 85c0113
read-package-upexpects an options object ({ cwd }), not a file path string. The string is silently ignored (find-upreadsoptions.cwd, which isundefined), so the lookup always starts fromprocess.cwd()instead of the function's directory. The@ts-expect-errorTODO on that line is exactly this bug — it only "seems to work" becauseprocess.cwd()is usually inside the project.It stops working when
netlify devis run with--cwd <project>from a directory outside the project: the CLI honors--cwdfor config resolution but never callsprocess.chdir(), soreadPackageUpsearches upward from the invocation directory. If nopackage.jsonexists there or in its ancestors,hasTypeModuleisfalse, andbuildFunctionskips writing the{"type":"commonjs"}marker into each function's.netlify/functions-serve/<name>/directory:cli/src/lib/functions/runtimes/js/builders/zisi.ts
Lines 91 to 100 in 85c0113
In a project whose root
package.jsonhas"type": "module", Node then parses every zisi/esbuild CJS function bundle as ESM, and every function invocation 500s with:Running the identical command from inside the project works (the buggy fallback happens to resolve the right
package.json), which is presumably why this is rarely reported. Editors/wrappers that spawnnetlify dev --cwd <project>with a different working directory hit it every time.A secondary effect of the same line:
mustUseEsbuild = hasTypeModule || mustTranspileis also computed from the wrongpackage.json, so a"type": "module"project with plain.jsfunctions and no explicitnode_bundlerwouldn't get esbuild auto-selected either.Suggested fix
This is exactly what
@netlify/functions-devalready does in its equivalent code path (itsdist/main.js:readPackageUp({ cwd: path.dirname(func.mainFile) })), and it lets you drop the@ts-expect-error. Happy to open a PR.Steps to reproduce
package.jsoncontaining"type": "module"netlify.tomlwith[functions] node_bundler = "esbuild"and a v1 TypeScript function (e.g.netlify/functions/hello.ts)package.jsonin itself or any ancestor (e.g.C:\or a fresh temp dir), run:curl http://localhost:8888/.netlify/functions/hello→ 500, terminal showsReferenceError: module is not defined in ES module scope, and.netlify/functions-serve/hello/contains nopackage.jsonmarker.netlify devfrom inside the project directory → the function works and the{"type":"commonjs"}marker is written.Verified on netlify-cli 26.0.1; the code is unchanged in v27.1.1 and current
main(85c0113). Patching the installeddist/lib/functions/runtimes/js/builders/zisi.jswith the one-line fix above resolves it.Configuration
Environment
(Reproduced on 26.0.1; bug confirmed present in 27.1.1 and
mainby source inspection. Not OS-specific — the lookup falls back toprocess.cwd()on any platform.)