From f338e3c15694081c8afeedb6051fc0b139c53493 Mon Sep 17 00:00:00 2001 From: Ayeshas09 Date: Tue, 10 Mar 2026 14:00:12 +0500 Subject: [PATCH 1/7] feat: add api-version flag in sdk generate --- package-lock.json | 4 +-- src/actions/sdk/generate.ts | 25 ++++++++++---- src/commands/sdk/generate.ts | 7 ++-- src/prompts/sdk/generate.ts | 23 ++++++++++--- src/types/versioned-build-context.ts | 49 +++++++++++++++++++++------- 5 files changed, 81 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index 27d5aea5..8cd43ed6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@apimatic/cli", - "version": "1.1.0-beta.7", + "version": "1.1.0-beta.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@apimatic/cli", - "version": "1.1.0-beta.7", + "version": "1.1.0-beta.8", "license": "MIT", "dependencies": { "@apimatic/sdk": "^0.2.0-alpha.7", diff --git a/src/actions/sdk/generate.ts b/src/actions/sdk/generate.ts index e4db2e0b..084ebd32 100644 --- a/src/actions/sdk/generate.ts +++ b/src/actions/sdk/generate.ts @@ -28,7 +28,8 @@ export class GenerateAction { sdkDirectory: DirectoryPath, language: Language, force: boolean, - zipSdk: boolean + zipSdk: boolean, + apiVersion?: string ): Promise => { if (buildDirectory.isEqual(sdkDirectory)) { this.prompts.sameBuildAndSdkDir(buildDirectory); @@ -36,14 +37,26 @@ export class GenerateAction { } const versionedBuildContext = new VersionedBuildContext(buildDirectory); - if (await versionedBuildContext.exists()) { - const resolvedDirectory = await versionedBuildContext.getResolvedBuildDirectory(); - if (!resolvedDirectory) { - this.prompts.versionedBuildEmpty(); + if (await versionedBuildContext.isVersioned()) { + const versionDirs = await versionedBuildContext.getVersionDirectories(); + if (versionDirs.length === 0) { + this.prompts.versionedBuildEmpty(await versionedBuildContext.getVersionsDirectory()); return ActionResult.failed(); } + + const resolvedDirectory = versionDirs.length === 1 + ? versionDirs[0] + : apiVersion + ? await versionedBuildContext.resolveVersionDirectory(apiVersion) + : await this.prompts.selectVersion(versionDirs); + + if (!resolvedDirectory) { + if (apiVersion) this.prompts.versionNotFound(); + return apiVersion ? ActionResult.failed() : ActionResult.cancelled(); + } + buildDirectory = resolvedDirectory; - this.prompts.versionedBuild(versionedBuildContext.getRelativePath(resolvedDirectory)); + sdkDirectory = sdkDirectory.join(resolvedDirectory.leafName()); } const specDirectory = buildDirectory.join("spec"); diff --git a/src/commands/sdk/generate.ts b/src/commands/sdk/generate.ts index 349d66e4..7f2ff93d 100644 --- a/src/commands/sdk/generate.ts +++ b/src/commands/sdk/generate.ts @@ -26,6 +26,9 @@ Supports multiple programming languages including Java, C#, Python, JavaScript, char: "d", description: "Directory where the SDK will be generated" }), + "api-version": Flags.string({ + description: "Version of the API documentation to use for SDK generation (if multiple versions exist)" + }), ...FlagsProvider.force, zip: Flags.boolean({ default: false, @@ -44,7 +47,7 @@ Supports multiple programming languages including Java, C#, Python, JavaScript, async run() { const { - flags: { language, input, destination, force, zip: zipSdk, "auth-key": authKey } + flags: { language, input, destination, force, zip: zipSdk, "auth-key": authKey, "api-version": apiVersion } } = await this.parse(SdkGenerate); const workingDirectory = DirectoryPath.createInput(input); @@ -58,7 +61,7 @@ Supports multiple programming languages including Java, C#, Python, JavaScript, intro("Generate SDK"); const action = new GenerateAction(this.getConfigDir(), commandMetadata, authKey); - const result = await action.execute(buildDirectory, sdkDirectory, language as Language, force, zipSdk); + const result = await action.execute(buildDirectory, sdkDirectory, language as Language, force, zipSdk, apiVersion); outro(result); } diff --git a/src/prompts/sdk/generate.ts b/src/prompts/sdk/generate.ts index 3b2eb38a..77c64c60 100644 --- a/src/prompts/sdk/generate.ts +++ b/src/prompts/sdk/generate.ts @@ -1,4 +1,4 @@ -import { isCancel, confirm, log } from "@clack/prompts"; +import { isCancel, confirm, log, select } from "@clack/prompts"; import { DirectoryPath } from "../../types/file/directoryPath.js"; import { format as f, } from "../format.js"; import { Result } from "neverthrow"; @@ -47,13 +47,26 @@ export class SdkGeneratePrompts { log.error(serviceError.errorMessage); } - public versionedBuildEmpty() { - const message = `The ${f.var("versioned_docs")} directory is empty or contains no valid versions.`; + public versionedBuildEmpty(directory: DirectoryPath) { + const message = `The ${f.var(directory.leafName())} directory is either empty or invalid: ${f.path(directory)}`; this.logGenerationError(message); } - public versionedBuild(relativePath: string) { - log.warning(`SDK Generation for multi-versioned builds is not supported. Generating SDK for ${f.var(relativePath)} instead.`); + public versionNotFound() { + this.logGenerationError(`The API version is invalid.`); + } + + public async selectVersion(versions: DirectoryPath[]): Promise { + const version = await select({ + message: "Select an API version for SDK generation:", + options: versions.map((v) => ({ label: v.leafName(), value: v })) + }); + + if (isCancel(version)) { + return undefined; + } + + return version; } public sdkGenerated(sdk: DirectoryPath) { diff --git a/src/types/versioned-build-context.ts b/src/types/versioned-build-context.ts index 037d66a1..5f5e093c 100644 --- a/src/types/versioned-build-context.ts +++ b/src/types/versioned-build-context.ts @@ -1,27 +1,52 @@ -import * as path from "path"; import { FileService } from "../infrastructure/file-service.js"; import { DirectoryPath } from "./file/directoryPath.js"; +import { FilePath } from "./file/filePath.js"; +import { FileName } from "./file/fileName.js"; export class VersionedBuildContext { private readonly fileService = new FileService(); - private readonly versionedDocsDir: DirectoryPath; + private readonly buildDirectory: DirectoryPath; + private buildConfig: Record | undefined; constructor(buildDirectory: DirectoryPath) { - this.versionedDocsDir = buildDirectory.join("versioned_docs"); + this.buildDirectory = buildDirectory; } - public async exists(): Promise { - return this.fileService.directoryExists(this.versionedDocsDir); + private async getBuildConfig(): Promise | undefined> { + if (this.buildConfig !== undefined) { + return this.buildConfig; + } + const buildJsonPath = new FilePath(this.buildDirectory, new FileName("APIMATIC-BUILD.json")); + if (!(await this.fileService.fileExists(buildJsonPath))) { + return undefined; + } + const contents = await this.fileService.getContents(buildJsonPath); + this.buildConfig = JSON.parse(contents); + return this.buildConfig; } - public async getResolvedBuildDirectory(): Promise { - const subDirs = await this.fileService.getSubDirectoriesPaths(this.versionedDocsDir); - if (subDirs.length === 0) - return null; - return subDirs[0]; + public async isVersioned(): Promise { + const config = await this.getBuildConfig(); + return config !== undefined && "generateVersionedPortal" in config; } - public getRelativePath(resolvedDirectory: DirectoryPath): string { - return path.join(".", "src", "versioned_docs", resolvedDirectory.leafName()); + public async getVersionsDirectory(): Promise { + const config = await this.getBuildConfig(); + const versionsPath = (config?.versionsPath as string) ?? "versioned_docs"; + return this.buildDirectory.join(versionsPath); + } + + public async getVersionDirectories(): Promise { + const versionsDir = await this.getVersionsDirectory(); + if (!(await this.fileService.directoryExists(versionsDir))) { + return []; + } + const subDirs = await this.fileService.getSubDirectoriesPaths(versionsDir); + return subDirs.filter((d) => d.leafName().startsWith("version-")); + } + + public async resolveVersionDirectory(apiVersion: string): Promise { + const versionDirs = await this.getVersionDirectories(); + return versionDirs.find((dir) => dir.leafName() === apiVersion) ?? null; } } From 7cf42de916447a0d00a57ebf3e07aca4394c473e Mon Sep 17 00:00:00 2001 From: Ayeshas09 Date: Tue, 10 Mar 2026 14:50:54 +0500 Subject: [PATCH 2/7] apply feedback suggestions --- src/commands/sdk/generate.ts | 2 +- src/types/versioned-build-context.ts | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/commands/sdk/generate.ts b/src/commands/sdk/generate.ts index 7f2ff93d..4ec08801 100644 --- a/src/commands/sdk/generate.ts +++ b/src/commands/sdk/generate.ts @@ -27,7 +27,7 @@ Supports multiple programming languages including Java, C#, Python, JavaScript, description: "Directory where the SDK will be generated" }), "api-version": Flags.string({ - description: "Version of the API documentation to use for SDK generation (if multiple versions exist)" + description: "Version of the API to use for SDK generation (if multiple versions exist)" }), ...FlagsProvider.force, zip: Flags.boolean({ diff --git a/src/types/versioned-build-context.ts b/src/types/versioned-build-context.ts index 5f5e093c..91db9d70 100644 --- a/src/types/versioned-build-context.ts +++ b/src/types/versioned-build-context.ts @@ -1,27 +1,27 @@ import { FileService } from "../infrastructure/file-service.js"; import { DirectoryPath } from "./file/directoryPath.js"; -import { FilePath } from "./file/filePath.js"; -import { FileName } from "./file/fileName.js"; +import { BuildContext } from "./build-context.js"; +import { BuildConfig } from "./build/build.js"; export class VersionedBuildContext { private readonly fileService = new FileService(); + private readonly buildContext: BuildContext; private readonly buildDirectory: DirectoryPath; - private buildConfig: Record | undefined; + private buildConfig: BuildConfig | undefined; constructor(buildDirectory: DirectoryPath) { this.buildDirectory = buildDirectory; + this.buildContext = new BuildContext(buildDirectory); } - private async getBuildConfig(): Promise | undefined> { + private async getBuildConfig(): Promise { if (this.buildConfig !== undefined) { return this.buildConfig; } - const buildJsonPath = new FilePath(this.buildDirectory, new FileName("APIMATIC-BUILD.json")); - if (!(await this.fileService.fileExists(buildJsonPath))) { + if (!(await this.buildContext.validate())) { return undefined; } - const contents = await this.fileService.getContents(buildJsonPath); - this.buildConfig = JSON.parse(contents); + this.buildConfig = await this.buildContext.getBuildFileContents(); return this.buildConfig; } @@ -41,8 +41,7 @@ export class VersionedBuildContext { if (!(await this.fileService.directoryExists(versionsDir))) { return []; } - const subDirs = await this.fileService.getSubDirectoriesPaths(versionsDir); - return subDirs.filter((d) => d.leafName().startsWith("version-")); + return await this.fileService.getSubDirectoriesPaths(versionsDir); } public async resolveVersionDirectory(apiVersion: string): Promise { From 178b0550e7b2b2ec6511c5faf5ce00282ac48c4b Mon Sep 17 00:00:00 2001 From: Ayeshas09 Date: Tue, 10 Mar 2026 15:09:39 +0500 Subject: [PATCH 3/7] add a minor todo --- src/types/versioned-build-context.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/versioned-build-context.ts b/src/types/versioned-build-context.ts index 91db9d70..e4eef643 100644 --- a/src/types/versioned-build-context.ts +++ b/src/types/versioned-build-context.ts @@ -3,6 +3,7 @@ import { DirectoryPath } from "./file/directoryPath.js"; import { BuildContext } from "./build-context.js"; import { BuildConfig } from "./build/build.js"; +// TODO: remove BuildContext from this class export class VersionedBuildContext { private readonly fileService = new FileService(); private readonly buildContext: BuildContext; From 3c3f470ef0f29feebd224fc83a83a86e8760b020 Mon Sep 17 00:00:00 2001 From: Ayeshas09 Date: Wed, 11 Mar 2026 10:30:13 +0500 Subject: [PATCH 4/7] apply feedback suggestions --- src/actions/sdk/generate.ts | 30 ++++++++++-------- src/prompts/sdk/generate.ts | 4 +-- src/types/versioned-build-context.ts | 47 ++++++++++------------------ 3 files changed, 36 insertions(+), 45 deletions(-) diff --git a/src/actions/sdk/generate.ts b/src/actions/sdk/generate.ts index 084ebd32..bdc52992 100644 --- a/src/actions/sdk/generate.ts +++ b/src/actions/sdk/generate.ts @@ -37,26 +37,30 @@ export class GenerateAction { } const versionedBuildContext = new VersionedBuildContext(buildDirectory); - if (await versionedBuildContext.isVersioned()) { - const versionDirs = await versionedBuildContext.getVersionDirectories(); - if (versionDirs.length === 0) { - this.prompts.versionedBuildEmpty(await versionedBuildContext.getVersionsDirectory()); + const versionedBuildResult = await versionedBuildContext.validate(); + if (versionedBuildResult.isValid) { + if (versionedBuildResult.versions.length === 0) { + this.prompts.versionedBuildEmpty(versionedBuildResult.versionsDirectory); return ActionResult.failed(); } - const resolvedDirectory = versionDirs.length === 1 - ? versionDirs[0] + if (apiVersion && !versionedBuildResult.versions.includes(apiVersion)) { + this.prompts.versionNotFound(); + return ActionResult.failed(); + } + + const version = versionedBuildResult.versions.length === 1 + ? versionedBuildResult.versions[0] : apiVersion - ? await versionedBuildContext.resolveVersionDirectory(apiVersion) - : await this.prompts.selectVersion(versionDirs); + ? apiVersion + : await this.prompts.selectVersion(versionedBuildResult.versions); - if (!resolvedDirectory) { - if (apiVersion) this.prompts.versionNotFound(); - return apiVersion ? ActionResult.failed() : ActionResult.cancelled(); + if (!version) { + return ActionResult.cancelled(); } - buildDirectory = resolvedDirectory; - sdkDirectory = sdkDirectory.join(resolvedDirectory.leafName()); + buildDirectory = versionedBuildResult.versionsDirectory.join(version); + sdkDirectory = sdkDirectory.join(version); } const specDirectory = buildDirectory.join("spec"); diff --git a/src/prompts/sdk/generate.ts b/src/prompts/sdk/generate.ts index 77c64c60..ba8cec03 100644 --- a/src/prompts/sdk/generate.ts +++ b/src/prompts/sdk/generate.ts @@ -56,10 +56,10 @@ export class SdkGeneratePrompts { this.logGenerationError(`The API version is invalid.`); } - public async selectVersion(versions: DirectoryPath[]): Promise { + public async selectVersion(versions: string[]): Promise { const version = await select({ message: "Select an API version for SDK generation:", - options: versions.map((v) => ({ label: v.leafName(), value: v })) + options: versions.map((v) => ({ label: v, value: v })) }); if (isCancel(version)) { diff --git a/src/types/versioned-build-context.ts b/src/types/versioned-build-context.ts index e4eef643..ea363442 100644 --- a/src/types/versioned-build-context.ts +++ b/src/types/versioned-build-context.ts @@ -1,52 +1,39 @@ import { FileService } from "../infrastructure/file-service.js"; import { DirectoryPath } from "./file/directoryPath.js"; import { BuildContext } from "./build-context.js"; -import { BuildConfig } from "./build/build.js"; + +type VersionedBuildValidateResult = { isValid: false; } | { + isValid: true; + versionsDirectory: DirectoryPath; + versions: string[]; +}; // TODO: remove BuildContext from this class export class VersionedBuildContext { private readonly fileService = new FileService(); private readonly buildContext: BuildContext; private readonly buildDirectory: DirectoryPath; - private buildConfig: BuildConfig | undefined; constructor(buildDirectory: DirectoryPath) { this.buildDirectory = buildDirectory; this.buildContext = new BuildContext(buildDirectory); } - private async getBuildConfig(): Promise { - if (this.buildConfig !== undefined) { - return this.buildConfig; - } + public async validate(): Promise { if (!(await this.buildContext.validate())) { - return undefined; + return { isValid: false }; + } + const config = await this.buildContext.getBuildFileContents(); + if (!config || !("generateVersionedPortal" in config)) { + return { isValid: false }; } - this.buildConfig = await this.buildContext.getBuildFileContents(); - return this.buildConfig; - } - - public async isVersioned(): Promise { - const config = await this.getBuildConfig(); - return config !== undefined && "generateVersionedPortal" in config; - } - - public async getVersionsDirectory(): Promise { - const config = await this.getBuildConfig(); const versionsPath = (config?.versionsPath as string) ?? "versioned_docs"; - return this.buildDirectory.join(versionsPath); - } - - public async getVersionDirectories(): Promise { - const versionsDir = await this.getVersionsDirectory(); + const versionsDir = this.buildDirectory.join(versionsPath); if (!(await this.fileService.directoryExists(versionsDir))) { - return []; + return { isValid: false }; } - return await this.fileService.getSubDirectoriesPaths(versionsDir); - } - - public async resolveVersionDirectory(apiVersion: string): Promise { - const versionDirs = await this.getVersionDirectories(); - return versionDirs.find((dir) => dir.leafName() === apiVersion) ?? null; + const versionsDirs = await this.fileService.getSubDirectoriesPaths(versionsDir); + const versions = versionsDirs.map((dir) => dir.leafName()); + return { isValid: true, versionsDirectory: versionsDir, versions: versions }; } } From 5a240cb4934c93377f8209f1a997d811bdc9f615 Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Wed, 11 Mar 2026 11:25:33 +0500 Subject: [PATCH 5/7] refactor: code refactoring for better readability skipping ternary operation --- src/actions/sdk/generate.ts | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/actions/sdk/generate.ts b/src/actions/sdk/generate.ts index bdc52992..02a64ece 100644 --- a/src/actions/sdk/generate.ts +++ b/src/actions/sdk/generate.ts @@ -44,19 +44,21 @@ export class GenerateAction { return ActionResult.failed(); } - if (apiVersion && !versionedBuildResult.versions.includes(apiVersion)) { - this.prompts.versionNotFound(); + let version: string; + if (apiVersion) { + if (!versionedBuildResult.versions.includes(apiVersion)) { + this.prompts.versionNotFound(); return ActionResult.failed(); } - - const version = versionedBuildResult.versions.length === 1 - ? versionedBuildResult.versions[0] - : apiVersion - ? apiVersion - : await this.prompts.selectVersion(versionedBuildResult.versions); - - if (!version) { - return ActionResult.cancelled(); + version = apiVersion; + } else if (versionedBuildResult.versions.length === 1) { + version = versionedBuildResult.versions[0]; + } else { + const selectedVersion = await this.prompts.selectVersion(versionedBuildResult.versions); + if (!selectedVersion) { + return ActionResult.cancelled(); + } + version = selectedVersion; } buildDirectory = versionedBuildResult.versionsDirectory.join(version); From ceb7a0ddd2c197fa385a438e8c1a19b17b7c5b69 Mon Sep 17 00:00:00 2001 From: Asad Ali <14asadali@gmail.com> Date: Wed, 11 Mar 2026 11:35:19 +0500 Subject: [PATCH 6/7] nits: resolved TODO in versionedBuildContext --- src/types/versioned-build-context.ts | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/types/versioned-build-context.ts b/src/types/versioned-build-context.ts index ea363442..266f3245 100644 --- a/src/types/versioned-build-context.ts +++ b/src/types/versioned-build-context.ts @@ -8,32 +8,30 @@ type VersionedBuildValidateResult = { isValid: false; } | { versions: string[]; }; -// TODO: remove BuildContext from this class export class VersionedBuildContext { private readonly fileService = new FileService(); - private readonly buildContext: BuildContext; private readonly buildDirectory: DirectoryPath; constructor(buildDirectory: DirectoryPath) { this.buildDirectory = buildDirectory; - this.buildContext = new BuildContext(buildDirectory); } public async validate(): Promise { - if (!(await this.buildContext.validate())) { + const buildContext = new BuildContext(this.buildDirectory); + if (!(await buildContext.validate())) { return { isValid: false }; } - const config = await this.buildContext.getBuildFileContents(); - if (!config || !("generateVersionedPortal" in config)) { + const config = await buildContext.getBuildFileContents(); + if (!("generateVersionedPortal" in config)) { return { isValid: false }; } - const versionsPath = (config?.versionsPath as string) ?? "versioned_docs"; - const versionsDir = this.buildDirectory.join(versionsPath); - if (!(await this.fileService.directoryExists(versionsDir))) { + const versionsPath = (config.versionsPath as string) ?? "versioned_docs"; + const versionsDirectory = this.buildDirectory.join(versionsPath); + if (!(await this.fileService.directoryExists(versionsDirectory))) { return { isValid: false }; } - const versionsDirs = await this.fileService.getSubDirectoriesPaths(versionsDir); + const versionsDirs = await this.fileService.getSubDirectoriesPaths(versionsDirectory); const versions = versionsDirs.map((dir) => dir.leafName()); - return { isValid: true, versionsDirectory: versionsDir, versions: versions }; + return { isValid: true, versionsDirectory, versions }; } } From 3c6887781f8a519e9a3c338b603a3254f78d8231 Mon Sep 17 00:00:00 2001 From: Ali Asghar <75574550+aliasghar98@users.noreply.github.com> Date: Wed, 11 Mar 2026 11:45:30 +0500 Subject: [PATCH 7/7] docs(readme): update readme --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 55ae56b2..f4edadaf 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ $ npm install -g @apimatic/cli $ apimatic COMMAND running command... $ apimatic (--version) -@apimatic/cli/1.1.0-beta.7 win32-x64 node-v23.4.0 +@apimatic/cli/1.1.0-beta.8 win32-x64 node-v23.4.0 $ apimatic --help [COMMAND] USAGE $ apimatic COMMAND @@ -411,8 +411,8 @@ Generate an SDK for your API ``` USAGE - $ apimatic sdk generate -l csharp|java|php|python|ruby|typescript|go [-i ] [-d ] [-f] [--zip] [-k - ] + $ apimatic sdk generate -l csharp|java|php|python|ruby|typescript|go [-i ] [-d ] [--api-version + ] [-f] [--zip] [-k ] FLAGS -d, --destination= directory where the SDK will be generated @@ -422,7 +422,8 @@ FLAGS -k, --auth-key= override current authentication state with an authentication key. -l, --language=