Skip to content
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@apimatic/cli",
"description": "The official CLI for APIMatic.",
"version": "1.1.0-alpha.16",
"version": "1.1.0-alpha.19",
"author": "APIMatic",
"bin": {
"apimatic": "./bin/run.js"
Expand Down
25 changes: 17 additions & 8 deletions src/actions/portal/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { BuildContext } from "../../types/build-context.js";
import { PortalContext } from "../../types/portal-context.js";
import { withDirPath } from "../../infrastructure/tmp-extensions.js";


export class GenerateAction {
private readonly prompts: PortalGeneratePrompts = new PortalGeneratePrompts();
private readonly zipArchiver: ZipService = new ZipService();
Expand All @@ -30,13 +29,12 @@ export class GenerateAction {
force: boolean,
zipPortal: boolean
): Promise<ActionResult> => {

if (buildDirectory.isEqual(portalDirectory)) {
return ActionResult.error(`The 'src' and 'portal' directory cannot be the same: "${portalDirectory}"`);
}

const buildContext = new BuildContext(buildDirectory);
if (!await buildContext.validate()) {
if (!(await buildContext.validate())) {
return ActionResult.error(`The 'src' directory is either empty or invalid: "${buildDirectory}"`);
}

Expand Down Expand Up @@ -68,10 +66,14 @@ export class GenerateAction {

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

private async parseError(error: string | NodeJS.ReadableStream, portalDirectory: DirectoryPath, tempDirectory: DirectoryPath): Promise<string> {
if (typeof error === 'string') {
private async parseError(
error: string | NodeJS.ReadableStream,
portalDirectory: DirectoryPath,
tempDirectory: DirectoryPath
): Promise<string> {
if (typeof error === "string") {
return error;
}

Expand All @@ -82,7 +84,14 @@ export class GenerateAction {
await this.zipArchiver.unArchive(tempErrorFilePath, portalDirectory);

const errorReportPath = portalDirectory.join("apimatic-debug");
return "An error occurred during portal generation due to an issue with the input. An error report has been written at the destination path: " +
errorReportPath;

const htmlFilePath = new FilePath(errorReportPath, new FileName("apimatic-report.html"));
await this.fileService.openFile(htmlFilePath); // Open the error report in the default browser

return (
"An error occurred during portal generation due to an issue with the input. " +
"An error report has been written at the destination path: " +
errorReportPath
);
}
}
34 changes: 32 additions & 2 deletions src/infrastructure/file-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { FilePath } from "../types/file/filePath.js";
import { DirectoryPath } from "../types/file/directoryPath.js";
import { pipeline } from "stream";
import { promisify } from "util";
import { spawn } from "child_process";

export class FileService {
public async fileExists(file: FilePath): Promise<boolean> {
Expand All @@ -29,7 +30,7 @@ export class FileService {
const files = await fsExtra.readdir(dir.toString());
return files.length === 0;
} catch (error) {
return error instanceof Error && 'code' in error && error.code === "ENOENT";
return error instanceof Error && "code" in error && error.code === "ENOENT";
}
}

Expand All @@ -50,7 +51,7 @@ export class FileService {
}

public async getContents(filePath: FilePath): Promise<string> {
return await fsExtra.readFile(filePath.toString(), 'utf-8');
return await fsExtra.readFile(filePath.toString(), "utf-8");
}

public async writeFile(filePath: FilePath, stream: NodeJS.ReadableStream) {
Expand All @@ -66,6 +67,35 @@ export class FileService {
await fsExtra.copyFile(source.toString(), destination.toString());
}

public async openFile(filePath: FilePath): Promise<void> {
const targetPath = filePath.toString();

// Determine the command and args without using the shell
let command: string;
let args: string[];

switch (os.platform()) {
case "win32":
command = "cmd";
args = ["/c", "start", "", targetPath];
break;
case "darwin":
command = "open";
args = [targetPath];
break;
default:
command = "xdg-open";
args = [targetPath];
break;
}

try {
const child = spawn(command, args, { stdio: "ignore", detached: true });
child.unref(); // Let it run without blocking
} catch {
// Silently ignore errors
}
}
}

const streamPipeline = promisify(pipeline);