From 58918c775a333ffa4f74825063d65b328146ea62 Mon Sep 17 00:00:00 2001 From: Ali Asghar Date: Sat, 5 Jul 2025 15:31:27 +0500 Subject: [PATCH 01/11] fix: resolved invalid handling for specs provided through url --- src/controllers/portal/quickstart.ts | 8 +++----- src/prompts/portal/quickstart.ts | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/controllers/portal/quickstart.ts b/src/controllers/portal/quickstart.ts index a49538ae..70aeddbe 100644 --- a/src/controllers/portal/quickstart.ts +++ b/src/controllers/portal/quickstart.ts @@ -28,7 +28,7 @@ import { AuthenticationError } from "../../types/utils.js"; export class PortalQuickstartController { private readonly specUrl = - "https://github.com/apimatic/static-portal-workflow/blob/master/spec/Apimatic-Calculator.json"; + "https://raw.githubusercontent.com/apimatic/static-portal-workflow/refs/heads/master/spec/Apimatic-Calculator.json"; async isUserAuthenticated(configDir: string): Promise { const storedAuth = await getAuthInfo(configDir); @@ -43,7 +43,6 @@ export class PortalQuickstartController { } async getSpecFile(spec: string): Promise { - let filePath = ""; const tempSpecDir = await createTempDirectory(); if (spec) { @@ -63,7 +62,7 @@ export class PortalQuickstartController { const specFile = await axios.get(specPath, { responseType: "arraybuffer" }); const fileName = path.basename(specPath); - filePath = path.join(tempSpecDir, fileName); + const filePath = path.join(tempSpecDir, fileName); await fsExtra.writeFile(filePath, specFile.data); } catch (error) { if (axios.isAxiosError(error)) { @@ -115,7 +114,6 @@ export class PortalQuickstartController { } else { specPath = path.normalize(specPath); const fileType = await filetype.fromFile(specPath); - filePath = tempSpecDir; if (fileType?.ext === "zip") { await unzipFile(fs.createReadStream(specPath), tempSpecDir); @@ -126,7 +124,7 @@ export class PortalQuickstartController { } } - return { localPath: filePath, url: this.specUrl }; + return { localPath: tempSpecDir, url: this.specUrl }; } async getSpecValidationSummary( diff --git a/src/prompts/portal/quickstart.ts b/src/prompts/portal/quickstart.ts index 6680688a..2fc8203c 100644 --- a/src/prompts/portal/quickstart.ts +++ b/src/prompts/portal/quickstart.ts @@ -92,7 +92,7 @@ export class PortalQuickstartPrompts { const spec = await text({ message: `Provide a local path or a public URL for your OpenAPI Definition file:`, placeholder: "Press Enter to use a sample OpenAPI file for APIMatic", - defaultValue: "", + defaultValue: "https://raw.githubusercontent.com/apimatic/static-portal-workflow/refs/heads/master/spec/Apimatic-Calculator.json", validate: (input) => { if (!input) return; From 7768495b104c98adbb64660f119c35d80861af34 Mon Sep 17 00:00:00 2001 From: Ali Asghar Date: Sat, 5 Jul 2025 15:32:07 +0500 Subject: [PATCH 02/11] chore: updated cli version in package.json --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 42177d56..1a3c06c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@apimatic/cli", - "version": "1.1.0-alpha.10", + "version": "1.1.0-alpha.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@apimatic/cli", - "version": "1.1.0-alpha.10", + "version": "1.1.0-alpha.13", "license": "MIT", "dependencies": { "@apimatic/sdk": "^0.2.0-alpha.1", diff --git a/package.json b/package.json index ee3f3332..20aa7c2e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@apimatic/cli", "description": "The official CLI for APIMatic.", - "version": "1.1.0-alpha.10", + "version": "1.1.0-alpha.13", "author": "APIMatic", "bin": { "apimatic": "./bin/run.js" From 554eb9e7b52c65242b722cf0d2c79cb2cafbe5e7 Mon Sep 17 00:00:00 2001 From: Ali Asghar <75574550+aliasghar98@users.noreply.github.com> Date: Sun, 6 Jul 2025 00:35:20 +0500 Subject: [PATCH 03/11] chore: updated package.json to add 'which' --- package-lock.json | 76 ++++++++++++++++++++++++++++++++++++++++++----- package.json | 4 ++- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 42177d56..957c4b56 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@apimatic/cli", - "version": "1.1.0-alpha.10", + "version": "1.1.0-alpha.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@apimatic/cli", - "version": "1.1.0-alpha.10", + "version": "1.1.0-alpha.13", "license": "MIT", "dependencies": { "@apimatic/sdk": "^0.2.0-alpha.1", @@ -34,6 +34,7 @@ "treeify": "^1.1.0", "tslib": "^2.8.1", "unzipper": "^0.12.3", + "which": "^5.0.0", "yaml": "^2.8.0" }, "bin": { @@ -58,6 +59,7 @@ "@types/sinon": "^17.0.4", "@types/treeify": "^1.0.3", "@types/unzipper": "^0.10.4", + "@types/which": "^3.0.4", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", "chai": "^4.5.0", @@ -5493,6 +5495,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/which": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/which/-/which-3.0.4.tgz", + "integrity": "sha512-liyfuo/106JdlgSchJzXEQCVArk0CvevqPote8F8HgWgJ3dRCcTHgJIsLDuee0kxk/mhbInzIZk3QWSZJ8R+2w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.5.14", "dev": true, @@ -7227,6 +7236,27 @@ "node": ">= 8" } }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/dargs": { "version": "7.0.0", "dev": true, @@ -9838,8 +9868,13 @@ "license": "MIT" }, "node_modules/isexe": { - "version": "2.0.0", - "license": "ISC" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "license": "ISC", + "engines": { + "node": ">=16" + } }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -12778,6 +12813,29 @@ "node": ">=8.0.0" } }, + "node_modules/spawn-wrap/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/spawn-wrap/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/spdx-correct": { "version": "3.2.0", "dev": true, @@ -13782,16 +13840,18 @@ } }, "node_modules/which": { - "version": "2.0.2", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "license": "ISC", "dependencies": { - "isexe": "^2.0.0" + "isexe": "^3.1.1" }, "bin": { - "node-which": "bin/node-which" + "node-which": "bin/which.js" }, "engines": { - "node": ">= 8" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/which-boxed-primitive": { diff --git a/package.json b/package.json index ee3f3332..9b02e4f4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@apimatic/cli", "description": "The official CLI for APIMatic.", - "version": "1.1.0-alpha.10", + "version": "1.1.0-alpha.13", "author": "APIMatic", "bin": { "apimatic": "./bin/run.js" @@ -69,6 +69,7 @@ "treeify": "^1.1.0", "tslib": "^2.8.1", "unzipper": "^0.12.3", + "which": "^5.0.0", "yaml": "^2.8.0" }, "devDependencies": { @@ -90,6 +91,7 @@ "@types/sinon": "^17.0.4", "@types/treeify": "^1.0.3", "@types/unzipper": "^0.10.4", + "@types/which": "^3.0.4", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", "chai": "^4.5.0", From 0ffc0d2f893f25f041404857c13d592eddc64a7e Mon Sep 17 00:00:00 2001 From: Ali Asghar <75574550+aliasghar98@users.noreply.github.com> Date: Sun, 6 Jul 2025 00:35:49 +0500 Subject: [PATCH 04/11] fix: updated serve watcher to handle only one portal generation at a time --- src/controllers/portal/serve.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/controllers/portal/serve.ts b/src/controllers/portal/serve.ts index 657db34d..abd8de34 100644 --- a/src/controllers/portal/serve.ts +++ b/src/controllers/portal/serve.ts @@ -67,6 +67,7 @@ export const watchAndRegeneratePortal = async ( }); const deletedDirectories = new Set(); + let isHandlingChange = false; watcher .on("all", async (event, path) => { @@ -81,7 +82,18 @@ export const watchAndRegeneratePortal = async ( } } } - await handleFileChange(sourceDir, portalDir, configDir, overrideAuthKey, absoluteIgnoredPaths); + + if (isHandlingChange) { + return; + } + + isHandlingChange = true; + try { + await handleFileChange(sourceDir, portalDir, configDir, overrideAuthKey, absoluteIgnoredPaths); + } + finally { + isHandlingChange = false; + } }) .on("error", (error: Error) => { console.error("Watcher error:", error); From 861e7f23b0be0b2a7d7e71352c433a17b3657966 Mon Sep 17 00:00:00 2001 From: Ali Asghar <75574550+aliasghar98@users.noreply.github.com> Date: Sun, 6 Jul 2025 00:42:25 +0500 Subject: [PATCH 05/11] fix: resolved text editor problem for linux and mac --- src/actions/portal/recipe/new-recipe.ts | 35 +++++++++++++++++-- .../portal/recipe/recipe-generator.ts | 2 +- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/actions/portal/recipe/new-recipe.ts b/src/actions/portal/recipe/new-recipe.ts index 59a7e1cd..82e52b7e 100644 --- a/src/actions/portal/recipe/new-recipe.ts +++ b/src/actions/portal/recipe/new-recipe.ts @@ -1,6 +1,7 @@ import * as path from "path"; import fs from "fs"; import fsExtra from "fs-extra"; +import which from "which"; import { parse } from "yaml"; import { TreeObject } from "treeify"; import { tmpdir } from "os"; @@ -188,10 +189,15 @@ export class PortalRecipeAction { if (!editor) { if (process.platform === "win32") { await execa("cmd", ["/c", "start", "/wait", "notepad", tempFilePath], { stdio: "ignore" }); - } else { - editor = "nano"; + } else if (process.platform === "darwin") { + editor = "open -e"; await execa(editor, [tempFilePath], { stdio: "ignore" }); } + else if (process.platform === "linux") { + const [ editor, ...args ] = await this.findEditorLinux(); + + await execa(editor, [...args, tempFilePath], { stdio: "ignore" }); + } } else { if (editor === "code" || editor.endsWith("code.cmd") || editor.endsWith("code.exe")) { editorArgs.push("--wait"); @@ -213,6 +219,31 @@ export class PortalRecipeAction { } } + private async findEditorLinux() { + const editors = [ + ['gedit'], + ['kate'], + ['pluma'], + ['mousepad'], + ['leafpad'], + ['xed'], + ['code', '--wait'], + ['atom', '--wait'], + ['sublime-text', '--wait'], + ]; + + for (const editor of editors) { + try { + await which(editor[0]); + return editor; + } catch { + // Ignore and move on to the next editor + } + } + + throw new Error('No supported GUI editor found on this Linux system.'); + } + private async promptUserAndAddEndpointStepToRecipe( recipe: PortalRecipe, endpointGroups: Map, diff --git a/src/application/portal/recipe/recipe-generator.ts b/src/application/portal/recipe/recipe-generator.ts index c193fc51..4edf57c5 100644 --- a/src/application/portal/recipe/recipe-generator.ts +++ b/src/application/portal/recipe/recipe-generator.ts @@ -80,7 +80,7 @@ export class PortalRecipeGenerator { recipesConfig.workflows = []; } const existingIndex = recipesConfig.workflows.findIndex( - (workflow: any) => workflow.name === recipeName + (workflow: any) => workflow.permalink === `page:recipes/${recipeFileName}` ); const newWorkflow = { From 4351510ee93283eac0846abd2299f20a8757e770 Mon Sep 17 00:00:00 2001 From: Ali Asghar <75574550+aliasghar98@users.noreply.github.com> Date: Mon, 7 Jul 2025 11:19:29 +0500 Subject: [PATCH 06/11] revert: updated serve watcher to handle only one portal generation at a time --- src/controllers/portal/serve.ts | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/src/controllers/portal/serve.ts b/src/controllers/portal/serve.ts index abd8de34..e4026f27 100644 --- a/src/controllers/portal/serve.ts +++ b/src/controllers/portal/serve.ts @@ -54,7 +54,7 @@ export const watchAndRegeneratePortal = async ( ignoredPaths: string[] = [] ) => { // Convert ignoredPaths to absolute paths for consistent comparison - const generatedFilesPaths = getGeneratedFilesPaths(sourceDir, portalDir); + const generatedFilesPaths = getGeneratedFilesPaths(sourceDir, portalDir); const absoluteIgnoredPaths = [ ...ignoredPaths.filter((ignoredPath) => ignoredPath.trim() !== ""), ...generatedFilesPaths @@ -67,7 +67,6 @@ export const watchAndRegeneratePortal = async ( }); const deletedDirectories = new Set(); - let isHandlingChange = false; watcher .on("all", async (event, path) => { @@ -82,18 +81,7 @@ export const watchAndRegeneratePortal = async ( } } } - - if (isHandlingChange) { - return; - } - - isHandlingChange = true; - try { - await handleFileChange(sourceDir, portalDir, configDir, overrideAuthKey, absoluteIgnoredPaths); - } - finally { - isHandlingChange = false; - } + await handleFileChange(sourceDir, portalDir, configDir, overrideAuthKey, absoluteIgnoredPaths); }) .on("error", (error: Error) => { console.error("Watcher error:", error); @@ -156,7 +144,11 @@ async function handleFileChange( ) ); } else if (error.response.status === 403) { - console.error(getMessageInRedColor(`Access denied. It looks like you don't have access to APIMatic's Docs as Code offering. Check your subscription details and contact our team at support@apimatic.io if you believe this is a mistake.`)); + console.error( + getMessageInRedColor( + `Access denied. It looks like you don't have access to APIMatic's Docs as Code offering. Check your subscription details and contact our team at support@apimatic.io if you believe this is a mistake.` + ) + ); } else if (error.response.status === 422) { console.error( getMessageInRedColor( @@ -165,7 +157,9 @@ async function handleFileChange( ); } else if (error.response.status === 500) { console.error( - getMessageInRedColor(`Failed to regenerate the portal. Please ensure that the provided build directory follows the correct structure and contains valid API definition and build files. If the issue persists, reach out to our team at support@apimatic.io`) + getMessageInRedColor( + `Failed to regenerate the portal. Please ensure that the provided build directory follows the correct structure and contains valid API definition and build files. If the issue persists, reach out to our team at support@apimatic.io` + ) ); } else { console.error( @@ -184,11 +178,7 @@ async function handleFileChange( } else if (error.code === "ENOTFOUND" || error.code === "ERR_NETWORK") { throw new Error(getMessageInRedColor(`Network error. Please check your internet connection and try again.`)); } else { - console.error( - getMessageInRedColor( - `No response received from the server. Please try again later.` - ) - ); + console.error(getMessageInRedColor(`No response received from the server. Please try again later.`)); } } else { console.error(getMessageInRedColor(`Failed to regenerate portal: ${error.message}`)); From 199e652439dabc033afaf2c4687da2c64fcc853d Mon Sep 17 00:00:00 2001 From: Ali Asghar <75574550+aliasghar98@users.noreply.github.com> Date: Tue, 8 Jul 2025 00:17:22 +0500 Subject: [PATCH 07/11] fix: updated mac text editor to use -t flag --- src/actions/portal/recipe/new-recipe.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actions/portal/recipe/new-recipe.ts b/src/actions/portal/recipe/new-recipe.ts index 82e52b7e..9928e9dc 100644 --- a/src/actions/portal/recipe/new-recipe.ts +++ b/src/actions/portal/recipe/new-recipe.ts @@ -190,7 +190,7 @@ export class PortalRecipeAction { if (process.platform === "win32") { await execa("cmd", ["/c", "start", "/wait", "notepad", tempFilePath], { stdio: "ignore" }); } else if (process.platform === "darwin") { - editor = "open -e"; + editor = "open -t"; await execa(editor, [tempFilePath], { stdio: "ignore" }); } else if (process.platform === "linux") { From 97203de8ae38baf4098e72e121420588ce17e262 Mon Sep 17 00:00:00 2001 From: Sohail Date: Tue, 8 Jul 2025 10:25:32 +0500 Subject: [PATCH 08/11] feat(apimatic-cli): add user-agent log method --- package-lock.json | 8 ++++---- package.json | 2 +- src/commands/api/validate.ts | 4 ++-- src/commands/portal/quickstart.ts | 6 +++--- src/commands/sdk/generate.ts | 4 ++-- src/controllers/api/validate.ts | 4 ++-- src/controllers/portal/quickstart.ts | 4 ++-- src/controllers/sdk/generate.ts | 6 +++--- src/infrastructure/services/portal-service.ts | 10 ++++++++++ 9 files changed, 29 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 42177d56..e9ea3d16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.1.0-alpha.10", "license": "MIT", "dependencies": { - "@apimatic/sdk": "^0.2.0-alpha.1", + "@apimatic/sdk": "^0.2.0-alpha.2", "@clack/prompts": "1.0.0-alpha.1", "@oclif/core": "^4.2.8", "@oclif/plugin-autocomplete": "^3.2.24", @@ -250,9 +250,9 @@ } }, "node_modules/@apimatic/sdk": { - "version": "0.2.0-alpha.1", - "resolved": "https://registry.npmjs.org/@apimatic/sdk/-/sdk-0.2.0-alpha.1.tgz", - "integrity": "sha512-PDO6vN+DhiWcoUmDzND8Fwro/CNAg+Jbr2jBqXbGssVDw4P2BikKRqnhZoZPlPNYwEUaUr7/SIQwsP7Qx/1zMQ==", + "version": "0.2.0-alpha.2", + "resolved": "https://registry.npmjs.org/@apimatic/sdk/-/sdk-0.2.0-alpha.2.tgz", + "integrity": "sha512-Ii5UQGMuChXYzqJXKlEujqelJ0JSRVVk68Zgqnt0aZJZRClr0vcg8s9Yb7oeZjvEJNJod1uRqTak73pK3JrJfg==", "license": "MIT", "dependencies": { "@apimatic/authentication-adapters": "^0.5.4", diff --git a/package.json b/package.json index ee3f3332..f9576e87 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "test": "tsx node_modules/mocha/bin/_mocha --forbid-only \"test/**/*.test.ts\" --timeout 99999" }, "dependencies": { - "@apimatic/sdk": "^0.2.0-alpha.1", + "@apimatic/sdk": "^0.2.0-alpha.2", "@clack/prompts": "1.0.0-alpha.1", "@oclif/core": "^4.2.8", "@oclif/plugin-autocomplete": "^3.2.24", diff --git a/src/commands/api/validate.ts b/src/commands/api/validate.ts index 21a820e1..524928b6 100644 --- a/src/commands/api/validate.ts +++ b/src/commands/api/validate.ts @@ -1,7 +1,7 @@ import fsExtra from "fs-extra"; import { ux, Flags, Command } from "@oclif/core"; -import { ApiError, ApiValidationExternalApIsController, ApiValidationSummary, Client } from "@apimatic/sdk"; +import { ApiError, ApiValidationExternalApisController, ApiValidationSummary, Client } from "@apimatic/sdk"; import { AuthenticationError, loggers } from "../../types/utils.js"; import { SDKClient } from "../../client-utils/sdk-client.js"; @@ -42,7 +42,7 @@ Specification file provided is valid const overrideAuthKey = flags["auth-key"] ? flags["auth-key"] : null; const client: Client = await SDKClient.getInstance().getClient(overrideAuthKey, this.config.configDir); - const apiValidationController: ApiValidationExternalApIsController = new ApiValidationExternalApIsController( + const apiValidationController: ApiValidationExternalApisController = new ApiValidationExternalApisController( client ); diff --git a/src/commands/portal/quickstart.ts b/src/commands/portal/quickstart.ts index 7918b2c1..cdbfceed 100644 --- a/src/commands/portal/quickstart.ts +++ b/src/commands/portal/quickstart.ts @@ -1,5 +1,5 @@ import { Command } from "@oclif/core"; -import { ApiValidationExternalApIsController, ApiValidationSummary, Client } from "@apimatic/sdk"; +import { ApiValidationExternalApisController, ApiValidationSummary, Client } from "@apimatic/sdk"; import { SDKClient } from "../../client-utils/sdk-client.js"; import { PortalQuickstartPrompts } from "../../prompts/portal/quickstart.js"; import { PortalQuickstartController } from "../../controllers/portal/quickstart.js"; @@ -28,7 +28,7 @@ export default class PortalQuickstart extends Command { prompts: PortalQuickstartPrompts, controller: PortalQuickstartController, specFile: SpecFile, - apiValidationController: ApiValidationExternalApIsController + apiValidationController: ApiValidationExternalApisController ): Promise { const apiValidationSummary = await controller.getSpecValidationSummary(prompts, specFile, apiValidationController); @@ -99,7 +99,7 @@ export default class PortalQuickstart extends Command { } const client: Client = await SDKClient.getInstance().getClient(null, this.config.configDir); - const apiValidationController: ApiValidationExternalApIsController = new ApiValidationExternalApIsController( + const apiValidationController: ApiValidationExternalApisController = new ApiValidationExternalApisController( client ); diff --git a/src/commands/sdk/generate.ts b/src/commands/sdk/generate.ts index ecee9ebe..1cbde0ab 100644 --- a/src/commands/sdk/generate.ts +++ b/src/commands/sdk/generate.ts @@ -3,7 +3,7 @@ import fsExtra from "fs-extra"; import { Command, Flags } from "@oclif/core"; import { SDKClient } from "../../client-utils/sdk-client.js"; -import { ApiError, Client, CodeGenerationExternalApIsController } from "@apimatic/sdk"; +import { ApiError, Client, CodeGenerationExternalApisController } from "@apimatic/sdk"; import { replaceHTML, isJSONParsable, getFileNameFromPath } from "../../utils/utils.js"; import { getSDKGenerationId, downloadGeneratedSDK } from "../../controllers/sdk/generate.js"; @@ -83,7 +83,7 @@ Success! Your SDK is located at swagger_sdk_csharp const overrideAuthKey = flags["auth-key"] ? flags["auth-key"] : null; const client: Client = await SDKClient.getInstance().getClient(overrideAuthKey, this.config.configDir); - const sdkGenerationController: CodeGenerationExternalApIsController = new CodeGenerationExternalApIsController( + const sdkGenerationController: CodeGenerationExternalApisController = new CodeGenerationExternalApisController( client ); diff --git a/src/controllers/api/validate.ts b/src/controllers/api/validate.ts index 2b8544cf..6351ed06 100644 --- a/src/controllers/api/validate.ts +++ b/src/controllers/api/validate.ts @@ -1,11 +1,11 @@ import fsExtra from "fs-extra"; -import { ApiResponse, ApiValidationExternalApIsController, ApiValidationSummary, ContentType, FileWrapper } from "@apimatic/sdk"; +import { ApiResponse, ApiValidationExternalApisController, ApiValidationSummary, ContentType, FileWrapper } from "@apimatic/sdk"; import { GetValidationParams } from "../../types/api/validate.js"; import { createTempDirectory, deleteFile, zipDirectory } from "../../utils/utils.js"; export const getValidationSummary = async ( { file, url }: GetValidationParams, - apiValidationController: ApiValidationExternalApIsController + apiValidationController: ApiValidationExternalApisController ): Promise => { let validation: ApiResponse; diff --git a/src/controllers/portal/quickstart.ts b/src/controllers/portal/quickstart.ts index a49538ae..a826d087 100644 --- a/src/controllers/portal/quickstart.ts +++ b/src/controllers/portal/quickstart.ts @@ -6,7 +6,7 @@ import fs from "fs"; import fsExtra from "fs-extra"; import { readdir } from "fs/promises"; import { getAuthInfo } from "../../client-utils/auth-manager.js"; -import { ApiError, ApiValidationExternalApIsController, ApiValidationSummary } from "@apimatic/sdk"; +import { ApiError, ApiValidationExternalApisController, ApiValidationSummary } from "@apimatic/sdk"; import { LoginCredentials, SpecFile } from "../../types/portal/quickstart.js"; import { SDKClient } from "../../client-utils/sdk-client.js"; import { @@ -132,7 +132,7 @@ export class PortalQuickstartController { async getSpecValidationSummary( prompts: PortalQuickstartPrompts, specFile: SpecFile, - apiValidationController: ApiValidationExternalApIsController + apiValidationController: ApiValidationExternalApisController ): Promise { const validationFlags: GetValidationParams = { file: specFile.localPath, diff --git a/src/controllers/sdk/generate.ts b/src/controllers/sdk/generate.ts index a1506de7..43067f7d 100644 --- a/src/controllers/sdk/generate.ts +++ b/src/controllers/sdk/generate.ts @@ -2,7 +2,7 @@ import fsExtra from "fs-extra"; import { ux } from "@oclif/core"; import { ApiResponse, - CodeGenerationExternalApIsController, + CodeGenerationExternalApisController, UserCodeGeneration, Platforms, GenerateSdkViaUrlRequest, @@ -13,7 +13,7 @@ import { unzipFile, writeFileUsingReadableStream } from "../../utils/utils.js"; export const getSDKGenerationId = async ( { file, url, platform }: GenerationIdParams, - sdkGenerationController: CodeGenerationExternalApIsController + sdkGenerationController: CodeGenerationExternalApisController ): Promise => { ux.action.start("Generating SDK"); @@ -51,7 +51,7 @@ const getSDKPlatform = (platform: string): Platforms | SimplePlatforms => { // Download Platform export const downloadGeneratedSDK = async ( { codeGenId, zippedSDKPath, sdkFolderPath, zip }: DownloadSDKParams, - sdkGenerationController: CodeGenerationExternalApIsController + sdkGenerationController: CodeGenerationExternalApisController ): Promise => { ux.action.start("Downloading SDK"); const { result }: ApiResponse = await sdkGenerationController.downloadSdk(codeGenId); diff --git a/src/infrastructure/services/portal-service.ts b/src/infrastructure/services/portal-service.ts index e7e554f4..7419b953 100644 --- a/src/infrastructure/services/portal-service.ts +++ b/src/infrastructure/services/portal-service.ts @@ -21,6 +21,7 @@ import { Result } from "../../types/common/result.js"; import { getMessageInRedColor, parseStreamBodyToJson, extractZipFile, deleteFile } from "../../utils/utils.js"; import { TransformationData } from "../../types/api/transform.js"; import { Sdl } from "../../types/sdl/sdl.js"; +import * as os from "os"; export class PortalService { private readonly CONTENT_TYPE = ContentType.EnumMultipartformdata; @@ -93,11 +94,20 @@ export class PortalService { return `X-Auth-Key ${key ?? ""}`; }; + private getUserAgent(): string { + const osInfo = `${os.platform()} ${os.release()}`; + const engine = "Node.js"; + const engineVersion = process.version; + + return `APIMATIC CLI - [OS: ${osInfo}, Engine: ${engine}/${engineVersion}]`; + } + private createApiClient = (authorizationHeader: string): Client => { return new Client({ customHeaderAuthenticationCredentials: { Authorization: authorizationHeader }, + userAgent: this.getUserAgent(), timeout: this.TIMEOUT }); }; From 8a19299ebaf248927651c4e2e0690b67b66575ff Mon Sep 17 00:00:00 2001 From: Ali Asghar <75574550+aliasghar98@users.noreply.github.com> Date: Tue, 8 Jul 2025 11:14:44 +0500 Subject: [PATCH 09/11] fix: remove contents of build directory folder before setting it up --- src/controllers/portal/quickstart.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/portal/quickstart.ts b/src/controllers/portal/quickstart.ts index 37411d7a..26926da6 100644 --- a/src/controllers/portal/quickstart.ts +++ b/src/controllers/portal/quickstart.ts @@ -220,6 +220,8 @@ export class PortalQuickstartController { } }); + fsExtra.emptyDirSync(targetFolder); + try { await git.clone(staticPortalRepoUrl, targetFolder); } catch (error) { From 40d16436fc23923c4c26eed0b2864c32fbc2ea51 Mon Sep 17 00:00:00 2001 From: Ali Asghar <75574550+aliasghar98@users.noreply.github.com> Date: Tue, 8 Jul 2025 11:26:42 +0500 Subject: [PATCH 10/11] fix: passed argument for mac editor correctly --- src/actions/portal/recipe/new-recipe.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/actions/portal/recipe/new-recipe.ts b/src/actions/portal/recipe/new-recipe.ts index 9928e9dc..c916aa05 100644 --- a/src/actions/portal/recipe/new-recipe.ts +++ b/src/actions/portal/recipe/new-recipe.ts @@ -190,8 +190,8 @@ export class PortalRecipeAction { if (process.platform === "win32") { await execa("cmd", ["/c", "start", "/wait", "notepad", tempFilePath], { stdio: "ignore" }); } else if (process.platform === "darwin") { - editor = "open -t"; - await execa(editor, [tempFilePath], { stdio: "ignore" }); + editor = "open"; + await execa(editor, ["-t", tempFilePath], { stdio: "ignore" }); } else if (process.platform === "linux") { const [ editor, ...args ] = await this.findEditorLinux(); From add56c33e3eaa18e2b4b206a5eb41b73c142b908 Mon Sep 17 00:00:00 2001 From: Ali Asghar <75574550+aliasghar98@users.noreply.github.com> Date: Tue, 8 Jul 2025 13:23:42 +0500 Subject: [PATCH 11/11] fix: added vim as default editor for both linux and mac --- src/actions/portal/recipe/new-recipe.ts | 56 +++++++------------------ 1 file changed, 14 insertions(+), 42 deletions(-) diff --git a/src/actions/portal/recipe/new-recipe.ts b/src/actions/portal/recipe/new-recipe.ts index c916aa05..94287fe7 100644 --- a/src/actions/portal/recipe/new-recipe.ts +++ b/src/actions/portal/recipe/new-recipe.ts @@ -176,27 +176,24 @@ export class PortalRecipeAction { stepName: string ): Promise> { this.prompts.displayContentStepInfo(); - this.prompts.startProgressIndicatorWithMessage("Waiting for you to close the text editor"); let editor = process.env.EDITOR; let editorArgs: string[] = []; + const tempFilePath = path.join(tmpdir(), `recipe-markdown-content-${Date.now()}.md`); + const template = `# The Heading Goes Here\n\nThis is placeholder text for your API Recipe content step. Feel free to edit this. Save your changes and then close the file once you're done.`; + await fsExtra.writeFile(tempFilePath, template); try { - const tempFilePath = path.join(tmpdir(), `recipe-markdown-content-${Date.now()}.md`); - const template = `# The Heading Goes Here\n\nThis is placeholder text for your API Recipe content step. Feel free to edit this. Save your changes and then close the file once you're done.`; - - await fsExtra.writeFile(tempFilePath, template); - if (!editor) { if (process.platform === "win32") { await execa("cmd", ["/c", "start", "/wait", "notepad", tempFilePath], { stdio: "ignore" }); - } else if (process.platform === "darwin") { - editor = "open"; - await execa(editor, ["-t", tempFilePath], { stdio: "ignore" }); - } - else if (process.platform === "linux") { - const [ editor, ...args ] = await this.findEditorLinux(); - - await execa(editor, [...args, tempFilePath], { stdio: "ignore" }); + } else if (process.platform === "darwin" || process.platform === "linux") { + editor = "vim"; + try { + await execa(editor, [tempFilePath], { stdio: "inherit" }); + } + catch (error) { + // User exiting vim can throw a non-zero exit code leading to exception, ignore it. + } } } else { if (editor === "code" || editor.endsWith("code.cmd") || editor.endsWith("code.exe")) { @@ -206,42 +203,17 @@ export class PortalRecipeAction { await execa(editor, editorArgs, { stdio: "ignore" }); } - this.prompts.stopProgressIndicatorWithMessage("✅ Text editor closed."); const fileContent = await fsExtra.readFile(tempFilePath, "utf-8"); - - await fsExtra.unlink(tempFilePath); - recipe.addContentStep(stepName, stepName, fileContent); + this.prompts.displayStepAddedSuccessfullyMessage(); return Result.success("Added content step successfully."); } catch (error) { return Result.failure(`Unable to add content step. Please try again later.`); } - } - - private async findEditorLinux() { - const editors = [ - ['gedit'], - ['kate'], - ['pluma'], - ['mousepad'], - ['leafpad'], - ['xed'], - ['code', '--wait'], - ['atom', '--wait'], - ['sublime-text', '--wait'], - ]; - - for (const editor of editors) { - try { - await which(editor[0]); - return editor; - } catch { - // Ignore and move on to the next editor - } + finally { + await fsExtra.unlink(tempFilePath); } - - throw new Error('No supported GUI editor found on this Linux system.'); } private async promptUserAndAddEndpointStepToRecipe(