Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enable proxy support in the cli #1608

Merged
merged 13 commits into from
Jan 5, 2025
Merged
5,782 changes: 2,976 additions & 2,806 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"fast-levenshtein": "^3.0.0",
"fs-extra": "^11.1.0",
"generator-v2": "npm:@asyncapi/generator@^2.4.1",
"https-proxy-agent": "^7.0.6",
"inquirer": "^8.2.0",
"js-yaml": "^4.1.0",
"lodash.template": "^4.4.0",
Expand Down Expand Up @@ -172,4 +173,4 @@
"action:test": "cd github-action && make test"
},
"types": "lib/index.d.ts"
}
}
18 changes: 15 additions & 3 deletions src/commands/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { SpecificationFileNotFound } from '../core/errors/specification-file';
import { convert, convertOpenAPI, convertPostman } from '@asyncapi/converter';
import type { AsyncAPIConvertVersion, OpenAPIConvertVersion } from '@asyncapi/converter';
import { cyan, green } from 'picocolors';

import { proxyFlags } from '../core/flags/proxy.flags';
// @ts-ignore
import specs from '@asyncapi/specs';
import { convertFlags } from '../core/flags/convert.flags';
Expand All @@ -20,15 +20,27 @@ export default class Convert extends Command {
static metricsMetadata: any = {};
static description = 'Convert asyncapi documents older to newer versions or OpenAPI/postman-collection documents to AsyncAPI';

static flags = convertFlags(latestVersion);
static flags = {
...convertFlags(latestVersion),
...proxyFlags()

};

static args = {
'spec-file': Args.string({description: 'spec path, url, or context-name', required: false}),
proxyHost: Args.string({description: 'Name of the Proxy Host', required: false}),
proxyPort: Args.string({description: 'Name of the Port of the ProxyHost', required: false}),
};

async run() {
const { args, flags } = await this.parse(Convert);
const filePath = args['spec-file'];
let filePath = args['spec-file'];
const proxyHost = flags['proxyHost'];
const proxyPort = flags['proxyPort'];
if (proxyHost && proxyPort) {
const proxyUrl = `http://${proxyHost}:${proxyPort}`;
filePath = `${filePath}+${proxyUrl}`; // Update filePath with proxyUrl
}
let convertedFile;
let convertedFileFormatted;

Expand Down
35 changes: 25 additions & 10 deletions src/commands/optimize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { promises } from 'fs';
import { Parser } from '@asyncapi/parser';
import { optimizeFlags } from '../core/flags/optimize.flags';
import { proxyFlags } from '../core/flags/proxy.flags';

const { writeFile } = promises;

Expand Down Expand Up @@ -42,27 +43,41 @@
'asyncapi optimize ./asyncapi.yaml --ignore=schema'
];

static flags = optimizeFlags();
static flags = {
...optimizeFlags(),
...proxyFlags(),
};

static args = {
'spec-file': Args.string({description: 'spec path, url, or context-name', required: false}),
proxyHost: Args.string({description: 'Name of the Proxy Host', required: false}),
proxyPort: Args.string({description: 'Name of the Port of the ProxyHost', required: false}),
};

parser = new Parser();

async run() {

Check warning on line 59 in src/commands/optimize.ts

View workflow job for this annotation

GitHub Actions / Test NodeJS PR - ubuntu-latest

Refactor this function to reduce its Cognitive Complexity from 17 to the 15 allowed
const { args, flags } = await this.parse(Optimize); //NOSONAR
const filePath = args['spec-file'];

let filePath = args['spec-file'];
const proxyHost = flags['proxyHost'];
const proxyPort = flags['proxyPort'];
if (proxyHost && proxyPort) {
const proxyUrl = `http://${proxyHost}:${proxyPort}`;
filePath = `${filePath}+${proxyUrl}`; // Update filePath with proxyUrl
}
try {
this.specFile = await load(filePath);
} catch (err) {
this.error(
new ValidationError({
type: 'invalid-file',
filepath: filePath,
})
);
} catch (err:any) {
if (err.message.includes('Failed to download')) {
throw new Error('Proxy Connection Error: Unable to establish a connection to the proxy check hostName or PortNumber.');
} else {
this.error(
new ValidationError({
type: 'invalid-file',
filepath: filePath,
})
);
}
}

let optimizer: Optimizer;
Expand Down
21 changes: 15 additions & 6 deletions src/commands/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,41 @@ import { validate, ValidateOptions, ValidationStatus, parse } from '../core/pars
import { load } from '../core/models/SpecificationFile';
import { specWatcher } from '../core/globals';
import { validateFlags } from '../core/flags/validate.flags';
import { proxyFlags } from '../core/flags/proxy.flags';
import { calculateScore } from '../core/utils/scoreCalculator';

export default class Validate extends Command {
static description = 'validate asyncapi file';

static flags = validateFlags();
static flags = {
...validateFlags(),
...proxyFlags(), // Merge proxyFlags with validateFlags
};

static args = {
'spec-file': Args.string({description: 'spec path, url, or context-name', required: false}),
proxyHost: Args.string({description: 'Name of the Proxy Host', required: false}),
proxyPort: Args.string({description: 'Name of the Port of the ProxyHost', required: false}),
};

async run() {
const { args, flags } = await this.parse(Validate); //NOSONAR

const filePath = args['spec-file'];
let filePath = args['spec-file'];
const proxyHost = flags['proxyHost'];
const proxyPort = flags['proxyPort'];
if (proxyHost && proxyPort) {
const proxyUrl = `http://${proxyHost}:${proxyPort}`;
filePath = `${filePath}+${proxyUrl}`; // Update filePath with proxyUrl
}
this.specFile = await load(filePath);
const watchMode = flags.watch;
if (flags['score']) {
this.specFile = await load(filePath);
const { document } = await parse(this,this.specFile);
this.log(`The score of the asyncapi document is ${await calculateScore(document)}`);
}
this.specFile = await load(filePath);
if (watchMode) {
specWatcher({ spec: this.specFile, handler: this, handlerName: 'validate' });
}

const result = await validate(this, this.specFile, flags as ValidateOptions);
this.metricsMetadata.validation_result = result;

Expand Down
15 changes: 15 additions & 0 deletions src/core/flags/proxy.flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Flags } from '@oclif/core';

export const proxyFlags = () => {
return {
proxyHost: Flags.string({
description: 'Name of the ProxyHost',
required: false
}),
proxyPort: Flags.string({
description: 'Port number number for the proxyHost.',
required: false
})
};
};

40 changes: 34 additions & 6 deletions src/core/models/SpecificationFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { loadContext } from './Context';
import { ErrorLoadingSpec } from '../errors/specification-file';
import { MissingContextFileError } from '../errors/context-error';
import { fileFormat } from 'core/flags/format.flags';

import { HttpsProxyAgent } from 'https-proxy-agent';
const { readFile, lstat } = fs;
const allowedFileNames: string[] = [
'asyncapi.json',
Expand Down Expand Up @@ -87,15 +87,43 @@ export class Specification {

static async fromURL(URLpath: string) {
let response;
const delimiter = '+';
let targetUrl = URLpath;
let proxyUrl = '';

// Check if URLpath contains a proxy URL
if (URLpath.includes(delimiter)) {
[targetUrl, proxyUrl] = URLpath.split(delimiter);
}

try {
response = await fetch(URLpath, { method: 'GET' });
if (!response.ok) {
throw new ErrorLoadingSpec('url', URLpath);
// Validate the target URL
new URL(targetUrl);

const fetchOptions: any = { method: 'GET' };

// If proxy URL is provided, create a proxy agent
if (proxyUrl) {
try {
new URL(proxyUrl);
const proxyAgent = new HttpsProxyAgent(proxyUrl);
fetchOptions.agent = proxyAgent;
response = await fetch(targetUrl,fetchOptions);
} catch (err: any) {
throw new Error('Proxy Connection Error: Unable to establish a connection to the proxy check hostName or PortNumber');
}
} else {
response = await fetch(targetUrl);
if (!response.ok) {
throw new ErrorLoadingSpec('url', targetUrl);
}
}
} catch (error) {
throw new ErrorLoadingSpec('url', URLpath);
console.log(error);
throw new ErrorLoadingSpec('url', targetUrl);
}
return new Specification(await response?.text() as string, { fileURL: URLpath });

return new Specification(await response?.text() as string, { fileURL: targetUrl });
}
}

Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/newtemplate/__transpiled/index.js

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

9 changes: 9 additions & 0 deletions test/integration/convert.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ describe('convert', () => {
expect(ctx.stderr).to.equal('');
done();
});
test
.stderr()
.stdout()
.command(['convert', 'http://localhost:8080/dummySpec.yml --proxyHost=host --proxyPort=8080'])
.it('should throw error when url is passed with proxyHost and proxyPort with invalid host ', (ctx, done) => {
expect(ctx.stdout).to.contain('');
expect(ctx.stderr).to.equal('error loading AsyncAPI document from url: Failed to download http://localhost:8080/dummySpec.yml --proxyHost=host --proxyPort=8080.\n');
done();
});
});

describe('with no arguments', () => {
Expand Down
9 changes: 9 additions & 0 deletions test/integration/optimize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ describe('optimize', () => {
expect(ctx.stderr).to.equal('');
done();
});
test
.stderr()
.stdout()
.command(['optimize', 'http://localhost:8080/dummySpec.yml --proxyHost=host --proxyPort=8080'])
.it('should throw error when url is passed with proxyHost and proxyPort with invalid host ', (ctx, done) => {
expect(ctx.stdout).to.contain('');
expect(ctx.stderr).to.equal('Error: Proxy Connection Error: Unable to establish a connection to the proxy check hostName or PortNumber.\n');
done();
});
});

describe('with no arguments', () => {
Expand Down
9 changes: 9 additions & 0 deletions test/integration/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ describe('validate', () => {
expect(ctx.stderr).to.equal('');
done();
});
test
.stderr()
.stdout()
.command(['validate', 'http://localhost:8080/dummySpec.yml --proxyHost=host --proxyPort=8080'])
.it('should throw error when url is passed with proxyHost and proxyPort with invalid host ', (ctx, done) => {
expect(ctx.stdout).to.contain('');
expect(ctx.stderr).to.equal('error loading AsyncAPI document from url: Failed to download http://localhost:8080/dummySpec.yml --proxyHost=host --proxyPort=8080.\n');
done();
});

test
.stderr()
Expand Down
Loading