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: add proxy support in commands/generate folder #1622

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
29 changes: 26 additions & 3 deletions src/commands/generate/fromTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { intro, isCancel, spinner, text } from '@clack/prompts';
import { inverse, yellow, magenta, green, red } from 'picocolors';
import fetch from 'node-fetch';
import { fromTemplateFlags } from '../../core/flags/generate/fromTemplate.flags';
import { proxyFlags } from 'core/flags/proxy.flags';
import { HttpsProxyAgent } from 'https-proxy-agent';

interface IMapBaseUrlToFlag {
url: string,
Expand Down Expand Up @@ -57,18 +59,31 @@ export default class Template extends Command {
'asyncapi generate fromTemplate asyncapi.yaml @asyncapi/html-template --param version=1.0.0 singleFile=true --output ./docs --force-write'
];

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

static args = {
asyncapi: Args.string({ description: '- Local path, url or context-name pointing to AsyncAPI file', required: true }),
template: Args.string({ description: '- Name of the generator template like for example @asyncapi/html-template or https://github.com/asyncapi/html-template', required: true }),
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() {
const { args, flags } = await this.parse(Template); // NOSONAR
const { args, flags } = await this.parse(Template); // NOSONAR
const interactive = !flags['no-interactive'];
const proxyHost = flags['proxyHost'];
const proxyPort = flags['proxyPort'];

let proxyAgent;
if (proxyHost && proxyPort) {
const proxyUrl = `http://${proxyHost}:${proxyPort}`;
proxyAgent = new HttpsProxyAgent(proxyUrl);
}

let { asyncapi, template } = args;
let output = flags.output as string;
Expand All @@ -94,7 +109,9 @@ export default class Template extends Command {
url: flags['registry-url'],
auth: flags['registry-auth'],
token: flags['registry-token']
}
},
...(proxyAgent && { fetchOptions: { agent: proxyAgent } })

};
const asyncapiInput = (await load(asyncapi)) || (await load());

Expand Down Expand Up @@ -258,6 +275,9 @@ export default class Template extends Command {
try {
specification = await load(asyncapi);
} 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.');
}
return this.error(
new ValidationError({
type: 'invalid-file',
Expand All @@ -283,6 +303,9 @@ export default class Template extends Command {
try {
specification = await load(asyncapi);
} 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.');
}
return this.error(
new ValidationError({
type: 'invalid-file',
Expand Down
19 changes: 18 additions & 1 deletion src/commands/generate/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,34 @@
import { green, inverse } from 'picocolors';
import { generateModels, Languages, ModelinaArgs } from '@asyncapi/modelina-cli';
import { modelsFlags } from '../../core/flags/generate/models.flags';
import { proxyFlags } from 'core/flags/proxy.flags';
import { HttpsProxyAgent } from 'https-proxy-agent';
import fetch, { RequestInit } from 'node-fetch';

export default class Models extends Command {
static description = 'Generates typed models';

static readonly args = ModelinaArgs as any;

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

async run() {
const { args, flags } = await this.parse(Models);
let { language, file } = args;
let { output } = flags;

const interactive = !flags['no-interactive'];
const proxyHost = flags['proxyHost'];
const proxyPort = flags['proxyPort'];
let proxyAgent;

Check failure on line 30 in src/commands/generate/models.ts

View workflow job for this annotation

GitHub Actions / Test NodeJS PR - ubuntu-latest

Variable 'proxyAgent' implicitly has type 'any' in some locations where its type cannot be determined.

if (proxyHost && proxyPort) {
const proxyUrl = `http://${proxyHost}:${proxyPort}`;
proxyAgent = new HttpsProxyAgent(proxyUrl);
}

if (!interactive) {
intro(inverse('AsyncAPI Generate Models'));
Expand All @@ -29,6 +43,9 @@
output = parsedArgs.output;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check here @IITI-tushar

const customFetch = proxyAgent
? (url: string, options: RequestInit = {}) => fetch(url, { ...options, agent: proxyAgent })

Check failure on line 47 in src/commands/generate/models.ts

View workflow job for this annotation

GitHub Actions / Test NodeJS PR - ubuntu-latest

Variable 'proxyAgent' implicitly has an 'any' type.
: fetch;
const inputFile = (await load(file)) || (await load());

const { document, diagnostics ,status } = await parse(this, inputFile, flags as ValidateOptions);
Expand Down
51 changes: 51 additions & 0 deletions test/integration/generate/fromTemplate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,55 @@ describe('template', () => {
}
);
});
describe('fetch, httpsproxyagent, and proxyflags', () => {
test
.stdout()
.command([
...generalOptions,
'--output=./test/docs/proxy',
'--force-write',
'--proxyHost=localhost',
'--proxyPort=8080',
nonInteractive
])
.it('should use proxy settings when provided', (ctx, done) => {
expect(ctx.stdout).to.contain('Check out your shiny new generated files at ./test/docs/proxy');
cleanup('./test/docs/proxy');
done();
});

test
.stdout()
.command([
'generate:fromTemplate',
'https://raw.githubusercontent.com/asyncapi/spec/master/examples/2.0.0/streetlights.yml',
'@asyncapi/html-template',
'--output=./test/docs/fetch',
'--force-write',
nonInteractive
])
.it('should fetch remote AsyncAPI file', (ctx, done) => {
expect(ctx.stdout).to.contain('Check out your shiny new generated files at ./test/docs/fetch');
cleanup('./test/docs/fetch');
done();
});

test
.stdout()
.command([
'generate:fromTemplate',
'https://raw.githubusercontent.com/asyncapi/spec/master/examples/2.0.0/streetlights.yml',
'@asyncapi/html-template',
'--output=./test/docs/proxy-fetch',
'--force-write',
'--proxyHost=localhost',
'--proxyPort=8080',
nonInteractive
])
.it('should fetch remote AsyncAPI file using proxy', (ctx, done) => {
expect(ctx.stdout).to.contain('Check out your shiny new generated files at ./test/docs/proxy-fetch');
cleanup('./test/docs/proxy-fetch');
done();
});
});
});
63 changes: 63 additions & 0 deletions test/integration/generate/models.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { expect, test } from '@oclif/test';
import path from 'path';
import rimraf from 'rimraf';
import { createMockServer, stopMockServer } from '../../helpers';
import { HttpsProxyAgent } from 'https-proxy-agent';
import fetch from 'node-fetch';
const generalOptions = ['generate:models'];
const outputDir = './test/fixtures/generate/models';

Expand Down Expand Up @@ -36,6 +38,67 @@ describe('models', () => {
done();
});

describe('proxy-related tests', () => {
test
.stderr()
.stdout()
.env({ PROXY_HOST: '127.0.0.1', PROXY_PORT: '3128' }) // Mock environment variables for proxy
.do(() => {
process.env.PROXY_HOST = '127.0.0.1';
process.env.PROXY_PORT = '3128';
})
.command([...generalOptions, 'typescript', 'http://localhost:8080/dummySpec.yml', '--proxyHost=127.0.0.1', '--proxyPort=3128'])
.it('works with proxy settings', (ctx, done) => {
expect(ctx.stdout).to.contain('Successfully generated the following models: ');
expect(process.env.PROXY_HOST).to.equal('127.0.0.1');
expect(process.env.PROXY_PORT).to.equal('3128');
done();
});

test
.stderr()
.stdout()
.command([...generalOptions, 'typescript', 'http://localhost:8080/dummySpec.yml'])
.it('works without proxy settings', (ctx, done) => {
expect(ctx.stdout).to.contain('Successfully generated the following models: ');
done();
});
});

describe('fetch with proxy and without proxy', () => {
test
.stderr()
.stdout()
.do(() => {
const proxyUrl = 'http://127.0.0.1:3128';
const proxyAgent = new HttpsProxyAgent(proxyUrl);
const customFetch = (url, options = {}) => fetch(url, { ...options, agent: proxyAgent });

customFetch('http://localhost:8080/dummySpec.yml')
.then((response) => {
expect(response.ok).to.be.true;
})
.catch((err) => {
throw new Error(`Failed to fetch with proxy: ${err.message}`);
});
})
.it('tests fetch with proxy agent');

test
.stderr()
.stdout()
.do(() => {
fetch('http://localhost:8080/dummySpec.yml')
.then((response) => {
expect(response.ok).to.be.true;
})
.catch((err) => {
throw new Error(`Failed to fetch without proxy: ${err.message}`);
});
})
.it('tests fetch without proxy agent');
});

describe('with logging diagnostics', () => {
test
.stderr()
Expand Down
Loading