Skip to content

Refine SwiftUI migration shell#225

Open
techmore wants to merge 22 commits into
mainfrom
codex/swiftui-migration-clean
Open

Refine SwiftUI migration shell#225
techmore wants to merge 22 commits into
mainfrom
codex/swiftui-migration-clean

Conversation

@techmore

@techmore techmore commented Jun 19, 2026

Copy link
Copy Markdown
Owner

This PR is the clean, conflict-free replay of the SwiftUI/native shell migration slice on top of current main.\n\nScope:\n- SwiftUI macOS app shell and menu bar launcher\n- Native Google Drive helper bridge\n- Launcher/startup cleanup for the fixed loopback runtime\n- Nightly eval path updates and smoke probes\n- Packaging sources for the macOS app bundle\n\nValidation:\n- ./packaging/macos/bundle.sh\n- ./scripts/nightly_product_eval.sh --run\n\nBranch: codex/swiftui-migration-clean

@techmore techmore marked this pull request as ready for review June 19, 2026 15:30

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7c6352cdb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

static var readinessURL: URL {
baseURL.appendingPathComponent("api/health/ready")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use a readiness endpoint the server serves

The macOS shell polls /api/health/ready, but the Node runtime in this commit only registers /api/app-identity and there is no api/health/ready route in server.js. When the packaged app launches an otherwise healthy runtime, waitForRuntime will receive 404s for the full timeout, leave the menu in Starting/timeout, and skip the automatic browser open.

Useful? React with 👍 / 👎.

if let persistedWorkDir = environment["NMAPUI_RUNTIME_WORKDIR"], !persistedWorkDir.isEmpty {
runtimeWorkDir = URL(fileURLWithPath: persistedWorkDir)
} else {
runtimeWorkDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Set a valid runtime workdir for bundled launches

When the app is opened from bundle.sh/install.sh via LaunchServices without NMAPUI_RUNTIME_WORKDIR, this falls back to the app process's current directory while the default command is node server.js, and the bundle does not copy server.js into that directory. In the normal installed-app path the runtime can start from / or another LaunchServices cwd and node cannot find server.js, so the menu bar app cannot launch the backend until the user manually configures a workdir.

Useful? React with 👍 / 👎.

Comment thread google_drive_bridge.js
function runGoogleDriveHelper(args, dataDir, input = null) {
return new Promise((resolve) => {
const helperCommand = process.env.NMAPUI_GOOGLE_DRIVE_HELPER
|| path.join(__dirname, 'packaging/macos/.build/out/Products/Debug/GoogleDriveHelper');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Point the bridge at the built helper

build.sh and bundle.sh invoke plain swift build, whose product is under the SwiftPM build directory such as packaging/macos/.build/debug/GoogleDriveHelper, but the default here points at .build/out/Products/Debug and no script creates that path. With the default environment, every Google Drive status/connect/upload call spawns a nonexistent binary, and because there is no child error handler the runtime crashes instead of reporting helper failure.

Useful? React with 👍 / 👎.

exit(0)

case "save-credentials":
let payloadData = credentialsJSON.isEmpty ? Data("{}".utf8) : Data(credentialsJSON.utf8)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Read credentials from stdin on save

save_google_drive_credentials in server.js still passes the uploaded file contents as stdin via runGoogleDriveHelper(['save-credentials'], DATA_DIR, data.credentialsJson || ''), but this branch only reads the --credentials-json argument and substitutes {} when it is absent. Through the Settings UI, importing any credentials file therefore overwrites .google_drive/credentials.json with {}, and the subsequent auth flow always reports missing OAuth credentials.

Useful? React with 👍 / 👎.

Comment on lines +435 to +437
let filePaths = args
.filter { $0.hasPrefix("/") }
.map { URL(fileURLWithPath: $0) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Parse only files passed after --files

The bridge appends --root <DATA_DIR> to every helper invocation, and the server calls upload with --files ...existingFiles; filtering all absolute arguments here also captures the data directory from --root while ignoring the --files boundary. In a real report upload the helper tries Data(contentsOf:) on the data directory after the report files, causing Google Drive sync to fail even when credentials are valid.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a4c709b2b9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

var configured: Bool?
var connected: Bool?
var expiresAt: String?
var authURL: String?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Return the auth URL under the expected key

HelperResponse is encoded with Swift property names, so the successful auth-url response contains authURL, while the existing settings handler checks data.auth_url before opening the browser. With valid credentials, clicking Connect receives success: true but no auth_url, causing the UI to show the authorization error and never open Google OAuth.

Useful? React with 👍 / 👎.

Comment on lines +379 to +381
let credentials = readJSON([String: String].self, from: paths.credentials) ?? [:]
guard let clientId = credentials["client_id"], !clientId.isEmpty,
let _ = credentials["client_secret"], !redirectURI.isEmpty else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Accept Google Cloud's nested credentials JSON

This decodes credentials as a flat [String: String] and then requires top-level client_id/client_secret. The previous helper unwrapped the standard Google Cloud Console shapes under installed or web; after importing that normal credentials file, status/auth-url treat it as missing credentials and the Drive connection flow cannot start.

Useful? React with 👍 / 👎.

@@ -0,0 +1,43 @@
#!/usr/bin/env node
const { io } = require('socket.io-client');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add the Socket.IO client dependency for the smoke test

The new smoke helper requires socket.io-client, but package.json/package-lock.json only install the server package socket.io. In a clean checkout or the GitHub Actions job after npm install, npm run nightly-eval:run reaches this script and Node exits with MODULE_NOT_FOUND, so the nightly evaluation fails after the startup probes instead of recording a pass.

Useful? React with 👍 / 👎.


<key>ProgramArguments</key>
<array>
<string>/Users/seandolbec/Projects/NmapUI/scripts/nightly_product_eval.sh</string>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Generate the LaunchAgent paths from the checkout

nightly-eval:launchd-install copies this plist unchanged, so on any machine where the repo is not at /Users/seandolbec/Projects/NmapUI, launchd tries to execute a nonexistent script and uses nonexistent working/log paths. Installing from the repo's npm script therefore creates a scheduler that never runs for other checkouts unless users manually edit the plist.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

if let accessToken = tokenState.accessToken, !accessToken.isEmpty {
return accessToken

P2 Badge Refresh expired Drive access tokens before reuse

When a connected account has both an accessToken and a refreshToken, this returns the stored access token without checking expiresAt; after the OAuth access token expires, folder lookup and uploads keep using the stale bearer token and Drive sync fails until the user reconnects. The refresh branch below is only reached when accessToken is absent, so it needs to consider token expiry before returning.


printJSON(HelperResponse(success: true, status: "Uploaded \(uploaded.count) file(s) to Google Drive", error: nil, configured: nil, connected: nil, expiresAt: nil, authURL: nil))

P2 Badge Return Drive upload metadata to the runtime

On every successful upload this discards the uploaded results and folderID by encoding only HelperResponse, whose fields do not include uploaded, folder_id, or day_folder_id; uploadReportFilesToDrive/saveDriveMetadata then cannot persist the folder ID or show Drive links even though the files were uploaded. Include the upload payload fields in the JSON response instead of reducing it to just a status string.


shellQuote(xmlPath)

P2 Badge Generate reports from the archived XML copy

After copying the scan XML into the report archive, the XSLT step now reads the reusable work-file path again after asynchronous gowitness capture. If another scan starts while screenshot/report generation is still pending, phase2_results.xml can be overwritten and the generated HTML/PDF can describe the wrong scan while the archived XML and history point to the original one; use the archived copy for the transform once the copy succeeds.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sean Dolbec added 5 commits July 9, 2026 12:27
Keep the SwiftUI app unprivileged and elevate only nmap via a LaunchDaemon helper, host the perfected HTML UI full-bleed, generate HTML/PDF reports after complete scans, and expose helper install/status in Settings for unattended daily/weekly runs.
After complete scans, upload HTML/PDF/XML when Drive sync is enabled and connected, enrich auto-scan config with next-run metadata for the dashboard countdown banner, and fix a few contract tests.
Detect the primary interface more reliably, keep targets sorting stable, and split the phase-two scan, HTML transform, and cleanup steps for clearer failures.
Connect/disconnect now use the native helper with a local OAuth callback server, credentials import is normalized for Google console JSON, settings persist save-to-desktop, and completed reports can copy to Desktop/NmapUI Reports.
@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

relaxOwnership(of: request.workDirectory)

P1 Badge Restrict helper work directories before chmod

When the privileged helper is installed, a run request can pass validation with any existing absolute workDirectory, and this line then recursively chmods that directory as root. Because the daemon also accepts local socket requests, a crafted request using / or a home directory would rewrite permissions far outside NmapUI scan artifacts after nmap exits; constrain the work directory to the app's scan root before relaxing ownership.


chmod(HelperConstants.socketPath, 0o666)

P1 Badge Restrict access to the privileged helper socket

On multi-user Macs, setting the root helper socket to 0666 lets any local account connect and send run or cancel requests; the helper only validates that the executable is named nmap, so other users can trigger root-owned scans through NmapUI's daemon. The socket should be owned/checked for the installing user or authenticate peer credentials instead of being world-writable.


if grep -oE 'http://localhost:[0-9]+' "$(server_log)" | tail -n1 | grep -oE '[0-9]+' >/tmp/nightly-product-eval-port.$$ 2>/dev/null; then

P2 Badge Probe the native app through a real readiness signal

The nightly job now starts packaging/macos/run.sh, which launches the Swift-native app rather than an HTTP server, so nothing in this path logs a http://localhost:<port> URL for this grep to discover; the committed eval log already shows it stops at “Server did not report a listening port.” As a result npm run nightly-eval:run exits before any probes on every scheduled workflow run unless the eval is changed to use a native readiness signal or starts an actual server.


let profile = RuntimeCustomerProfile.current(prefix: "CSP", networkState: networkState)

P2 Badge Use the saved customer prefix for scheduled reports

For unattended auto-scans, report generation always builds the customer profile with the hard-coded CSP prefix, while interactive scans use the profile stored in config.json. Users who configured a customer prefix will have scheduled reports filed under a different folder/name than manual reports for the same customer, so load the saved customer profile prefix before generating the scheduled report.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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.

1 participant