Skip to content

Commit 241d4be

Browse files
aliasghar98Ayeshas09asadali214sohail2721
authored
feat: add support for spec pruning and webhooks callbacks flags in toc command (#220)
--------- Co-authored-by: Ayesha <88117894+Ayeshas09@users.noreply.github.com> Co-authored-by: Asad Ali <14asadali@gmail.com> Co-authored-by: Muhammad Sohail <62895181+sohail2721@users.noreply.github.com>
1 parent 0ee9f50 commit 241d4be

20 files changed

Lines changed: 925 additions & 251 deletions

File tree

.prettierrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"parser": "typescript",
33
"printWidth": 120,
4-
"singleQuote": false,
4+
"singleQuote": true,
55
"trailingComma": "none",
66
"tabWidth": 2
77
}

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ $ npm install -g @apimatic/cli
2424
$ apimatic COMMAND
2525
running command...
2626
$ apimatic (--version)
27-
@apimatic/cli/1.0.0-beta.1 win32-x64 node-v23.4.0
27+
@apimatic/cli/1.1.0-beta.3 win32-x64 node-v23.4.0
2828
$ apimatic --help [COMMAND]
2929
USAGE
3030
$ apimatic COMMAND
@@ -349,16 +349,21 @@ Generate a Table of Contents (TOC) file for your API documentation portal
349349
```
350350
USAGE
351351
$ apimatic portal toc new [-d <value>] [-i <value>] [-f] [--expand-endpoints] [--expand-models]
352+
[--expand-webhooks] [--expand-callbacks]
352353
353354
FLAGS
354355
-d, --destination=<value> [default: <input>/src/content] path where the toc.yml will be generated.
355356
-f, --force overwrite changes without asking for user consent.
356357
-i, --input=<value> [default: ./] path to the parent directory containing the 'src' directory, which includes
357358
API specifications and configuration files.
359+
--expand-callbacks include individual entries for each callback in the generated 'toc.yml'. Requires a valid
360+
API specification in the working directory.
358361
--expand-endpoints include individual entries for each endpoint in the generated 'toc.yml'. Requires a valid
359362
API specification in the working directory.
360363
--expand-models include individual entries for each model in the generated 'toc.yml'. Requires a valid API
361364
specification in the working directory.
365+
--expand-webhooks include individual entries for each webhook in the generated 'toc.yml'. Requires a valid
366+
API specification in the working directory.
362367
363368
DESCRIPTION
364369
Generate a Table of Contents (TOC) file for your API documentation portal

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/actions/action-result.ts

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,94 @@
11
enum ResultType {
2-
Success= 0,
2+
Success = 0,
33
Cancel = 130,
4-
Failure= 1
4+
Failure = 1,
55
}
66

7-
export class ActionResult {
7+
export class ActionResult<T = void> {
88
private readonly message: string;
99
private readonly resultType: ResultType;
10+
private readonly value?: T;
1011

11-
private constructor(resultType: ResultType, message: string) {
12+
private constructor(resultType: ResultType, message: string, value?: T) {
1213
this.resultType = resultType;
1314
this.message = message;
15+
this.value = value;
1416
}
1517

16-
static success() {
17-
return new ActionResult(ResultType.Success, " Succeeded ");
18+
static success<T>(value?: T): ActionResult<T> {
19+
return new ActionResult<T>(ResultType.Success, "Succeeded", value);
1820
}
1921

20-
static failed() {
21-
return new ActionResult(ResultType.Failure, " Failed ");
22+
static failed<T = never>(message = "Failed"): ActionResult<T> {
23+
return new ActionResult(ResultType.Failure, message);
2224
}
2325

24-
static cancelled() {
25-
return new ActionResult(ResultType.Cancel, " Cancelled ");
26+
static cancelled<T = never>(message = "Cancelled"): ActionResult<T> {
27+
return new ActionResult(ResultType.Cancel, message);
2628
}
2729

28-
static stopped() {
29-
return new ActionResult(ResultType.Cancel, " Stopped ");
30+
static stopped<T = never>(message = "Stopped"): ActionResult<T> {
31+
return new ActionResult(ResultType.Cancel, message);
3032
}
3133

32-
public getMessage() {
34+
public getMessage(): string {
3335
return this.message;
3436
}
3537

36-
public getExitCode() {
38+
public getExitCode(): number {
3739
return this.resultType.valueOf();
3840
}
3941

40-
public isFailed() {
42+
public isFailed(): boolean {
4143
return this.resultType === ResultType.Failure;
4244
}
4345

44-
public mapAll<T>(onSuccess: () => T, onFailure: () => T, onCancel: () => T): T {
46+
public isSuccess(): boolean {
47+
return this.resultType === ResultType.Success;
48+
}
49+
50+
public isCancelled(): boolean {
51+
return this.resultType === ResultType.Cancel;
52+
}
53+
54+
public match<R>(
55+
onSuccess: (value: T) => R,
56+
onFailure: (message: string) => R,
57+
onCancel: (message: string) => R
58+
): R {
4559
switch (this.resultType) {
4660
case ResultType.Success:
47-
return onSuccess();
61+
return onSuccess(this.value!);
62+
case ResultType.Failure:
63+
return onFailure(this.message);
64+
case ResultType.Cancel:
65+
return onCancel(this.message);
66+
}
67+
}
68+
69+
public getValue(): T {
70+
if (!this.isSuccess()) {
71+
throw new Error(`Cannot unwrap ${ResultType[this.resultType]} result: ${this.message}`);
72+
}
73+
return this.value!;
74+
}
75+
76+
public getValueOr(defaultValue: T): T {
77+
return this.isSuccess() ? this.value! : defaultValue;
78+
}
79+
80+
public mapAll<R>(
81+
onSuccess: (value?: T) => R,
82+
onFailure: () => R,
83+
onCancel: () => R
84+
): R {
85+
switch (this.resultType) {
86+
case ResultType.Success:
87+
return onSuccess(this.value);
4888
case ResultType.Failure:
4989
return onFailure();
5090
case ResultType.Cancel:
5191
return onCancel();
5292
}
5393
}
54-
}
94+
}

src/actions/api/validate.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { DirectoryPath } from "../../types/file/directoryPath.js";
22
import { ActionResult } from "../action-result.js";
33
import { ApiValidatePrompts } from "../../prompts/api/validate.js";
4-
import { ValidationService } from "../../infrastructure/services/validation-service.js";
4+
import { UnallowedFeaturesResponse, ValidationService } from "../../infrastructure/services/validation-service.js";
55
import { CommandMetadata } from "../../types/common/command-metadata.js";
66
import { ResourceInput } from "../../types/file/resource-input.js";
77
import { withDirPath } from "../../infrastructure/tmp-extensions.js";
88
import { ResourceContext } from "../../types/resource-context.js";
9+
import { ValidationSummary } from "@apimatic/sdk";
910

1011
export class ValidateAction {
1112
private readonly prompts: ApiValidatePrompts = new ApiValidatePrompts();
@@ -22,7 +23,7 @@ export class ValidateAction {
2223
public readonly execute = async (
2324
resourcePath: ResourceInput,
2425
displayValidationSummary = true
25-
): Promise<ActionResult> => {
26+
): Promise<ActionResult<UnallowedFeaturesResponse | null>> => {
2627
return await withDirPath(async (tempDirectory) => {
2728
const resourceContext = new ResourceContext(tempDirectory);
2829
const specFileDirResult = await resourceContext.resolveTo(resourcePath);
@@ -44,13 +45,26 @@ export class ValidateAction {
4445
}
4546
const validationSummary = validationSummaryResult.value;
4647
if (displayValidationSummary) {
47-
this.prompts.displayValidationMessages(validationSummary);
48+
if (this.hasValidationIssues(validationSummary.result.validation)) {
49+
this.prompts.displayValidationSummary(validationSummary.result.validation);
50+
}
51+
if (this.hasValidationIssues(validationSummary.result.linting)) {
52+
this.prompts.displayValidationSummary(validationSummary.result.linting);
53+
}
4854
}
49-
if (!validationSummary.success) {
55+
if (!validationSummary.result.validation.isSuccess || !validationSummary.result.linting.isSuccess) {
5056
return ActionResult.failed();
5157
}
52-
53-
return ActionResult.success();
58+
return ActionResult.success(validationSummary.unallowedFeatures);
5459
});
5560
};
61+
62+
private hasValidationIssues(summary: ValidationSummary): boolean {
63+
return (
64+
summary.blocking.length > 0 ||
65+
summary.errors.length > 0 ||
66+
summary.warnings.length > 0 ||
67+
summary.information.length > 0
68+
);
69+
}
5670
}

src/actions/portal/quickstart.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { FileDownloadService } from "../../infrastructure/services/file-download
1616
import { getLanguagesConfig } from "../../types/build/build.js";
1717
import { FilePath } from "../../types/file/filePath.js";
1818
import { SpecContext } from "../../types/spec-context.js";
19+
import { FeaturesToRemove, ValidationService } from "../../infrastructure/services/validation-service.js";
20+
import { FileName } from "../../types/file/fileName.js";
1921

2022
const defaultPort: number = 3000 as const;
2123

@@ -26,19 +28,25 @@ export class PortalQuickstartAction {
2628
private readonly configDir: DirectoryPath;
2729
private readonly commandMetadata: CommandMetadata;
2830
private readonly fileDownloadService = new FileDownloadService();
29-
private readonly buildFileUrl = new UrlPath(`https://github.com/apimatic/sample-docs-as-code-portal/archive/refs/heads/master.zip`);
30-
private readonly defaultSpecUrl = new UrlPath(`https://raw.githubusercontent.com/apimatic/sample-docs-as-code-portal/refs/heads/master/src/spec/openapi.json`);
31+
private readonly buildFileUrl = new UrlPath(
32+
`https://github.com/apimatic/sample-docs-as-code-portal/archive/refs/heads/master.zip`
33+
);
34+
private readonly defaultSpecUrl = new UrlPath(
35+
`https://raw.githubusercontent.com/apimatic/sample-docs-as-code-portal/refs/heads/master/src/spec/openapi.json`
36+
);
3137
private readonly repositoryFolderName = "sample-docs-as-code-portal-master/src" as const;
38+
private readonly validationService: ValidationService;
3239

3340
constructor(configDir: DirectoryPath, commandMetadata: CommandMetadata) {
3441
this.configDir = configDir;
3542
this.commandMetadata = commandMetadata;
43+
this.validationService = new ValidationService(this.configDir);
3644
}
3745

3846
public readonly execute = async (): Promise<ActionResult> => {
3947
const storedAuth = await getAuthInfo(this.configDir.toString());
4048
if (!storedAuth?.authKey) {
41-
const loginResult = await new LoginAction(this.configDir, this.commandMetadata).execute();
49+
const loginResult = await new LoginAction(this.configDir, this.commandMetadata).execute();
4250
if (loginResult.isFailed()) {
4351
return ActionResult.failed();
4452
}
@@ -98,6 +106,24 @@ export class PortalQuickstartAction {
98106
}
99107
}
100108

109+
const unallowed = validationResult.getValue();
110+
if (unallowed && (unallowed.Features?.length > 0 || unallowed.EndpointCount > unallowed.EndpointLimit)) {
111+
const config: FeaturesToRemove = {
112+
features: unallowed.Features.filter((name) => !!name),
113+
endpointsToKeep: unallowed.EndpointLimit
114+
};
115+
116+
const stripUnallowedFeaturesResult = await this.validationService.stripUnallowedFeatures(specPath, config);
117+
if (stripUnallowedFeaturesResult.isErr()) {
118+
this.prompts.splitSpecDetected(unallowed);
119+
return ActionResult.failed();
120+
} else {
121+
this.prompts.stripUnallowedFeaturesStep(unallowed);
122+
const specContext = new SpecContext(tempDirectory);
123+
specPath = await specContext.save(stripUnallowedFeaturesResult.value, new FileName("pruned-spec.zip"));
124+
}
125+
}
126+
101127
// Step 3/4
102128
this.prompts.selectLanguagesStep();
103129
const languages = await this.prompts.selectLanguagesPrompt();
@@ -163,9 +189,11 @@ export class PortalQuickstartAction {
163189
const result = await portalServeAction.execute(sourceDirectory, portalDirectory, defaultPort, true, false, () => {
164190
this.prompts.nextSteps();
165191
});
192+
166193
if (result.isFailed()) {
167194
return ActionResult.failed();
168195
}
196+
169197
return ActionResult.success();
170198
});
171199
};

0 commit comments

Comments
 (0)