Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

Commit

Permalink
feat: allow stream response to file (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
iowillhoit authored Jul 16, 2024
1 parent 85fa226 commit 2d2ee4d
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 26 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ node_modules

oclif.manifest.json
oclif.lock

coverage
.nyc_output
4 changes: 2 additions & 2 deletions command-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
{
"command": "org:api",
"plugin": "@cristiand391/sf-plugin-api",
"flags": ["body", "header", "include", "method", "target-org"],
"flags": ["body", "header", "include", "method", "target-org", "stream-to-file"],
"alias": [],
"flagChars": ["H", "X", "i", "o"],
"flagChars": ["H", "X", "i", "o", "S"],
"flagAliases": []
}
]
71 changes: 47 additions & 24 deletions src/commands/org/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { readFile } from 'node:fs/promises';
import { EOL } from 'node:os';
import * as fs from 'node:fs'
import got, { Headers } from 'got';
import chalk from 'chalk';
import { ProxyAgent } from 'proxy-agent';
Expand Down Expand Up @@ -38,6 +39,7 @@ export class OrgApi extends SfCommand<void> {
char: 'i',
summary: 'Include HTTP response status and headers in the output.',
default: false,
exclusive: ['stream-to-file'],
}),
method: Flags.option({
options: [
Expand All @@ -60,10 +62,17 @@ export class OrgApi extends SfCommand<void> {
char: 'H',
multiple: true,
}),
'stream-to-file': Flags.string({
summary: 'Stream response to file',
helpValue: 'Example: report.xlsx',
char: 'S',
exclusive: ['include'],
}),
body: Flags.string({
summary:
'The file to use as the body for the request (use "-" to read from standard input).',
'The file to use as the body for the request (use "-" to read from standard input, use "" for an empty body).',
parse: async (input) => {
if (input === '') return '';
if (input === '-') {
const body = await readStdin();
if (body) {
Expand Down Expand Up @@ -92,13 +101,14 @@ export class OrgApi extends SfCommand<void> {
const headers: { [key: string]: string } = {};

for (const header of keyValPair) {
const split = header.split(':');
if (split.length !== 2) {
const [key, ...rest] = header.split(':')
const value = rest.join(':').trim();
if (!key || !value) {
throw new SfError(`Failed to parse HTTP header: "${header}".`, '', [
'Make sure the header is in a "key:value" format, e.g. "Accept: application/json"',
]);
}
headers[split[0]] = split[1].trim();
headers[key] = value;
}

return headers;
Expand All @@ -108,14 +118,15 @@ export class OrgApi extends SfCommand<void> {
const { flags, args } = await this.parse(OrgApi);

const org = flags['target-org'];
const streamFile = flags['stream-to-file'];

await org.refreshAuth();

const url = `${org.getField<string>(Org.Fields.INSTANCE_URL)}/${
args.endpoint
}`;

const res = await got(url, {
const options = {
agent: { https: new ProxyAgent() },
method: flags.method,
headers: {
Expand All @@ -129,30 +140,42 @@ export class OrgApi extends SfCommand<void> {
body: flags.method === 'GET' ? undefined : flags.body,
throwHttpErrors: false,
followRedirect: false,
});
}

if (streamFile) {
const responseStream = got.stream(url, options);
const fileStream = fs.createWriteStream(streamFile);
responseStream.pipe(fileStream);

// Print HTTP response status and headers.
if (flags.include) {
let httpInfo = `HTTP/${res.httpVersion} ${res.statusCode} ${EOL}`;
fileStream.on('finish', () => this.log(`File saved to ${streamFile}`));
fileStream.on('error', (error) => { throw SfError.wrap(error) });
responseStream.on('error', (error) => { throw SfError.wrap(error) });
} else {
const res = await got(url, options);

for (const [header] of Object.entries(res.headers)) {
httpInfo += `${chalk.blue.bold(header)}: ${
res.headers[header] as string
}${EOL}`;
// Print HTTP response status and headers.
if (flags.include) {
let httpInfo = `HTTP/${res.httpVersion} ${res.statusCode} ${EOL}`;

for (const [header] of Object.entries(res.headers)) {
httpInfo += `${chalk.blue.bold(header)}: ${
res.headers[header] as string
}${EOL}`;
}
this.log(httpInfo);
}
this.log(httpInfo);
}

try {
// Try to pretty-print JSON response.
ux.styledJSON(JSON.parse(res.body));
} catch (err) {
// If response body isn't JSON, just print it to stdout.
this.log(res.body);
}
try {
// Try to pretty-print JSON response.
ux.styledJSON(JSON.parse(res.body));
} catch (err) {
// If response body isn't JSON, just print it to stdout.
this.log(res.body);
}

if (res.statusCode >= 400) {
process.exitCode = 1;
if (res.statusCode >= 400) {
process.exitCode = 1;
}
}
}
}

0 comments on commit 2d2ee4d

Please sign in to comment.