Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/proxy",
"version": "0.2.6",
"version": "0.2.7",
"description": "A CLI tool to run an Express server that proxies CRUD requests to a ZenStack backend",
"main": "index.js",
"publishConfig": {
Expand All @@ -20,7 +20,12 @@
"clean": "rimraf dist",
"build": "pnpm clean && tsc && copyfiles -F \"bin/*\" dist && copyfiles ./README.md ./LICENSE ./package.json dist && pnpm pack dist --pack-destination ../build"
},
"keywords": ["zenstack", "proxy", "express", "cli"],
"keywords": [
"zenstack",
"proxy",
"express",
"cli"
],
"author": "",
"license": "MIT",
"dependencies": {
Expand Down Expand Up @@ -53,4 +58,4 @@
"require": "./index.js"
}
}
}
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function createProgram() {

program
.option('-z, --zenstack <path>', 'Path to ZenStack generated folder')
.option('-p, --port <number>', 'Port number for the server', '8008')
.option('-p, --port <number>', 'Port number for the server', '2311')
.option('-s, --schema <path>', 'Path to ZModel schema file', 'schema.zmodel')
.option('-d, --datasource-url <url>', 'Datasource URL (overrides schema configuration)')
.option('-l, --log <level...>', 'Query log levels (e.g., query, info, warn, error)')
Expand Down
37 changes: 28 additions & 9 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import cors from 'cors'
import { ZenStackMiddleware } from '@zenstackhq/server/express'
import { ZModelConfig } from './zmodel-parser'
import { getNodeModulesFolder, getPrismaVersion, getZenStackVersion } from './utils/version-utils'
import { blue, grey } from 'colors'
import { blue, grey, red } from 'colors'
import semver from 'semver'
import { CliError } from './cli-error'

Expand Down Expand Up @@ -168,23 +168,29 @@ async function loadZenStackModules(
enums = prismaModule.$Enums || {}
}

let zenstackAbsPath = zenstackPath
const zenstackAbsPath = zenstackPath
? path.isAbsolute(zenstackPath)
? zenstackPath
: path.join(process.cwd(), zenstackPath)
: undefined

if (zenstackAbsPath) {
const modelMetaPath = path.join(zenstackAbsPath, 'model-meta')
let modelMetaPath: string | undefined
try {
if (zenstackAbsPath) {
modelMetaPath = path.join(zenstackAbsPath, 'model-meta')
} else {
modelMetaPath = '@zenstackhq/runtime/model-meta'
}
modelMeta = require(modelMetaPath).default
} else {
modelMeta = require('@zenstackhq/runtime/model-meta').default
} catch {
throw new CliError(
`Failed to load ZenStack generated model meta from: ${modelMetaPath}\n` +
`Please run \`zenstack generate\` first or specify the correct output directory of ZenStack generated modules using the \`-z\` option.`
)
}

if (!modelMeta.models) {
throw new CliError(
'ZenStack generated modules not found. Please make sure `zenstack generate` has been run or specify the correct path using the `-z` option.'
)
throw new CliError(`Generated model meta not found. Please run \`zenstack generate\` first.`)
}

const zenstackVersion = getZenStackVersion()
Expand Down Expand Up @@ -247,6 +253,19 @@ export async function startServer(options: ServerOptions) {
console.log(`ZenStack Studio is running at: ${blue('https://studio.zenstack.dev')}`)
})

server.on('error', (err: NodeJS.ErrnoException) => {
if (err.code === 'EADDRINUSE') {
console.error(
red(
`Port ${options.port} is already in use. Please choose a different port using -p option.`
)
)
} else {
throw new CliError(`Failed to start the server: ${err.message}`)
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throwing a CliError inside the server.on('error', ...) handler means the subsequent process.exit(1) is unreachable for the non-EADDRINUSE branch, and the error will surface as an unhandled exception outside the CLI's try/catch around program.parseAsync(). To keep error handling consistent and avoid unhandled exceptions, this handler should either log the error and call process.exit(1) without throwing, or propagate the failure through a rejected promise from startServer so the top-level CLI error handling can catch and report it.

Suggested change
throw new CliError(`Failed to start the server: ${err.message}`)
console.error(red(`Failed to start the server: ${err.message}`))

Copilot uses AI. Check for mistakes.
}
process.exit(1)
})
Comment on lines +256 to +267
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for global exception handlers that might intercept throws from server "error" events
rg -n "uncaughtException|unhandledRejection" -S

Repository: zenstackhq/proxy

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Read the relevant section of src/server.ts to verify the code
if [ -f "src/server.ts" ]; then
  wc -l src/server.ts
  echo "---"
  sed -n '250,275p' src/server.ts
else
  echo "File not found, searching for server files..."
  find . -name "server.ts" -type f | head -10
fi

Repository: zenstackhq/proxy

Length of output: 844


🏁 Script executed:

#!/bin/bash
# Search for CliError definition and usage to understand its purpose
rg -n "class CliError|new CliError" -A 2 -B 2 | head -50

Repository: zenstackhq/proxy

Length of output: 2727


🏁 Script executed:

#!/bin/bash
# Find CliError class definition
rg -n "class CliError|export class CliError" -A 5

Repository: zenstackhq/proxy

Length of output: 117


🏁 Script executed:

#!/bin/bash
# Check if there are other server error handlers and how they handle errors
rg -n "\.on\('error'" -A 10 -B 2

Repository: zenstackhq/proxy

Length of output: 653


Avoid throwing in the server "error" handler; process.exit(1) is unreachable for non-EADDRINUSE errors.

Throwing CliError here bypasses the subsequent process.exit(1) and causes an uncaught exception with a stack trace instead of a clean error message. No global exception handlers exist to catch this, so adopt the same console.error pattern used for the EADDRINUSE case.

🔧 Suggested fix
   server.on('error', (err: NodeJS.ErrnoException) => {
     if (err.code === 'EADDRINUSE') {
       console.error(
         red(
           `Port ${options.port} is already in use. Please choose a different port using -p option.`
         )
       )
     } else {
-      throw new CliError(`Failed to start the server: ${err.message}`)
+      console.error(red(`Failed to start the server: ${err.message}`))
     }
     process.exit(1)
   })
🤖 Prompt for AI Agents
In `@src/server.ts` around lines 256 - 267, The server 'error' handler currently
throws a CliError for non-EADDRINUSE errors which prevents the subsequent
process.exit(1) from running; change the handler in server.on('error', ...) so
it logs the error message the same way as the EADDRINUSE branch (e.g.,
console.error(red(`Failed to start the server: ${err.message}`))) instead of
throwing CliError, then allow the existing process.exit(1) to execute; update
references to CliError usage in this handler accordingly.


// Graceful shutdown
process.on('SIGTERM', async () => {
server.close(() => {
Expand Down
Loading