Repository: stateful/vscode-runme
Pull request #459 (2023-04-27 16:43:50)

Replace 'port' setting with 'custom address'
Part of #430 


Patch:
diff --git a/package.json b/package.json
index f7428b31..23edee4a 100644
--- a/package.json
+++ b/package.json
@@ -296,11 +296,10 @@
             "default": 60000,
             "markdownDescription": "Timeout until Runme will cancel the clone process."
           },
-          "runme.server.port": {
-            "type": "number",
+          "runme.server.customAddress": {
+            "type": "string",
             "scope": "machine",
-            "default": 7863,
-            "markdownDescription": "Port used by Runme server. (It requires the GRPC interface enabled)"
+            "markdownDescription": "Custom runme server address. This address will be used instead of launching the server at start. Use this only if you know what you are doing!"
           },
           "runme.server.binaryPath": {
             "type": "string",
diff --git a/src/constants.ts b/src/constants.ts
index 3b9e7ed0..f72ef8ae 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -88,3 +88,4 @@ export const LANGUAGES = new Map([
 ].map(([, source, dest]) => [source, dest]))
 
 export const SERVER_ADDRESS = 'localhost'
+export const SERVER_PORT = 7863
diff --git a/src/extension/extension.ts b/src/extension/extension.ts
index 161bb5c1..6fb1b672 100644
--- a/src/extension/extension.ts
+++ b/src/extension/extension.ts
@@ -67,7 +67,10 @@ export class RunmeExtension {
         return window.showErrorMessage(`Failed to start Runme server. Reason: ${e.message}`)
       }
       TelemetryReporter.sendTelemetryErrorEvent('extension.server', { data: (e as Error).message })
-      return window.showErrorMessage('Failed to start Runme server, please try to reload the window')
+      return window.showErrorMessage(
+        'Failed to start Runme server, please try to reload the window. ' +
+        `Reason: ${(e as any).message}`
+      )
     }
 
     const treeViewer = new RunmeLauncherProvider(getDefaultWorkspace())
diff --git a/src/extension/server/runmeServer.ts b/src/extension/server/runmeServer.ts
index b0e1ff1f..13d8f137 100644
--- a/src/extension/server/runmeServer.ts
+++ b/src/extension/server/runmeServer.ts
@@ -11,7 +11,14 @@ import {
   HealthCheckResponse_ServingStatus
 } from '../grpc/healthTypes'
 import { SERVER_ADDRESS } from '../../constants'
-import { enableServerLogs, getBinaryPath, getPortNumber, getTLSDir, getTLSEnabled } from '../../utils/configuration'
+import {
+  enableServerLogs,
+  getBinaryPath,
+  getCustomServerAddress,
+  getPortNumber,
+  getTLSDir,
+  getTLSEnabled
+} from '../../utils/configuration'
 import { isPortAvailable } from '../utils'
 import { HealthClient } from '../grpc/client'
 
@@ -39,6 +46,7 @@ class RunmeServer implements Disposable {
     #disposables: Disposable[] = []
     #transport?: GrpcTransport
     #serverDisposables: Disposable[] = []
+    #forceExternalServer: boolean
 
     readonly #onClose = this.register(new EventEmitter<{ code: number|null }>())
     readonly #onTransportReady = this.register(new EventEmitter<{ transport: GrpcTransport }>())
@@ -49,7 +57,7 @@ class RunmeServer implements Disposable {
     constructor(
       extBasePath: Uri,
       options: IServerConfig,
-      protected readonly externalServer: boolean,
+      externalServer: boolean,
       protected readonly enableRunner = false
     ) {
       this.#port = getPortNumber()
@@ -59,6 +67,7 @@ class RunmeServer implements Disposable {
       this.#maxNumberOfIntents = options.maxNumberOfIntents
       this.#acceptsIntents = options.acceptsConnection?.intents || 50
       this.#acceptsInterval = options.acceptsConnection?.interval || 200
+      this.#forceExternalServer = externalServer
     }
 
     dispose() {
@@ -98,14 +107,22 @@ class RunmeServer implements Disposable {
     }
 
     address() {
-      return `${SERVER_ADDRESS}:${this.#port}`
+      return getCustomServerAddress() || `${SERVER_ADDRESS}:${this.#port}`
+    }
+
+    private get externalServer(): boolean {
+      return !!(getCustomServerAddress() || this.#forceExternalServer)
     }
 
     private static async getTLS(tlsDir: string) {
-      const certPEM = await fs.readFile(path.join(tlsDir, 'cert.pem'))
-      const privKeyPEM = await fs.readFile(path.join(tlsDir, 'key.pem'))
+      try {
+        const certPEM = await fs.readFile(path.join(tlsDir, 'cert.pem'))
+        const privKeyPEM = await fs.readFile(path.join(tlsDir, 'key.pem'))
 
-      return { certPEM, privKeyPEM }
+        return { certPEM, privKeyPEM }
+      } catch(e: any) {
+        throw new RunmeServerError('Unable to read TLS files', e)
+      }
     }
 
     protected async channelCredentials(): Promise<ChannelCredentials> {
diff --git a/src/extension/server/runmeServerError.ts b/src/extension/server/runmeServerError.ts
index c49d9a6f..a5945516 100644
--- a/src/extension/server/runmeServerError.ts
+++ b/src/extension/server/runmeServerError.ts
@@ -1,8 +1,11 @@
 class RunmeServerError extends Error {
-    constructor(message: string) {
-        super(message)
-        this.name = 'RunmeServerError'
+    constructor(message: string, err?: Error) {
+      if (err) {
+        message += `\nCaused by: ${err?.message}`
+      }
+      super(message)
+      this.name = 'RunmeServerError'
     }
 }
 
-export default RunmeServerError
\ No newline at end of file
+export default RunmeServerError
diff --git a/src/utils/configuration.ts b/src/utils/configuration.ts
index 311d2048..55a440fe 100644
--- a/src/utils/configuration.ts
+++ b/src/utils/configuration.ts
@@ -6,6 +6,7 @@ import { z } from 'zod'
 import { v4 as uuidv4 } from 'uuid'
 
 import { getAnnotations, isWindows } from '../extension/utils'
+import { SERVER_PORT } from '../constants'
 
 const SERVER_SECTION_NAME = 'runme.server'
 const TERMINAL_SECTION_NAME= 'runme.terminal'
@@ -16,12 +17,10 @@ type NotebookTerminalValue = keyof typeof configurationSchema.notebookTerminal
 
 const configurationSchema = {
     server: {
-        port: z
-            .number()
-            .positive()
-            .min(7070)
-            .max(9090)
-            .default(7863),
+        customAddress: z
+            .string()
+            .nonempty()
+            .optional(),
         binaryPath: z
             .string()
             .optional(),
@@ -80,7 +79,11 @@ const getCodeLensConfigurationValue = <T>(configName: keyof typeof configuration
 }
 
 const getPortNumber = (): number => {
-    return getServerConfigurationValue<number>('port', 7863)
+  return SERVER_PORT
+}
+
+const getCustomServerAddress = (): string|undefined => {
+  return getServerConfigurationValue<string|undefined>('customAddress', undefined)
 }
 
 const getTLSEnabled = (): boolean => {
@@ -171,4 +174,5 @@ export {
     getNotebookTerminalRows,
     getCodeLensEnabled,
     registerExtensionEnvironmentVariables,
+    getCustomServerAddress,
 }


Test patch:
diff --git a/tests/extension/configuration.test.ts b/tests/extension/configuration.test.ts
index 6e8ec074..9ca32129 100644
--- a/tests/extension/configuration.test.ts
+++ b/tests/extension/configuration.test.ts
@@ -14,6 +14,7 @@ import {
   getNotebookTerminalFontSize,
   getCodeLensEnabled
 } from '../../src/utils/configuration'
+import { SERVER_PORT } from '../../src/constants'
 
 vi.mock('../../src/extension/grpc/client', () => ({}))
 vi.mock('../../src/extension/grpc/runnerTypes', () => ({}))
@@ -100,9 +101,8 @@ suite('Configuration', () => {
     })
 
     test('Should use a valid specified port number', () => {
-        SETTINGS_MOCK.port = 8080
         const portNumber = getPortNumber()
-        expect(portNumber).toStrictEqual(8080)
+        expect(portNumber).toStrictEqual(SERVER_PORT)
     })
 
     test('Should disable server logs with an invalid value', () => {