Skip to content

Remove serverport from project file#1957

Merged
alex-Arc merged 2 commits intomasterfrom
remove-serverport
Mar 2, 2026
Merged

Remove serverport from project file#1957
alex-Arc merged 2 commits intomasterfrom
remove-serverport

Conversation

@alex-Arc
Copy link
Copy Markdown
Collaborator

@alex-Arc alex-Arc commented Jan 27, 2026

  • the serverPort value is dropped from the project file (nothing is moved over to avoid issues where the port moves on the next restart)
  • the env PORT is now available and will overwire the port on start-up

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 27, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR refactors server port handling by decoupling it from the settings model. The port is now managed via AppStateService with dedicated API endpoints, a new v4 migration, and conditional UI components that hide port settings in Docker environments. Environment variable PORT can now initialize the startup port.

Changes

Cohort / File(s) Summary
Client API Layer
apps/client/src/common/api/settings.ts
Added getServerPort() and postServerPort() functions to fetch and update server port via new dedicated endpoints.
Client UI - Forms
apps/client/src/features/app-settings/panel/settings-panel/GeneralSettings.tsx, apps/client/src/features/app-settings/panel/settings-panel/ServerPortSettings.tsx
Removed serverPort field from GeneralSettings form; added new ServerPortSettings component with form validation (1024–65535 range), error handling, and revert functionality.
Client UI - Navigation & Layout
apps/client/src/features/app-settings/panel/settings-panel/SettingsPanel.tsx, apps/client/src/features/app-settings/useAppSettingsMenu.tsx
Integrated ServerPortSettings into settings panel with conditional rendering; added "Server Port" menu option hidden in Docker environments.
Client Models
apps/client/src/common/models/OntimeSettings.ts
Removed serverPort property from placeholder settings object.
Server API Layer
apps/server/src/api-data/settings/settings.router.ts, apps/server/src/api-data/settings/settings.validation.ts
Added GET and POST /serverport endpoints with dedicated validation; removed serverPort docker guard from main settings POST handler.
Server Settings Parser
apps/server/src/api-data/settings/settings.parser.ts, apps/server/src/api-data/settings/__tests__/settings.parser.test.ts
Removed serverPort from parsed settings output; updated test expectations accordingly.
Server Migration Layer
apps/server/src/api-data/db/migration/db.migration.v3.ts, apps/server/src/api-data/db/migration/db.migration.v4.ts, apps/server/src/api-data/db/db.parser.ts, apps/server/src/api-data/db/migration/migration.test.ts
Added v4 migration module with shouldMigrateServerPort() and migrateServerPort() helpers; updated v3 return type to include serverPort; integrated v4 migration into parser flow.
Server App State & Port Management
apps/server/src/services/app-state-service/AppStateService.ts, apps/server/src/app.ts, apps/server/src/setup/environment.ts
Added serverPort property to AppState; introduced getServerPort() and setServerPort() methods; added startUpPort environment variable support; refactored startup port logic to use AppStateService instead of settings.
Server Data Models
apps/server/src/models/dataModel.ts, apps/server/src/models/demoProject.ts, apps/server/test-db/db.json
Removed serverPort from default data model, demo project, and test database.
Server Session & Utilities
apps/server/src/api-data/session/session.service.ts, apps/server/src/classes/data-provider/__tests__/DataProvider.utils.test.ts
Updated getInfo() to fetch serverPort asynchronously via AppStateService; removed serverPort from safeMerge test expectations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • PR #1753: Modifies settings router and validation with serverPort-specific handling changes that align with this PR's endpoint and validation separation strategy.
  • PR #1628: Touches database parser and migration flow; this PR's addition of v4 serverPort migration integrates directly into the parser structure modified by that PR.
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and accurately summarizes the main objective: removing the serverPort property from the project configuration file.
Description check ✅ Passed The description is directly related to the changeset, explaining the removal of serverPort from the project file and the introduction of the PORT environment variable.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch remove-serverport

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@alex-Arc alex-Arc marked this pull request as ready for review January 27, 2026 20:21
@alex-Arc alex-Arc requested a review from cpvalente January 27, 2026 20:22
@alex-Arc alex-Arc force-pushed the remove-serverport branch 2 times, most recently from c0c8e19 to 322c73a Compare January 27, 2026 20:29
Comment thread apps/server/src/services/app-state-service/AppStateService.ts Outdated
Comment thread apps/server/src/setup/environment.ts Outdated
Comment thread apps/server/src/app.ts Outdated
@cpvalente
Copy link
Copy Markdown
Owner

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 30, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/server/src/api-data/settings/settings.router.ts`:
- Around line 69-72: Replace the backtick character in the error message sent
when isDocker is true with a proper apostrophe; update the string passed to
res.status(403).json(...) (the message used in the isDocker branch) from "Can`t
change port when running inside docker" to use "Can't change port when running
inside docker" so the punctuation is correct.
🧹 Nitpick comments (6)
apps/server/src/classes/data-provider/__tests__/DataProvider.utils.test.ts (1)

1-1: Resolve or track the FIXME before merge.

Leaving a “FIXME: AI generated and not reviewed” in tests suggests unresolved work. Please remove it once reviewed or replace with a tracked ticket reference so it’s not forgotten.

apps/server/src/setup/environment.ts (1)

10-14: Consider simplifying the nested ternary for readability.

The logic is correct, but the nested ternary can be harder to follow. A minor refactor could improve clarity:

♻️ Optional: Simplified port parsing
-export const startUpPort = process.env.PORT
-  ? !isNaN(Number(process.env.PORT))
-    ? Number(process.env.PORT)
-    : null
-  : null;
+const portEnv = process.env.PORT;
+export const startUpPort = portEnv && !isNaN(Number(portEnv)) ? Number(portEnv) : null;
apps/server/src/api-data/settings/settings.validation.ts (1)

33-35: Minor validation inconsistency with client-side.

The server uses isPort() which validates ports 1-65535, while the client-side ServerPortSettings.tsx restricts input to 1024-65535 (excluding privileged ports). This means the API would accept ports < 1024, but the UI wouldn't allow submitting them.

This is likely intentional (API is more permissive, UI enforces best practice), but worth confirming. If you want strict alignment, consider adding a custom validator:

♻️ Optional: Align with client-side validation
 export const validateServerPort = [
-  body('serverPort').isPort().withMessage('Invalid value found for server port').toInt(),
+  body('serverPort')
+    .isPort()
+    .withMessage('Invalid value found for server port')
+    .toInt()
+    .custom((value) => {
+      if (value < 1024) {
+        throw new Error('Port must be >= 1024 to avoid privileged ports');
+      }
+      return true;
+    }),
   requestValidationFunction,
 ];
apps/server/src/api-data/db/migration/db.migration.v4.ts (1)

13-26: Consider logging or migrating the removed serverPort value.

The migration extracts serverPort (line 15) but discards it. Per the PR description, this is intentional to avoid port-change-on-restart issues. However, consider whether the value should be logged for debugging purposes or migrated to the app-state file for users upgrading from older versions.

apps/client/src/features/app-settings/panel/settings-panel/ServerPortSettings.tsx (2)

34-38: Redundant validation in onSubmit.

The range validation (lines 35-38) duplicates the min/max rules in register (lines 100-101). Since the submit button is disabled when !isValid (line 76), this check is unreachable during normal form submission. Consider removing the redundant check to reduce code duplication.

♻️ Proposed simplification
 const onSubmit = async (formData: ServerPortForm) => {
-  if (formData.serverPort < 1024 || formData.serverPort > 65535) {
-    setError('serverPort', { message: 'Port must be within range 1024 - 65535' });
-    return;
-  }
   try {
     await postServerPort(formData.serverPort);

93-107: maxLength attribute is ineffective on type="number" inputs.

The maxLength={5} attribute (line 96) has no effect on number-type inputs in most browsers. The max validation rule already constrains the value to 65535, which provides the intended limit.

♻️ Proposed fix
             <Input
               id='serverPort'
               type='number'
-              maxLength={5}
               style={{ width: '75px' }}

Comment thread apps/server/src/api-data/settings/settings.router.ts Outdated
Comment thread apps/server/src/api-data/db/migration/db.migration.v4.ts Outdated
@alex-Arc alex-Arc force-pushed the remove-serverport branch 2 times, most recently from 66bb825 to edb3f2c Compare January 31, 2026 16:33
@alex-Arc alex-Arc requested a review from cpvalente January 31, 2026 16:35
Copy link
Copy Markdown
Owner

@cpvalente cpvalente left a comment

Choose a reason for hiding this comment

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

When I tried to test this feature I was surprised to see that my server started in a random port. At first I thought that I had something on port 4001 but that was not the case.
We need to ensure that the migration is not changing perceived behaviour, ie: the app should prefer port 4001

I also wonder if we should have a small utility resolveServerPort which collects all the data sources and has logic for how to chose one. as of now things come from different places and it can be difficult to trace

Comment thread apps/server/src/setup/environment.ts Outdated
Comment thread apps/server/src/services/app-state-service/AppStateService.ts Outdated
Comment thread apps/server/src/api-data/session/session.service.ts Outdated
Comment thread apps/server/src/classes/data-provider/__tests__/DataProvider.utils.test.ts Outdated
Comment thread apps/server/src/app.ts Outdated
@alex-Arc alex-Arc force-pushed the remove-serverport branch 3 times, most recently from 0068f84 to fea1c18 Compare February 14, 2026 10:11
@alex-Arc alex-Arc requested a review from cpvalente February 14, 2026 10:11
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@apps/server/src/api-data/db/db.parser.ts`:
- Line 47: Update the FIXME comment in db.parser.ts to correct the typo: change
"dent" to "don't" so the comment reads e.g. "// FIXME: we should keep the port
if it is loaded from an initial file so people don't lose connection"; locate
the comment near the parser logic referencing initial file port handling and
update the text only.

In `@apps/server/src/classes/port-manager/PortManager.ts`:
- Around line 116-127: The forceCloudPort(Server) promise currently only
resolves on successful listen and has no error handler, so attach an 'error'
listener to the provided server to reject the promise if bind fails (e.g.,
EADDRINUSE), and ensure you remove the error listener after success to avoid
leaks; mirror the behavior from tryServerPort by adding server.once('error', err
=> { cleanup listeners; reject(err); }) and remove the handler inside the listen
callback before calling resolve(address.port).
- Around line 73-113: Invert the desktop/docker branch in tryServerPort: when
canChangePort() is true (desktop) allow falling back to an ephemeral port on
EADDRINUSE and only reject immediately for non-EADDRINUSE errors; when
canChangePort() is false (cloud/docker) reject any error immediately.
Concretely, negate the existing canChangePort() check and the
isPortInUseError(error) condition so the server.listen(0, ...) ephemeral
fallback runs for desktop when isPortInUseError(error) is true. Also add
symmetric error handling to forceCloudPort: attach server.once('error', ...) to
reject the Promise on errors (and, if you want parity with tryServerPort, treat
EADDRINUSE explicitly), so server.listen(...) cannot hang without resolving or
rejecting. Reference: PortManager.tryServerPort and PortManager.forceCloudPort.
🧹 Nitpick comments (2)
apps/server/src/classes/port-manager/PortManager.ts (1)

10-13: Consider using instance properties instead of static properties.

Using static properties (PortManager.port, PortManager.pendingRestart, PortManager.newPort) in a class that's instantiated as a singleton is unconventional. If the intent is to have a single instance, consider using instance properties with this.port, etc. The current pattern makes the class harder to test (can't reset state between tests) and mixes singleton patterns.

♻️ Alternative using instance properties
 class PortManager {
-  private static port: number;
-  private static pendingRestart = false;
-  private static newPort: MaybeNumber = null;
+  private port: number;
+  private pendingRestart = false;
+  private newPort: MaybeNumber = null;

   public getPort() {
     return {
-      port: PortManager.port,
-      pendingRestart: PortManager.pendingRestart,
-      newPort: PortManager.newPort,
+      port: this.port,
+      pendingRestart: this.pendingRestart,
+      newPort: this.newPort,
     };
   }
   // ... update other methods similarly
 }
apps/server/src/api-data/settings/settings.router.ts (1)

66-82: Consider using "Cannot" for consistency.

The error message "Can not change port" is grammatically correct but "Cannot" is more conventional in error messages.

📝 Proposed fix
 router.post('/serverport', validateServerPort, async (req: Request, res: Response<PortInfo | ErrorResponse>) => {
   if (!canChangePort()) {
-    res.status(403).json({ message: 'Can not change port when running inside docker' });
+    res.status(403).json({ message: 'Cannot change port when running inside Docker' });
     return;
   }

Comment thread apps/server/src/api-data/db/db.parser.ts Outdated
Comment thread apps/server/src/classes/port-manager/PortManager.ts
Comment thread apps/server/src/classes/port-manager/PortManager.ts
@alex-Arc alex-Arc force-pushed the remove-serverport branch 2 times, most recently from a0ceb14 to 7232a40 Compare February 17, 2026 06:28
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
apps/server/src/classes/port-manager/PortManager.ts (1)

54-71: Consider validating env port range and integer-ness.

parsePort currently accepts any numeric value (including decimals or out-of-range ports), which will later fail during listen.

♻️ Suggested refinement
   private parsePort(port: string | undefined) {
     if (typeof port !== 'string') return null;
     if (port === '') return null;
     const maybePort = Number(port);
-    if (isNaN(maybePort)) return null;
-    return maybePort;
+    if (!Number.isInteger(maybePort)) return null;
+    if (maybePort < 1 || maybePort > 65535) return null;
+    return maybePort;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/classes/port-manager/PortManager.ts` around lines 54 - 71,
parsePort currently converts any numeric string to a Number and returns it,
allowing floats and out-of-range values that will break listen; update parsePort
to validate that envPort is an integer (no decimals) and within the valid port
range (0–65535, or 1–65535 if you disallow 0), returning null for invalid values
so attachServer/PortManager.port falls back correctly; keep the function name
parsePort and ensure callers in attachServer/PortManager.port and any uses of
envPort continue to expect null for invalid input.
apps/server/src/classes/port-manager/PortManager.utils.ts (1)

14-15: Strengthen the AddressInfo type guard.

typeof address === 'object' accepts any object; checking for a numeric port reduces false positives.

♻️ Suggested refinement
 export function isAddressInfo(address: string | AddressInfo | null): address is AddressInfo {
-  return typeof address === 'object' && address !== null;
+  return (
+    typeof address === 'object' &&
+    address !== null &&
+    'port' in address &&
+    typeof (address as AddressInfo).port === 'number'
+  );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/classes/port-manager/PortManager.utils.ts` around lines 14 -
15, The isAddressInfo type guard (isAddressInfo) is too broad because typeof
address === 'object' matches any object; tighten it by first confirming address
!== null and then verifying it has a numeric port property (e.g., 'port' in
address && typeof (address as any).port === "number"); update the function to
check the port field (and optionally other AddressInfo keys like family or
address) so the guard reliably narrows to AddressInfo.
apps/client/src/features/app-settings/panel/settings-panel/ServerPortSettings.tsx (2)

32-35: Add reset to useCallback deps to avoid stale closure.

React hooks best practice; reset is captured but not listed.

♻️ Suggested update
-  const setPort = useCallback((info: PortInfo) => {
+  const setPort = useCallback((info: PortInfo) => {
     reset({ serverPort: info.port });
     setPendingRestart(info.pendingRestart);
-  }, []);
+  }, [reset]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/client/src/features/app-settings/panel/settings-panel/ServerPortSettings.tsx`
around lines 32 - 35, The useCallback for setPort captures reset and
setPendingRestart but currently has an empty deps array; update the dependency
array of setPort to include reset (and keep setPendingRestart if not stable) so
the closure doesn't hold a stale reference — locate setPort defined via
useCallback and add reset to its dependency list.

49-50: Avoid the extra GET by using the POST response.

postServerPort already returns PortInfo, so you can update state without a second round-trip (and avoid a failure window after a successful POST).

♻️ Suggested update
-      await postServerPort(formData.serverPort);
-      setPort(await getServerPort());
+      const res = await postServerPort(formData.serverPort);
+      setPort(res.data);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/client/src/features/app-settings/panel/settings-panel/ServerPortSettings.tsx`
around lines 49 - 50, postServerPort already returns the PortInfo, so avoid the
extra getServerPort round-trip: capture the result of
postServerPort(formData.serverPort) and pass that value directly to setPort
instead of calling getServerPort again; update the code around postServerPort /
setPort to use the returned PortInfo (and remove the redundant getServerPort
call) so the UI updates immediately with the POST response.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/client/src/features/app-settings/panel/settings-panel/ServerPortSettings.tsx`:
- Around line 106-114: The register call for serverPort currently validates
range and pattern but does not coerce the input type, so React Hook Form will
keep the value as a string; update the register('serverPort', ...) options to
include valueAsNumber: true so the field is converted to a number before
validation/submission (modify the register invocation where serverPort is
registered to add valueAsNumber: true alongside required, min, max, pattern).

In `@apps/server/src/api-data/db/db.parser.ts`:
- Around line 47-57: The code sets migrated = true before calling
v4.migrateServerPort, so if migrateServerPort throws the file will be
incorrectly marked migrated; move the assignment of migrated (and any logging
that assumes success) to after v4.migrateServerPort returns successfully: call
v4.shouldMigrateServerPort, then try to run const { db, serverPort } =
v4.migrateServerPort(migratedData); on success set migrated = true, update
migratedData = db, and call portManager.migratePortFromProjectFile(serverPort)
when initialLoad; keep the catch to logger.error(LogOrigin.Server, 'Failed to
migrate serverPort') without changing migrated.

In `@apps/server/src/classes/port-manager/PortManager.ts`:
- Around line 73-103: The fallback call to server.listen(0, '0.0.0.0', ...) in
tryServerPort can itself emit an 'error' and currently has no handler; attach a
temporary error listener before calling listen(0) that rejects the Promise with
that error and cleans itself up (and the success listener) if invoked, and
ensure you remove that listener on successful bind (after isAddressInfo check)
to avoid leaking handlers; reference tryServerPort, the existing
server.once('error') flow, the fallback listen(0) block, and the resolve/reject
paths when adding this error handling.

---

Nitpick comments:
In
`@apps/client/src/features/app-settings/panel/settings-panel/ServerPortSettings.tsx`:
- Around line 32-35: The useCallback for setPort captures reset and
setPendingRestart but currently has an empty deps array; update the dependency
array of setPort to include reset (and keep setPendingRestart if not stable) so
the closure doesn't hold a stale reference — locate setPort defined via
useCallback and add reset to its dependency list.
- Around line 49-50: postServerPort already returns the PortInfo, so avoid the
extra getServerPort round-trip: capture the result of
postServerPort(formData.serverPort) and pass that value directly to setPort
instead of calling getServerPort again; update the code around postServerPort /
setPort to use the returned PortInfo (and remove the redundant getServerPort
call) so the UI updates immediately with the POST response.

In `@apps/server/src/classes/port-manager/PortManager.ts`:
- Around line 54-71: parsePort currently converts any numeric string to a Number
and returns it, allowing floats and out-of-range values that will break listen;
update parsePort to validate that envPort is an integer (no decimals) and within
the valid port range (0–65535, or 1–65535 if you disallow 0), returning null for
invalid values so attachServer/PortManager.port falls back correctly; keep the
function name parsePort and ensure callers in attachServer/PortManager.port and
any uses of envPort continue to expect null for invalid input.

In `@apps/server/src/classes/port-manager/PortManager.utils.ts`:
- Around line 14-15: The isAddressInfo type guard (isAddressInfo) is too broad
because typeof address === 'object' matches any object; tighten it by first
confirming address !== null and then verifying it has a numeric port property
(e.g., 'port' in address && typeof (address as any).port === "number"); update
the function to check the port field (and optionally other AddressInfo keys like
family or address) so the guard reliably narrows to AddressInfo.

Comment thread apps/server/src/api-data/db/db.parser.ts
Comment thread apps/server/src/classes/port-manager/PortManager.ts
@alex-Arc alex-Arc force-pushed the remove-serverport branch 2 times, most recently from 01a556b to e514d99 Compare March 2, 2026 09:48
alex-Arc added 2 commits March 2, 2026 11:32
optional startup port from env will always override

function for parsing port from env

populate default port in app state

add test for migration
@alex-Arc alex-Arc force-pushed the remove-serverport branch from e514d99 to 5bea134 Compare March 2, 2026 10:32
@alex-Arc alex-Arc merged commit c1fcdf7 into master Mar 2, 2026
4 checks passed
@alex-Arc alex-Arc deleted the remove-serverport branch March 2, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants