Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3f28e34
update validation endpoint
sohail2721 Oct 6, 2025
dc6a36d
update failed condition
sohail2721 Oct 7, 2025
7190708
decoded json response
sohail2721 Oct 8, 2025
bef092b
make action result class generic
sohail2721 Oct 10, 2025
abe7bbb
call pruning endpoint
sohail2721 Oct 10, 2025
39a56f8
structural changes
sohail2721 Oct 13, 2025
e461a0a
make api call
sohail2721 Oct 14, 2025
7c80e7d
refactor
sohail2721 Oct 14, 2025
e1d673d
sonarqube
sohail2721 Oct 14, 2025
f0d29df
sonarqube(2)
sohail2721 Oct 14, 2025
02f79b3
feedback
sohail2721 Oct 15, 2025
a08440f
test
sohail2721 Oct 15, 2025
e12efc3
use service error instead of strings
sohail2721 Oct 16, 2025
1e2ed46
remove axios service
sohail2721 Oct 16, 2025
4c8008e
remove testing code
sohail2721 Oct 17, 2025
1e280e7
Remove unallowed features(sdk:quickstart)
sohail2721 Nov 3, 2025
1726098
add is split spec logic
sohail2721 Nov 5, 2025
55736a7
fix: compilation errors
aliasghar98 Nov 5, 2025
75ded3a
fix: minor issues
aliasghar98 Nov 6, 2025
c1d3889
fix: incorrect message handling
aliasghar98 Nov 6, 2025
fdb8319
fix: endpoint limit correction
aliasghar98 Nov 6, 2025
3f1908b
fix: pruning messaging handling
aliasghar98 Nov 6, 2025
bd78df6
update messages
sohail2721 Nov 7, 2025
11fabaf
conditions
sohail2721 Nov 7, 2025
38b6329
fix: messaging improvements and missign check for endpoints limit exc…
aliasghar98 Nov 7, 2025
fa4b29d
docs(readme): updated version
aliasghar98 Nov 7, 2025
899abb5
fix: remove local testing variable
aliasghar98 Nov 7, 2025
b4b1460
fix: messaging update for sdk generation failure
aliasghar98 Nov 7, 2025
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ $ npm install -g @apimatic/cli
$ apimatic COMMAND
running command...
$ apimatic (--version)
@apimatic/cli/1.0.0-beta.1 win32-x64 node-v23.4.0
@apimatic/cli/1.1.0-beta.3 win32-x64 node-v23.4.0
$ apimatic --help [COMMAND]
USAGE
$ apimatic COMMAND
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 58 additions & 18 deletions src/actions/action-result.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,94 @@
enum ResultType {
Success= 0,
Success = 0,
Cancel = 130,
Failure= 1
Failure = 1,
}

export class ActionResult {
export class ActionResult<T = void> {
private readonly message: string;
private readonly resultType: ResultType;
private readonly value?: T;

private constructor(resultType: ResultType, message: string) {
private constructor(resultType: ResultType, message: string, value?: T) {
this.resultType = resultType;
this.message = message;
this.value = value;
}

static success() {
return new ActionResult(ResultType.Success, " Succeeded ");
static success<T>(value?: T): ActionResult<T> {
return new ActionResult<T>(ResultType.Success, "Succeeded", value);
}

static failed() {
return new ActionResult(ResultType.Failure, " Failed ");
static failed<T = never>(message = "Failed"): ActionResult<T> {
return new ActionResult(ResultType.Failure, message);
}

static cancelled() {
return new ActionResult(ResultType.Cancel, " Cancelled ");
static cancelled<T = never>(message = "Cancelled"): ActionResult<T> {
return new ActionResult(ResultType.Cancel, message);
}

static stopped() {
return new ActionResult(ResultType.Cancel, " Stopped ");
static stopped<T = never>(message = "Stopped"): ActionResult<T> {
return new ActionResult(ResultType.Cancel, message);
}

public getMessage() {
public getMessage(): string {
return this.message;
}

public getExitCode() {
public getExitCode(): number {
return this.resultType.valueOf();
}

public isFailed() {
public isFailed(): boolean {
return this.resultType === ResultType.Failure;
}

public mapAll<T>(onSuccess: () => T, onFailure: () => T, onCancel: () => T): T {
public isSuccess(): boolean {
return this.resultType === ResultType.Success;
}

public isCancelled(): boolean {
return this.resultType === ResultType.Cancel;
}

public match<R>(
onSuccess: (value: T) => R,
onFailure: (message: string) => R,
onCancel: (message: string) => R
): R {
switch (this.resultType) {
case ResultType.Success:
return onSuccess();
return onSuccess(this.value!);
case ResultType.Failure:
return onFailure(this.message);
case ResultType.Cancel:
return onCancel(this.message);
}
}

public getValue(): T {
if (!this.isSuccess()) {
throw new Error(`Cannot unwrap ${ResultType[this.resultType]} result: ${this.message}`);
}
return this.value!;
}

public getValueOr(defaultValue: T): T {
return this.isSuccess() ? this.value! : defaultValue;
}

public mapAll<R>(
onSuccess: (value?: T) => R,
onFailure: () => R,
onCancel: () => R
): R {
switch (this.resultType) {
case ResultType.Success:
return onSuccess(this.value);
case ResultType.Failure:
return onFailure();
case ResultType.Cancel:
return onCancel();
}
}
}
}
26 changes: 20 additions & 6 deletions src/actions/api/validate.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { DirectoryPath } from "../../types/file/directoryPath.js";
import { ActionResult } from "../action-result.js";
import { ApiValidatePrompts } from "../../prompts/api/validate.js";
import { ValidationService } from "../../infrastructure/services/validation-service.js";
import { UnallowedFeaturesResponse, ValidationService } from "../../infrastructure/services/validation-service.js";
import { CommandMetadata } from "../../types/common/command-metadata.js";
import { ResourceInput } from "../../types/file/resource-input.js";
import { withDirPath } from "../../infrastructure/tmp-extensions.js";
import { ResourceContext } from "../../types/resource-context.js";
import { ValidationSummary } from "@apimatic/sdk";

export class ValidateAction {
private readonly prompts: ApiValidatePrompts = new ApiValidatePrompts();
Expand All @@ -22,7 +23,7 @@ export class ValidateAction {
public readonly execute = async (
resourcePath: ResourceInput,
displayValidationSummary = true
): Promise<ActionResult> => {
): Promise<ActionResult<UnallowedFeaturesResponse | null>> => {
return await withDirPath(async (tempDirectory) => {
const resourceContext = new ResourceContext(tempDirectory);
const specFileDirResult = await resourceContext.resolveTo(resourcePath);
Expand All @@ -44,13 +45,26 @@ export class ValidateAction {
}
const validationSummary = validationSummaryResult.value;
if (displayValidationSummary) {
this.prompts.displayValidationMessages(validationSummary);
if (this.hasValidationIssues(validationSummary.result.validation)) {
this.prompts.displayValidationSummary(validationSummary.result.validation);
}
if (this.hasValidationIssues(validationSummary.result.linting)) {
this.prompts.displayValidationSummary(validationSummary.result.linting);
}
}
if (!validationSummary.success) {
if (!validationSummary.result.validation.isSuccess || !validationSummary.result.linting.isSuccess) {
return ActionResult.failed();
}

return ActionResult.success();
return ActionResult.success(validationSummary.unallowedFeatures);
});
};

private hasValidationIssues(summary: ValidationSummary): boolean {
return (
summary.blocking.length > 0 ||
summary.errors.length > 0 ||
summary.warnings.length > 0 ||
summary.information.length > 0
);
}
}
34 changes: 31 additions & 3 deletions src/actions/portal/quickstart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { FileDownloadService } from "../../infrastructure/services/file-download
import { getLanguagesConfig } from "../../types/build/build.js";
import { FilePath } from "../../types/file/filePath.js";
import { SpecContext } from "../../types/spec-context.js";
import { FeaturesToRemove, ValidationService } from "../../infrastructure/services/validation-service.js";
import { FileName } from "../../types/file/fileName.js";

const defaultPort: number = 3000 as const;

Expand All @@ -26,19 +28,25 @@ export class PortalQuickstartAction {
private readonly configDir: DirectoryPath;
private readonly commandMetadata: CommandMetadata;
private readonly fileDownloadService = new FileDownloadService();
private readonly buildFileUrl = new UrlPath(`https://github.com/apimatic/sample-docs-as-code-portal/archive/refs/heads/master.zip`);
private readonly defaultSpecUrl = new UrlPath(`https://raw.githubusercontent.com/apimatic/sample-docs-as-code-portal/refs/heads/master/src/spec/openapi.json`);
private readonly buildFileUrl = new UrlPath(
`https://github.com/apimatic/sample-docs-as-code-portal/archive/refs/heads/master.zip`
);
private readonly defaultSpecUrl = new UrlPath(
`https://raw.githubusercontent.com/apimatic/sample-docs-as-code-portal/refs/heads/master/src/spec/openapi.json`
);
private readonly repositoryFolderName = "sample-docs-as-code-portal-master/src" as const;
private readonly validationService: ValidationService;

constructor(configDir: DirectoryPath, commandMetadata: CommandMetadata) {
this.configDir = configDir;
this.commandMetadata = commandMetadata;
this.validationService = new ValidationService(this.configDir);
}

public readonly execute = async (): Promise<ActionResult> => {
const storedAuth = await getAuthInfo(this.configDir.toString());
if (!storedAuth?.authKey) {
const loginResult = await new LoginAction(this.configDir, this.commandMetadata).execute();
const loginResult = await new LoginAction(this.configDir, this.commandMetadata).execute();
if (loginResult.isFailed()) {
return ActionResult.failed();
}
Expand Down Expand Up @@ -98,6 +106,24 @@ export class PortalQuickstartAction {
}
}

const unallowed = validationResult.getValue();
if (unallowed && (unallowed.Features?.length > 0 || unallowed.EndpointCount > unallowed.EndpointLimit)) {
const config: FeaturesToRemove = {
features: unallowed.Features.filter((name) => !!name),
endpointsToKeep: unallowed.EndpointLimit
};

const stripUnallowedFeaturesResult = await this.validationService.stripUnallowedFeatures(specPath, config);
if (stripUnallowedFeaturesResult.isErr()) {
this.prompts.splitSpecDetected(unallowed);
return ActionResult.failed();
} else {
this.prompts.stripUnallowedFeaturesStep(unallowed);
const specContext = new SpecContext(tempDirectory);
specPath = await specContext.save(stripUnallowedFeaturesResult.value, new FileName("pruned-spec.zip"));
}
}

// Step 3/4
this.prompts.selectLanguagesStep();
const languages = await this.prompts.selectLanguagesPrompt();
Expand Down Expand Up @@ -163,9 +189,11 @@ export class PortalQuickstartAction {
const result = await portalServeAction.execute(sourceDirectory, portalDirectory, defaultPort, true, false, () => {
this.prompts.nextSteps();
});

if (result.isFailed()) {
return ActionResult.failed();
}

return ActionResult.success();
});
};
Expand Down
20 changes: 20 additions & 0 deletions src/actions/sdk/quickstart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Language } from "../../types/sdk/generate.js";
import { LauncherService } from "../../infrastructure/launcher-service.js";
import { ZipService } from "../../infrastructure/zip-service.js";
import { FileName } from "../../types/file/fileName.js";
import { FeaturesToRemove, ValidationService } from "../../infrastructure/services/validation-service.js";

const defaultSpecUrl = new UrlPath(
`https://raw.githubusercontent.com/apimatic/sample-docs-as-code-portal/refs/heads/master/src/spec/openapi.json`
Expand All @@ -30,6 +31,7 @@ export class SdkQuickstartAction {
private readonly fileService = new FileService();
private readonly launcherService = new LauncherService();
private readonly zipService = new ZipService();
private readonly validationService = new ValidationService(this.configDir);

constructor(private readonly configDir: DirectoryPath, private readonly commandMetadata: CommandMetadata) {}

Expand Down Expand Up @@ -97,6 +99,24 @@ export class SdkQuickstartAction {
}
}

const unallowed = validationResult.getValue();
if (unallowed && (unallowed.Features?.length > 0 || unallowed.EndpointCount > unallowed.EndpointLimit)) {
const config: FeaturesToRemove = {
features: unallowed.Features.filter((name) => !!name),
endpointsToKeep: unallowed.EndpointLimit
};

const stripUnallowedFeaturesResult = await this.validationService.stripUnallowedFeatures(specPath, config);
if (stripUnallowedFeaturesResult.isErr()) {
this.prompts.splitSpecDetected(unallowed);
return ActionResult.failed();
} else {
this.prompts.stripUnallowedFeaturesStep(unallowed);
const specContext = new SpecContext(tempDirectory);
specPath = await specContext.save(stripUnallowedFeaturesResult.value, new FileName("pruned-spec.zip"));
}
}

// Step 3/4
this.prompts.selectLanguageStep();

Expand Down
2 changes: 1 addition & 1 deletion src/infrastructure/services/portal-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export class PortalService {
const message = (error.result!.errors as Record<string, string[]>)?.[""]?.[0];
return error.result!.title + "\n- " + message;
}
return "An unexpected error occurred while generating the portal, please try again later. If the problem persists, please reach out to our team at support@apimatic.io";
return "An unexpected error occurred while generating the SDK, please try again later. If the problem persists, please reach out to our team at support@apimatic.io";
};

private parseBadRequestResponse(errorMessage: string | undefined): string {
Expand Down
Loading