The Issue I need to solve
Simply put, I need to check if a node package is installed in runtime. The reason is that I’m building a new toolkit ZenStack, that supercharges Prisma with a powerful access control layer and API generation. Since it is built above Prisma, when running the CLI provided by ZenStack, it must check whether Prisma has already been installed. Otherwise, it would prompt the user to install Prisma first.
Get the answer
Let’s try to ask ChatGPT directly:
Cool! The answer looks compelling. Problem solved!
The side effect
It does work, but it brings one side effect. In my CLI project, I use async-exit-hook to handle uncaught errors like the below:
// You can hook uncaught errors with uncaughtExceptionHandler(), consequently adding
// async support to uncaught errors (normally uncaught errors result in a synchronous exit).
exitHook.uncaughtExceptionHandler(err => {
console.error(err);
});
After the require(’prisma’)
is executed successfully, which means the Prisma is there, if later in other code you throw any uncaught exception, the uncaughtExceptionHandler
would never be triggered. But if require any other existing module like require(’uuid’)
, then the uncaughtExceptionHandler
would still be triggered as before.
Although I haven’t found the real cause, from the above It looks like some code is running when Prisma is imported. I’m not sure whether Prisma provides any flag to control it; even if it does, it still doesn’t sound like a clean solution to me because it is like Pandora's box. You won’t know what gets executed.
Clean solution
The most direct way to avoid a side effect is to use a separate process. So how would you do that if the job is assigned to you? I will use the npm command. So let’s ask the omniscient again:
This looks like a clean solution to me.
Push harder
As good developers, we should always try to think further. What if the npm command is not installed or is broken? If we know how npm finds the module, we could do that ourselves.
Let’s ask again:
As the npm command obviously won’t execute any code for the package, it does find the name and version as the answer specified:
It will display the names and versions of all the installed package
So, where does it get the version information? Of course, from package.json file. Actually, you can easily verify it by creating dummy-package
folder under node_modules
, and then creating the package.json
file with the below content:
{
"name": "dummy-package",
"version": "9.9.9"
}
Then after running npm list --depth=0 dummy-package
, you could see the package info:
helloworld@1.0.0 /Users/jiasheng/branch/helloworld
└── dummy-package@9.9.9 extraneous
Therefore, instead of requiring the module like the original solution, we could change it to require the package.json file like:
const prisma:any = require('prisma/package.json')
Not only does it get rid of the side effect, but also you could get more information for that package, like version, etc.
Final Words
Anyway, you can see ChatGPT really could help us a lot, even writing the code for us, but it is us who actually think and resolve the issue thoroughly. Coming to the thought that AI would replace developers, I think Google should worry about that rather than us. 😉
Follow up
There is one comment left by Anthony that brings up one “side effect” of using ChatGPT “while ChatGPT arguably helped you solve your problem, you still failed to actually learn what was going on”, which I can’t agree more. Therefore, I investigated a little bit about the source code of Prisma, it turns out that the side effect of the first solution ChatGPT given is that when Prisma is required, it registered unhandledRejection
event handler in the below line:
https://github.com/prisma/prisma/blob/main/packages/cli/src/bin.ts#L57
process.on('unhandledRejection', (e) => {
debug(e)
})
The exception thrown later is in the Promise handler, so it got intercepted by this event handler. Therefore, another way of fixing it is to also register unhandledRejection
event handler using exitHook
like below:
// You can hook unhandled rejections with unhandledRejectionHandler()
exitHook.unhandledRejectionHandler(err => {
console.error(err);
});
But I would still stick to the required package.json solution as it has no side effects at all. 😁
ZenStack is our open-source TypeScript toolkit for building high-quality, scalable apps faster, smarter, and happier. It centralizes the data model, access policies, and validation rules in a single declarative schema on top of Prisma, well-suited for AI-enhanced development. Start integrating ZenStack with your existing stack now!