Skip to content
Draft
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
48 changes: 45 additions & 3 deletions Sources/SlayNodeMenuBar/CommandParsing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ enum CommandParser {
continue
}

if let jvmPort = extractJVMPortProperty(from: token) {
collected.insert(jvmPort)
continue
}

if let inlinePort = extractInlinePort(from: token) {
collected.insert(inlinePort)
continue
Expand Down Expand Up @@ -293,6 +298,10 @@ enum CommandParser {
return port
}

if let port = extractURLPort(from: normalizedValue) {
return port
}

if let port = extractShellDefaultPort(from: normalizedValue) {
return port
}
Expand All @@ -311,12 +320,43 @@ enum CommandParser {
return parts.last == "port"
}

private static func extractJVMPortProperty(from token: String) -> Int? {
guard token.hasPrefix("-D"),
let separator = token.firstIndex(of: "="),
separator > token.index(token.startIndex, offsetBy: 2) else {
return nil
}

let keyStart = token.index(token.startIndex, offsetBy: 2)
let key = String(token[keyStart..<separator]).lowercased()
guard isJVMPortPropertyKey(key) else { return nil }

let valueStart = token.index(after: separator)
let rawValue = String(token[valueStart...]).trimmingCharacters(in: .whitespacesAndNewlines)
return extractPortCandidate(from: rawValue)
?? extractURLPort(from: rawValue)
?? extractShellDefaultPort(from: rawValue)
?? parsePortPrefix(rawValue)
}

private static func isJVMPortPropertyKey(_ key: String) -> Bool {
guard !key.isEmpty else { return false }
let separators = CharacterSet(charactersIn: ".-_")
let keyParts = key.components(separatedBy: separators).filter { !$0.isEmpty }
guard let last = keyParts.last else { return false }
return last == "port"
}

private static func extractPortCandidate(from value: String) -> Int? {
if let directPort = Int(value), isValidPort(directPort) {
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
let unwrapped = unwrappedQuotedValue(trimmed)
let sanitized = unwrapped.trimmingCharacters(in: CharacterSet(charactersIn: ",;)"))

if let directPort = Int(sanitized), isValidPort(directPort) {
return directPort
}

return extractTrailingPort(from: value)
return extractTrailingPort(from: sanitized)
}

private static func extractTrailingPort(from value: String) -> Int? {
Expand Down Expand Up @@ -415,7 +455,9 @@ enum CommandParser {
guard let range = expression.range(of: separator) else { continue }
let candidate = String(expression[range.upperBound...]).trimmingCharacters(in: .whitespacesAndNewlines)
let unwrappedCandidate = unwrappedQuotedValue(candidate)
return extractPortCandidate(from: unwrappedCandidate) ?? parsePortPrefix(unwrappedCandidate)
return extractPortCandidate(from: unwrappedCandidate)
?? extractURLPort(from: unwrappedCandidate)
?? parsePortPrefix(unwrappedCandidate)
}

return nil
Expand Down
72 changes: 72 additions & 0 deletions Tests/SlayNodeMenuBarTests/CommandParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ final class CommandParserTests: XCTestCase {
XCTAssertEqual(ports, [3000, 9229])
}

func testInferPortsFromInlineFlagsWithQuotedValues() {
let tokens = ["node", "server.js", "--port=\"3000\"", "--inspect='127.0.0.1:9229'"]
let ports = CommandParser.inferPorts(from: tokens)

XCTAssertEqual(ports, [3000, 9229])
}

func testInferPortsDeduplicatesAndSortsValues() {
let tokens = ["node", "server.js", "--port=3000", "-p", "3000", "--inspect=127.0.0.1:9229"]
let ports = CommandParser.inferPorts(from: tokens)
Expand All @@ -79,6 +86,13 @@ final class CommandParserTests: XCTestCase {
XCTAssertEqual(ports, [4173, 9230])
}

func testInferPortsFromQuotedFlagArguments() {
let tokens = ["vite", "--port", "\"4173\"", "--inspect", "'127.0.0.1:9230'"]
let ports = CommandParser.inferPorts(from: tokens)

XCTAssertEqual(ports, [4173, 9230])
}

func testInferPortsFromDefaultInspectFlags() {
let tokens = ["node", "--inspect", "server.js", "--inspect-brk", "--inspect-wait"]
let ports = CommandParser.inferPorts(from: tokens)
Expand Down Expand Up @@ -148,6 +162,20 @@ final class CommandParserTests: XCTestCase {
XCTAssertEqual(ports, [3000, 4173, 9229])
}

func testInferPortsFromEnvironmentAssignmentsWithURLValues() {
let tokens = [
"PORT=http://localhost:3000",
"WEB_PORT=\"https://127.0.0.1:4173/app\"",
"API_PORT='http://0.0.0.0:8080,'",
"npm",
"run",
"dev"
]
let ports = CommandParser.inferPorts(from: tokens)

XCTAssertEqual(ports, [3000, 4173, 8080])
}

func testInferPortsFromEnvironmentAssignmentsWithShellDefaults() {
let tokens = [
"PORT=${PORT:-3000}",
Expand All @@ -164,6 +192,20 @@ final class CommandParserTests: XCTestCase {
XCTAssertEqual(ports, [3000, 4173, 8080, 9229, 9333])
}

func testInferPortsFromEnvironmentAssignmentsWithURLShellDefaults() {
let tokens = [
"PORT=${PORT:-http://localhost:3000}",
"API_PORT=${API_PORT:=https://127.0.0.1:8443/graphql}",
"WEB_PORT=${WEB_PORT-\"http://0.0.0.0:4173\"}",
"npm",
"run",
"dev"
]
let ports = CommandParser.inferPorts(from: tokens)

XCTAssertEqual(ports, [3000, 4173, 8443])
}

func testInferPortsFromSocketAddressFlags() {
let tokens = [
"deno",
Expand All @@ -179,6 +221,36 @@ final class CommandParserTests: XCTestCase {
XCTAssertEqual(ports, [8000, 9000, 9100, 9200])
}

func testInferPortsFromJVMSystemProperties() {
let tokens = [
"java",
"-Dhttp.port=8080",
"-Djetty.http.port=127.0.0.1:9000",
"-Dmanagement.server.port=9001",
"-Dgrpc.threads=32",
"-jar",
"app.jar"
]
let ports = CommandParser.inferPorts(from: tokens)

XCTAssertEqual(ports, [8080, 9000, 9001])
}

func testInferPortsFromURLJVMSystemProperties() {
let tokens = [
"java",
"-Dserver.port=http://localhost:8080",
"-Dmanagement.server.port=https://127.0.0.1:9001/actuator",
"-Ddebug.port=${DEBUG_PORT:-9229}",
"-Dapi.port=${API_PORT:=http://0.0.0.0:4173}",
"-jar",
"app.jar"
]
let ports = CommandParser.inferPorts(from: tokens)

XCTAssertEqual(ports, [4173, 8080, 9001, 9229])
}

func testInferPortsFromIPv6HostPortToken() {
let tokens = ["node", "server.js", "http://[::1]:5173"]
let ports = CommandParser.inferPorts(from: tokens)
Expand Down
Loading