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

Commit 06ef90d

Browse files
authored
feat: --body supports reading from STDIN (#23)
If you do `flag.allowStdin: true` then oclif will read from stdin for you if the value is `-`, so there's no way to know if the flag value came from stdin or the user specified a file path (here we need to read the file). I don't like having another specific flag just to read from stdin so I made this a string flag and handle reading from stdin/file in `flag.parse`. also: * made `got` not follow redirects (use `--include` flag and check the `Location` header if need that). * bumped deps
1 parent 0941d9c commit 06ef90d

File tree

5 files changed

+2072
-762
lines changed

5 files changed

+2072
-762
lines changed

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ FLAGS
3232
-X, --method=<option> [default: GET] The HTTP method for the request.
3333
<options: GET|POST|PUT|PATCH|HEAD|DELETE|OPTIONS|TRACE>
3434
-i, --include Include HTTP response status and headers in the output.
35-
-o, --target-org=username (required) Username or alias of the target org.
36-
--body=file The file to use as the body for the request.
35+
-o, --target-org=username (required) Username or alias of the target org. Not required if the `target-org`
36+
configuration variable is already set.
37+
--body=file The file to use as the body for the request (use "-" to read from standard input).
3738
3839
DESCRIPTION
3940
Makes an authenticated HTTP request to the Salesforce REST API and prints the response.

package.json

+14-13
Original file line numberDiff line numberDiff line change
@@ -9,38 +9,39 @@
99
"type": "module",
1010
"bugs": "https://github.com/cristiand391/sf-plugin-api/issues",
1111
"dependencies": {
12-
"@oclif/core": "^3.10.1",
13-
"@salesforce/core": "^5.3.17",
14-
"@salesforce/sf-plugins-core": "^4.0.0",
12+
"@oclif/core": "^3.18.1",
13+
"@salesforce/core": "^6.5.0",
14+
"@salesforce/sf-plugins-core": "^7.1.4",
1515
"chalk": "^5.3.0",
1616
"got": "^13.0.0",
1717
"proxy-agent": "^6.3.1"
1818
},
1919
"devDependencies": {
20-
"@oclif/plugin-command-snapshot": "^5.0.1",
20+
"@oclif/plugin-command-snapshot": "^5.0.6",
2121
"@types/chai": "^4.3.4",
2222
"@types/inquirer": "^9.0.3",
2323
"@types/mocha": "^10.0.1",
2424
"@types/sinon": "^10.0.13",
25-
"@typescript-eslint/eslint-plugin": "^6.9.0",
26-
"@typescript-eslint/parser": "^6.9.0",
25+
"@typescript-eslint/eslint-plugin": "^6.17.0",
26+
"@typescript-eslint/parser": "^6.17.0",
2727
"chai": "^4.3.6",
28-
"eslint": "^8.26.0",
28+
"eslint": "^8.56.0",
2929
"eslint-config-prettier": "^8.5.0",
3030
"eslint-config-salesforce": "^2.0.2",
31-
"eslint-config-salesforce-typescript": "^1.1.1",
31+
"eslint-config-salesforce-typescript": "^3.0.16",
3232
"eslint-plugin-import": "2.26.0",
3333
"eslint-plugin-jsdoc": "^46.4.6",
34-
"eslint-plugin-sf-plugin": "^1.16.2",
34+
"eslint-plugin-sf-plugin": "^1.17.1",
35+
"eslint-plugin-unicorn": "^49.0.0",
3536
"mocha": "^10.2.0",
3637
"nock": "^13.3.0",
37-
"oclif": "^3.11.3",
38+
"oclif": "^4.3.9",
3839
"prettier": "^3.0.1",
3940
"shx": "0.3.4",
4041
"sinon": "^15.0.3",
4142
"strip-ansi": "^7.1.0",
42-
"ts-node": "^10.9.1",
43-
"typescript": "^5.2.2",
43+
"ts-node": "^10.9.2",
44+
"typescript": "^5.3.3",
4445
"wireit": "^0.10.0"
4546
},
4647
"engines": {
@@ -135,7 +136,7 @@
135136
]
136137
},
137138
"test:deprecation-policy": {
138-
"command": "ts-node \"./bin/dev.js\" snapshot:compare",
139+
"command": "node --loader ts-node/esm --no-warnings=ExperimentalWarning \"./bin/dev.js\" snapshot:compare",
139140
"files": [
140141
"src/**/*.ts"
141142
],

src/commands/org/api.ts

+23-14
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { readFile } from 'node:fs/promises';
22
import { EOL } from 'node:os';
3-
import got, { Headers, Method } from 'got';
3+
import got, { Headers } from 'got';
44
import chalk from 'chalk';
55
import { ProxyAgent } from 'proxy-agent';
66
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
77
import { SfError, Org } from '@salesforce/core';
88
import { Args, ux } from '@oclif/core';
9+
import { readStdin } from '@oclif/core/lib/parser/parse.js';
910

1011
export class OrgApi extends SfCommand<void> {
1112
public static readonly summary =
@@ -31,17 +32,14 @@ export class OrgApi extends SfCommand<void> {
3132
// summary is already set in the org flag.
3233
// eslint-disable-next-line sf-plugin/flag-summary
3334
'target-org': Flags.requiredOrg({
34-
// TODO: this is already set in the org flag but getting a wrong type if not set here.
35-
// Fix flag types in oclif.
36-
required: true,
3735
helpValue: 'username',
3836
}),
3937
include: Flags.boolean({
4038
char: 'i',
4139
summary: 'Include HTTP response status and headers in the output.',
4240
default: false,
4341
}),
44-
method: Flags.custom<Method>({
42+
method: Flags.option({
4543
options: [
4644
'GET',
4745
'POST',
@@ -51,7 +49,7 @@ export class OrgApi extends SfCommand<void> {
5149
'DELETE',
5250
'OPTIONS',
5351
'TRACE',
54-
],
52+
] as const,
5553
summary: 'The HTTP method for the request.',
5654
char: 'X',
5755
default: 'GET',
@@ -62,8 +60,23 @@ export class OrgApi extends SfCommand<void> {
6260
char: 'H',
6361
multiple: true,
6462
}),
65-
body: Flags.file({
66-
summary: 'The file to use as the body for the request.',
63+
body: Flags.string({
64+
summary:
65+
'The file to use as the body for the request (use "-" to read from standard input).',
66+
parse: async (input) => {
67+
if (input === '-') {
68+
const body = await readStdin();
69+
if (body) {
70+
return body.trim();
71+
} else {
72+
throw new Error(
73+
'Unable to read body: `-` was provided but STDIN is empty.',
74+
);
75+
}
76+
} else {
77+
return readFile(input, 'utf8');
78+
}
79+
},
6780
helpValue: 'file',
6881
}),
6982
};
@@ -113,13 +126,9 @@ export class OrgApi extends SfCommand<void> {
113126
}`,
114127
...(flags.header ? OrgApi.getHeaders(flags.header) : {}),
115128
},
116-
body:
117-
flags.method === 'GET'
118-
? undefined
119-
: flags.body
120-
? await readFile(flags.body)
121-
: undefined,
129+
body: flags.method === 'GET' ? undefined : flags.body,
122130
throwHttpErrors: false,
131+
followRedirect: false,
123132
});
124133

125134
// Print HTTP response status and headers.

test/commands/org/api.test.ts

+29-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import nock = require('nock');
2-
import { TestContext, MockTestOrgData } from '@salesforce/core/lib/testSetup.js';
2+
import {
3+
TestContext,
4+
MockTestOrgData,
5+
} from '@salesforce/core/lib/testSetup.js';
36
import { SfError } from '@salesforce/core';
47
import { expect } from 'chai';
58
import stripAnsi from 'strip-ansi';
@@ -13,6 +16,13 @@ describe('org api', () => {
1316

1417
let stdoutSpy: sinon.SinonSpy;
1518

19+
const orgLimitsResponse = {
20+
ActiveScratchOrgs: {
21+
Max: 200,
22+
Remaining: 199,
23+
},
24+
};
25+
1626
beforeEach(async () => {
1727
await $$.stubAuths(testOrg);
1828
stdoutSpy = $$.SANDBOX.stub(process.stdout, 'write');
@@ -23,13 +33,6 @@ describe('org api', () => {
2333
});
2434

2535
it('should request org limits and default to "GET" HTTP method', async () => {
26-
const orgLimitsResponse = {
27-
ActiveScratchOrgs: {
28-
Max: 200,
29-
Remaining: 199,
30-
},
31-
};
32-
3336
nock(testOrg.instanceUrl)
3437
.get('/services/data/v56.0/limits')
3538
.reply(200, orgLimitsResponse);
@@ -98,4 +101,22 @@ describe('org api', () => {
98101
);
99102
}
100103
});
104+
105+
it('should not follow redirects', async () => {
106+
nock(testOrg.instanceUrl)
107+
.get('/services/data/v56.0/limites')
108+
.reply(301, orgLimitsResponse, {
109+
location: `${testOrg.instanceUrl}/services/data/v56.0/limits`,
110+
});
111+
112+
await OrgApi.run([
113+
'services/data/v56.0/limites',
114+
'--target-org',
115+
116+
]);
117+
118+
const output = stripAnsi(stdoutSpy.args.flat().join(''));
119+
120+
expect(JSON.parse(output)).to.deep.equal(orgLimitsResponse);
121+
});
101122
});

0 commit comments

Comments
 (0)