Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,5 @@ $RECYCLE.BIN/
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)

lib/
tmp/
tmp/
.vscode/launch.json
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ USAGE
$ apimatic portal:serve [-p <value>] [-d <value>] [-s <value>] [-o] [--no-reload] [-i <value>] [--auth-key <value>]

FLAGS
-d, --destination=<value> [default: ./api-portal] Directory to store and serve the generated portal.
-d, --destination=<value> [default: ./generated_portal] Directory to store and serve the generated portal.
-i, --ignore=<value> Comma-separated list of files/directories to ignore.
-o, --open Open the portal in the default browser.
-p, --port=<value> [default: 3000] Port to serve the portal.
Expand All @@ -285,7 +285,7 @@ DESCRIPTION
Generate and deploy a Docs as Code portal with hot reload.

EXAMPLES
$ apimatic portal:serve --source="./" --destination="./api-portal" --port=3000 --open --no-reload
$ apimatic portal:serve --source="./" --destination="./generated_portal" --port=3000 --open --no-reload
```

_See code: [src/commands/portal/serve.ts](https://github.com/apimatic/apimatic-cli/blob/v1.0.1-alpha.11/src/commands/portal/serve.ts)_
Expand Down
16 changes: 8 additions & 8 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
},
"dependencies": {
"@apimatic/sdk": "^0.1.0-alpha.2",
"@clack/prompts": "^0.10.0",
"@clack/prompts": "^0.11.0",
"@oclif/core": "^4.2.8",
"@oclif/plugin-autocomplete": "^3.2.24",
"@oclif/plugin-help": "^6.2.26",
Expand Down
37 changes: 24 additions & 13 deletions src/commands/portal/generate.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import * as path from "path";
import * as fs from "fs-extra";

import { ux, Command, Flags } from "@oclif/core";
import { Command, Flags } from "@oclif/core";
import { Client, DocsPortalManagementController } from "@apimatic/sdk";

import { AxiosError } from "axios";
import { SDKClient } from "../../client-utils/sdk-client";
import { GeneratePortalParams } from "../../types/portal/generate";
import { downloadDocsPortal } from "../../controllers/portal/generate";
import { zipDirectory, replaceHTML, isJSONParsable } from "../../utils/utils";
import { replaceHTML, isJSONParsable, validateAndZipPortalSource, getGeneratedFilesPaths } from "../../utils/utils";
import { AuthenticationError } from "../../types/utils";
import { PortalGeneratePrompts } from "../../prompts/portal/generate";

export default class PortalGenerate extends Command {
static description =
Expand Down Expand Up @@ -45,26 +46,31 @@ Your portal has been generated at D:/
const zip = flags.zip;
const sourceFolderPath: string = flags.folder;
const portalFolderPath: string = path.join(flags.destination, "generated_portal");
const zippedPortalPath: string = path.join(flags.destination, "generated_portal.zip");

const overrideAuthKey: string | null = flags["auth-key"] ? flags["auth-key"] : null;
const zippedPortalPath: string = path.join(flags.destination, ".generated_portal.zip");
const overrideAuthKey: string | null = flags["auth-key"] ?? null;
const prompts = new PortalGeneratePrompts();

// Check if at destination, portal already exists and throw error if force flag is not set for both zip and extracted
if (fs.existsSync(portalFolderPath) && !flags.force && !zip) {
throw new Error(`Can't download portal to path ${portalFolderPath}, because it already exists`);
await prompts.existingDestinationPortalFolderPrompt();
} else if (fs.existsSync(zippedPortalPath) && !flags.force && zip) {
throw new Error(`Can't download portal to path ${zippedPortalPath}, because it already exists`);
await prompts.existingDestinationPortalZipPrompt();
}
try {
if (!(await fs.pathExists(flags.destination))) {
throw new Error(`Destination path ${flags.destination} does not exist`);
throw new Error(`Destination path ${flags.destination} does not exist.`);
} else if (!(await fs.pathExists(flags.folder))) {
throw new Error(`Portal build folder ${flags.folder} does not exist`);
throw new Error(`Portal build folder ${flags.folder} does not exist.`);
}
const client: Client = await SDKClient.getInstance().getClient(overrideAuthKey, this.config.configDir);
const docsPortalController: DocsPortalManagementController = new DocsPortalManagementController(client);

const zippedBuildFilePath = await zipDirectory(sourceFolderPath, flags.destination);
const pathsToIgnore = getGeneratedFilesPaths(sourceFolderPath, portalFolderPath);
const zippedBuildFilePath = await validateAndZipPortalSource(
sourceFolderPath,
path.join(sourceFolderPath, ".portal_source.zip"),
pathsToIgnore
);

const generatePortalParams: GeneratePortalParams = {
zippedBuildFilePath,
Expand All @@ -74,11 +80,16 @@ Your portal has been generated at D:/
overrideAuthKey,
zip
};
ux.action.start('Generating portal');

prompts.displayPortalGenerationMessage();

const generatedPortalPath: string = await downloadDocsPortal(generatePortalParams, this.config.configDir);
ux.action.stop();
this.log(`Your portal has been generated at ${generatedPortalPath}`);

prompts.displayPortalGenerationSuccessMessage();

prompts.displayOutroMessage(generatedPortalPath);
} catch (error) {
prompts.displayPortalGenerationErrorMessage();
if (error && (error as AxiosError).response) {
const apiError = error as AxiosError;
const apiResponse = apiError.response;
Expand Down
49 changes: 21 additions & 28 deletions src/commands/portal/quickstart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ import { SpecFile } from "../../types/portal/quickstart";
import { getMessageInRedColor } from "../../utils/utils";

export default class PortalQuickstart extends Command {
static description = "Create your first API Portal using APIMatics Docs as Code offering.";
static description = "Create your first API Portal using APIMatic's Docs as Code offering.";

static examples = ["$ apimatic portal:quickstart"];

private async getSpecFile(
prompts: PortalQuickstartPrompts,
controller: PortalQuickstartController
): Promise<SpecFile> {
const spec = await prompts.specPrompt();
const specPath = await prompts.specPrompt();

const specFile = await controller.getSpecFile(spec);
const specFile = await controller.getSpecFile(specPath);

prompts.displaySpecValidationMessage();

Expand All @@ -30,25 +30,16 @@ export default class PortalQuickstart extends Command {
specFile: SpecFile,
apiValidationController: APIValidationExternalApisController
): Promise<ApiValidationSummary> {
try {
const apiValidationSummary = await controller.getSpecValidationSummary(specFile, apiValidationController);

if (!apiValidationSummary.success) {
prompts.displaySpecValidationFailureMessage();
await prompts.specValidationFailurePrompt();
} else {
prompts.displaySpecValidationSuccessMessage();
}
const apiValidationSummary = await controller.getSpecValidationSummary(prompts, specFile, apiValidationController);

return apiValidationSummary;
} catch (error) {
prompts.displaySpecValidationErrorMessage();
this.error(
getMessageInRedColor(
"The specified path/URL does not point to a valid API Definition file. Please provide a valid API Definition file and try again."
)
);
if (!apiValidationSummary.success) {
prompts.displaySpecValidationFailureMessage();
await prompts.specValidationFailurePrompt();
} else {
prompts.displaySpecValidationSuccessMessage();
}

return apiValidationSummary;
}

private async getBuildDirectory(
Expand All @@ -58,17 +49,17 @@ export default class PortalQuickstart extends Command {
apiValidationSummary: ApiValidationSummary,
languages: string[]
): Promise<string> {
const directory = await prompts.buildDirectoryPrompt();
const buildDirectoryPath = await prompts.buildDirectoryPrompt();

prompts.displayBuildDirectoryGenerationMessage();

await controller.setupBuildDirectory(prompts, directory, specFile, apiValidationSummary, languages);
await controller.setupBuildDirectory(prompts, buildDirectoryPath, specFile, apiValidationSummary, languages);

prompts.displayBuildDirectoryGenerationSuccessMessage(directory);
prompts.displayBuildDirectoryGenerationSuccessMessage(buildDirectoryPath);

prompts.displayBuildDirectoryAsTree(directory);
prompts.displayBuildDirectoryAsTree(buildDirectoryPath);

return directory;
return buildDirectoryPath;
}

private async getGeneratedPortalPath(
Expand Down Expand Up @@ -128,9 +119,11 @@ export default class PortalQuickstart extends Command {

const generatedPortalPath = await this.getGeneratedPortalPath(prompts, controller, directory);

controller.servePortal(generatedPortalPath, directory, this.config.configDir);

prompts.displayOutroMessage();
const serverStarted = await controller.servePortal(generatedPortalPath, directory, this.config.configDir);

if (serverStarted) {
prompts.displayOutroMessage(directory);
}
} catch (error) {
this.error(getMessageInRedColor(error instanceof Error ? error.message : String(error)));
}
Expand Down
16 changes: 4 additions & 12 deletions src/commands/portal/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Command, Flags } from "@oclif/core";
import { generatePortal } from "../../controllers/portal/serve";
import { PortalServerService } from "../../services/portal/server";
import { PortalServePrompts } from "../../prompts/portal/serve";
import { cleanUpGeneratedPortalFiles, getMessageInRedColor } from "../../utils/utils";
import { cleanUpGeneratedPortalFiles, getGeneratedFilesPaths, getMessageInRedColor } from "../../utils/utils";
import { PortalServeValidator } from "../../validators/portal/serveValidator";

export default class PortalServe extends Command {
Expand All @@ -19,7 +19,7 @@ export default class PortalServe extends Command {
destination: Flags.string({
char: "d",
description: "Directory to store and serve the generated portal.",
default: "./api-portal",
default: "./generated_portal",
parse: async (input) => path.resolve(input)
}),
source: Flags.string({
Expand Down Expand Up @@ -49,17 +49,9 @@ export default class PortalServe extends Command {
};

static examples = [
'$ apimatic portal:serve --source="./" --destination="./api-portal" --port=3000 --open --no-reload'
'$ apimatic portal:serve --source="./" --destination="./generated_portal" --port=3000 --open --no-reload'
];

private getGeneratedFilesPaths(sourceDir: string, portalDir: string): string[] {
const generatedZipPath = path.join(sourceDir, "portal_source.zip");
const generatedPortalZipPath = path.join(sourceDir, "generated_portal.zip");
const generatedPortalPath = path.join(path.dirname(portalDir), "api-portal");

return [generatedZipPath, generatedPortalPath, generatedPortalZipPath];
}

async run() {
const { flags } = await this.parse(PortalServe);
const ignoredPaths = flags.ignore.split(",").map((path) => path.trim());
Expand All @@ -70,7 +62,7 @@ export default class PortalServe extends Command {
const serverService = new PortalServerService();
const prompts = new PortalServePrompts();
const validator = new PortalServeValidator(this.error);
const allIgnoredPaths = [...ignoredPaths, ...this.getGeneratedFilesPaths(sourceDir, portalDir)];
const allIgnoredPaths = [...ignoredPaths, ...getGeneratedFilesPaths(sourceDir, portalDir)];

await validator.validate(port, flags.destination, sourceDir, portalDir);

Expand Down
26 changes: 9 additions & 17 deletions src/controllers/api/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,16 @@ export const getValidationSummary = async (

if (file) {
const fileStatus = fs.statSync(file);
if (fileStatus.isDirectory()){
if (fileStatus.isDirectory()) {
const tempDir = await createTempDirectory();
const zipPath = await zipDirectory(file, tempDir);
const zipFile = new FileWrapper(fs.createReadStream(zipPath));
validation = await apiValidationController.validateAPIViaFile(ContentType.EnumMultipartformdata, zipFile);

try {
const zipPath = await zipDirectory(file, tempDir);
const zipFile = new FileWrapper(fs.createReadStream(zipPath));
validation = await apiValidationController.validateAPIViaFile(ContentType.EnumMultipartformdata, zipFile);

await deleteFile(zipPath);
}
catch (error) {
throw new Error("There was an error validating your spec file.");
}
finally {
await fs.remove(tempDir);
}
}
else {
await deleteFile(zipPath);

await fs.remove(tempDir);
} else {
const fileDescriptor = new FileWrapper(fs.createReadStream(file));
validation = await apiValidationController.validateAPIViaFile(ContentType.EnumMultipartformdata, fileDescriptor);
}
Expand All @@ -38,6 +30,6 @@ export const getValidationSummary = async (
} else {
throw new Error("Please provide a specification file");
}

return validation.result;
};
Loading