From 9f9753b9f368fec7f82e924e655c7f5f38ef71c7 Mon Sep 17 00:00:00 2001 From: Daniel Jackins Date: Wed, 8 Oct 2025 15:50:21 -0600 Subject: [PATCH 1/2] update cli PAPI interactions --- README.md | 2 +- Sources/segmentcli/Commands/LivePlugins.swift | 90 ++++--------------- .../segmentcli/PAPI/PAPIEdgeFunctions.swift | 55 ++---------- 3 files changed, 23 insertions(+), 124 deletions(-) diff --git a/README.md b/README.md index 637aafa..85661e5 100755 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ an Auth token in your Segment workspace. Reach out to your Customer Support Engineer (CSE) or Customer Success Manager (CSM) to have them add this feature to your account. Once that is completed, you may continue. -### Uploading Your Analytics Live Plugins to Your Workspace +### Uploading Your Analytics Live Plugins to Your Source In order to upload your Analytics Live Plugins you'll need the following command: diff --git a/Sources/segmentcli/Commands/LivePlugins.swift b/Sources/segmentcli/Commands/LivePlugins.swift index 0e56bcd..c0b7a7e 100755 --- a/Sources/segmentcli/Commands/LivePlugins.swift +++ b/Sources/segmentcli/Commands/LivePlugins.swift @@ -14,23 +14,23 @@ import Segment class EdgeFnGroup: CommandGroup { let name = "liveplugins" let shortDescription = "Work with and develop analytics live plugins" - let children: [Routable] = [EdgeFnLatestCommand(), EdgeFnUpload(), EdgeFnDisable()] + let children: [Routable] = [EdgeFnLatestCommand(), EdgeFnUpload(), EdgeFnDeleteCode()] init() {} } -class EdgeFnDisable: Command { - let name = "disable" - let shortDescription = "Disable Live Plugins for a given source ID" +class EdgeFnDeleteCode: Command { + let name = "delete" + let shortDescription = "Deletes Live Plugin code for a given source ID" @Param var sourceId: String func execute() throws { guard let workspace = currentWorkspace else { exitWithError(code: .commandFailed, message: "No authentication tokens found."); return } executeAndWait { semaphore in - let spinner = Spinner(.dots, "Uploading live plugin ...") + let spinner = Spinner(.dots, "Deleting live plugin code ...") spinner.start() - PAPI.shared.edgeFunctions.disable(token: workspace.token, sourceId: sourceId) { data, response, error in + PAPI.shared.edgeFunctions.deleteCode(token: workspace.token, sourceId: sourceId) { data, response, error in spinner.stop() if let error = error { @@ -42,7 +42,7 @@ class EdgeFnDisable: Command { switch statusCode { case .ok: // success! - print("Live plugins disabled for \(self.sourceId.italic.bold).") + print("Live plugin code deleted for \(self.sourceId.italic.bold).") case .unauthorized: fallthrough @@ -74,80 +74,24 @@ class EdgeFnUpload: Command { let fileURL = URL(fileURLWithPath: filePath.expandingTildeInPath) - // generate upload URL - executeAndWait { semaphore in - let spinner = Spinner(.dots, "Generating upload URL ...") - spinner.start() - - PAPI.shared.edgeFunctions.generateUploadURL(token: workspace.token, sourceId: sourceId) { data, response, error in - spinner.stop() - - if let error = error { - exitWithError(error) - } - - let statusCode = PAPI.shared.statusCode(response: response) - - switch statusCode { - case .ok: - // success! - if let jsonData = data, let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] { - if let uploadString = json[keyPath: "data.uploadURL"] as? String { - uploadURL = URL(string: uploadString) - } - } - - case .unauthorized: - fallthrough - case .unauthorized2: - exitWithError(code: .commandFailed, message: "Supplied token is not authorized.") - case .notFound: - exitWithError(code: .commandFailed, message: "No live plugins were found.") - default: - exitWithError("An unknown error occurred.") - } - semaphore.signal() - } + // Read file contents + guard let fileContents = try? Data(contentsOf: fileURL) else { + exitWithError(code: .commandFailed, message: "Unable to read file contents from \(filePath)") + return } - // upload it to the URL we were given. - executeAndWait { semaphore in - let spinner = Spinner(.dots, "Uploading \(fileURL.lastPathComponent) ...") - spinner.start() - - PAPI.shared.edgeFunctions.uploadToGeneratedURL(token: workspace.token, url: uploadURL, fileURL: fileURL) { data, response, error in - spinner.stop() - - if let error = error { - exitWithError(error) - } - - let statusCode = PAPI.shared.statusCode(response: response) - - switch statusCode { - case .ok: - // success! - break - - case .unauthorized: - fallthrough - case .unauthorized2: - exitWithError(code: .commandFailed, message: "Supplied token is not authorized.") - case .notFound: - exitWithError(code: .commandFailed, message: "No live plugins were found.") - default: - exitWithError("An unknown error occurred.") - } - semaphore.signal() - } + // Convert Data to String + guard let code = String(data: fileContents, encoding: .utf8) else { + exitWithError(code: .commandFailed, message: "Unable to convert file contents to string. File may not be valid UTF-8.") + return } // call create to make a new connection to the version we just posted. executeAndWait { semaphore in let spinner = Spinner(.dots, "Creating new live plugin version ...") spinner.start() - - PAPI.shared.edgeFunctions.createNewVersion(token: workspace.token, sourceId: sourceId, uploadURL: uploadURL) { data, response, error in + + PAPI.shared.edgeFunctions.createNewVersion(token: workspace.token, sourceId: sourceId, code: code) { data, response, error in spinner.stop() if let error = error { diff --git a/Sources/segmentcli/PAPI/PAPIEdgeFunctions.swift b/Sources/segmentcli/PAPI/PAPIEdgeFunctions.swift index 6fe58b2..8d5cef0 100755 --- a/Sources/segmentcli/PAPI/PAPIEdgeFunctions.swift +++ b/Sources/segmentcli/PAPI/PAPIEdgeFunctions.swift @@ -9,7 +9,7 @@ import Foundation extension PAPI { class EdgeFunctions: PAPISection { - static let pathEntry = "edge-functions" + static let pathEntry = "live-plugins" func latest(token: String, sourceId: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { guard var url = URL(string: PAPIEndpoint) else { completion(nil, nil, "Unable to create URL."); return } @@ -27,47 +27,11 @@ extension PAPI { task.resume() } - func disable(token: String, sourceId: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { - guard var url = URL(string: PAPIEndpoint) else { completion(nil, nil, "Unable to create URL."); return } - - url.appendPathComponent(PAPI.Sources.pathEntry) - url.appendPathComponent(sourceId) - url.appendPathComponent(PAPI.EdgeFunctions.pathEntry) - url.appendPathComponent("disable") - - var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30) - request.httpMethod = "PATCH" - request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = "{ \"sourceId\": \"\(sourceId)\" }".data(using: .utf8) - - let task = URLSession.shared.dataTask(with: request, completionHandler: completion) - task.resume() - } - - func generateUploadURL(token: String, sourceId: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { - guard var url = URL(string: PAPIEndpoint) else { completion(nil, nil, "Unable to create URL."); return } - - url.appendPathComponent(PAPI.Sources.pathEntry) - url.appendPathComponent(sourceId) - url.appendPathComponent(PAPI.EdgeFunctions.pathEntry) - url.appendPathComponent("upload-url") - - var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30) - request.httpMethod = "POST" - request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - request.addValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = "{ \"sourceId\": \"\(sourceId)\" }".data(using: .utf8) - - let task = URLSession.shared.dataTask(with: request, completionHandler: completion) - task.resume() - } - // http://blah.com/whatever/create?sourceId=1 - - func createNewVersion(token: String, sourceId: String, uploadURL: URL?, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { + + func createNewVersion(token: String, sourceId: String, code: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { guard var url = URL(string: PAPIEndpoint) else { completion(nil, nil, "Unable to create URL."); return } - guard let uploadURL = uploadURL else { completion(nil, nil, "Upload URL is invalid."); return } + guard !code.isEmpty else { completion(nil, nil, "Code cannot be empty."); return } url.appendPathComponent(PAPI.Sources.pathEntry) url.appendPathComponent(sourceId) @@ -77,19 +41,10 @@ extension PAPI { request.httpMethod = "POST" request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = "{ \"uploadURL\": \"\(uploadURL.absoluteString)\", \"sourceId\": \"\(sourceId)\" }".data(using: .utf8) + request.httpBody = "{ \"code\": \"\(code)\", \"sourceId\": \"\(sourceId)\" }".data(using: .utf8) let task = URLSession.shared.dataTask(with: request, completionHandler: completion) task.resume() } - - func uploadToGeneratedURL(token: String, url: URL?, fileURL: URL?, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { - guard let url = url else { completion(nil, nil, "URL is nil."); return } - guard let fileURL = fileURL else { completion(nil, nil, "File URL is nil."); return } - var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30) - request.httpMethod = "PUT" - let task = URLSession.shared.uploadTask(with: request, fromFile: fileURL, completionHandler: completion) - task.resume() - } } } From c6fa99049999d7aa64eae08a79c432844b7b74e2 Mon Sep 17 00:00:00 2001 From: Daniel Jackins Date: Thu, 6 Nov 2025 11:49:12 -0700 Subject: [PATCH 2/2] fix endpoints --- Sources/segmentcli/Commands/LivePlugins.swift | 8 ++--- .../segmentcli/PAPI/PAPIEdgeFunctions.swift | 35 +++++++++++++++++-- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/Sources/segmentcli/Commands/LivePlugins.swift b/Sources/segmentcli/Commands/LivePlugins.swift index c0b7a7e..e6b8342 100755 --- a/Sources/segmentcli/Commands/LivePlugins.swift +++ b/Sources/segmentcli/Commands/LivePlugins.swift @@ -36,14 +36,14 @@ class EdgeFnDeleteCode: Command { if let error = error { exitWithError(error) } - + let statusCode = PAPI.shared.statusCode(response: response) - + switch statusCode { case .ok: // success! print("Live plugin code deleted for \(self.sourceId.italic.bold).") - + case .unauthorized: fallthrough case .unauthorized2: @@ -70,8 +70,6 @@ class EdgeFnUpload: Command { func execute() throws { guard let workspace = currentWorkspace else { exitWithError(code: .commandFailed, message: "No authentication tokens found."); return } - var uploadURL: URL? = nil - let fileURL = URL(fileURLWithPath: filePath.expandingTildeInPath) // Read file contents diff --git a/Sources/segmentcli/PAPI/PAPIEdgeFunctions.swift b/Sources/segmentcli/PAPI/PAPIEdgeFunctions.swift index 8d5cef0..d833479 100755 --- a/Sources/segmentcli/PAPI/PAPIEdgeFunctions.swift +++ b/Sources/segmentcli/PAPI/PAPIEdgeFunctions.swift @@ -27,6 +27,22 @@ extension PAPI { task.resume() } + func deleteCode(token: String, sourceId: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { + guard var url = URL(string: PAPIEndpoint) else { completion(nil, nil, "Unable to create URL."); return } + + url.appendPathComponent(PAPI.Sources.pathEntry) + url.appendPathComponent(sourceId) + url.appendPathComponent(PAPI.EdgeFunctions.pathEntry) + url.appendPathComponent("delete-code") + + var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30) + request.httpMethod = "DELETE" + request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + + let task = URLSession.shared.dataTask(with: request, completionHandler: completion) + task.resume() + } + // http://blah.com/whatever/create?sourceId=1 func createNewVersion(token: String, sourceId: String, code: String, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { @@ -36,12 +52,27 @@ extension PAPI { url.appendPathComponent(PAPI.Sources.pathEntry) url.appendPathComponent(sourceId) url.appendPathComponent(PAPI.EdgeFunctions.pathEntry) - + url.appendPathComponent("create") + var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 30) request.httpMethod = "POST" request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.addValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = "{ \"code\": \"\(code)\", \"sourceId\": \"\(sourceId)\" }".data(using: .utf8) + + let payload: [String: Any] = [ + "code": code, + "sourceId": sourceId + ] + + do { + request.httpBody = try JSONSerialization.data(withJSONObject: payload, options: []) + if let bodyString = String(data: request.httpBody!, encoding: .utf8) { + print("Request payload: \(bodyString)") + } + } catch { + completion(nil, nil, "Failed to create JSON payload: \(error)") + return + } let task = URLSession.shared.dataTask(with: request, completionHandler: completion) task.resume()