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

feat: add env api command #1

Merged
merged 5 commits into from
Dec 1, 2022
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 .husky/pre-push
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

yarn build && yarn test
yarn build
4 changes: 1 addition & 3 deletions .sfdevrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
{
"test": {
"testsPath": "test/**/*.test.ts"
Copy link
Owner Author

Choose a reason for hiding this comment

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

TODO: re-enable the tests scripts once I add NUTs.
I skipped this as I'll need to set up some hub credentials for this repo.

}
"exclude-scripts": ["docs", "pretest", "test", "lint"]
}
4 changes: 2 additions & 2 deletions bin/dev
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ const project = path.join(__dirname, '..', 'tsconfig.json');
// In dev mode -> use ts-node and dev plugins
process.env.NODE_ENV = 'development';

require('ts-node').register({ project });
// oclif.settings.tsconfigPath = project;
// Enable SWC for faster typescript compiling
require('ts-node').register({ project, swc: true });

// In dev mode, always show stack traces
const g = (global.oclif = global.oclif || {});
Expand Down
8 changes: 8 additions & 0 deletions command-snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{
"command": "env:api",
"plugin": "@cristiand391/sf-plugin-api",
"flags": ["body", "header", "include", "method", "target-org"],
"alias": []
}
]
44 changes: 44 additions & 0 deletions messages/env.api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# summary

Makes an authenticated HTTP request to the Salesforce REST API and prints the response.
cristiand391 marked this conversation as resolved.
Show resolved Hide resolved

# description

You must specify a Salesforce org to use, either with the --target-org flag or by setting your default org with the `target-org` configuration variable.

Read the Salesforce REST API developer guide at:
https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_rest.htm

# examples

- List information about limits in your org:

<%= config.bin %> <%= command.id %> 'services/data/v56.0/limits' --target-org my-org

- Get response in XML format by specifying the "Accept" HTTP header:

<%= config.bin %> <%= command.id %> 'services/data/v56.0/limits' --target-org my-org --header 'Accept: application/xml'

# flags.target-org.summary

Login username or alias for the target org.

# flags.method.summary

The HTTP method for the request.

# flags.include.summary

Include HTTP response status and headers in the output.

# flags.header.summary

HTTP header in "key:value" format.

# flags.body.summary

The file to use as the body for the request.

# errors.invalid-http-header

Failed to parse HTTP header: "%s".
39 changes: 17 additions & 22 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
{
"name": "@salesforce/plugin-api",
"name": "@cristiand391/sf-plugin-api",
"description": "Plugin to interact with the Salesforce APIs",
"version": "0.0.1",
"author": "Salesforce",
"bugs": "https://github.com/forcedotcom/cli/issues",
"author": {
"name": "Cristian Dominguez",
"url": "https://github.com/cristiand391"
},
"bugs": "https://github.com/cristiand391/sf-plugin-api/issues",
"dependencies": {
"@oclif/core": "^1.18.0",
"@salesforce/core": "^3.31.17",
"@salesforce/kit": "^1.7.1",
"@salesforce/sf-plugins-core": "^1.17.0",
"tslib": "^2"
"chalk": "^4",
"got": "11.8.5",
"proxy-agent": "^5.0.0",
"proxy-from-env": "^1.1.0"
},
"devDependencies": {
"@oclif/plugin-command-snapshot": "^3.2.5",
"@oclif/test": "^2.2.4",
"@salesforce/cli-plugins-testkit": "^3.2.3",
"@salesforce/dev-config": "^3.1.0",
"@salesforce/dev-scripts": "^3.1.0",
"@salesforce/plugin-command-reference": "^2.2.8",
"@salesforce/prettier-config": "^0.0.2",
"@salesforce/ts-sinon": "1.4.0",
"@swc/core": "^1.3.11",
"@types/proxy-from-env": "^1.0.1",
"@typescript-eslint/eslint-plugin": "^5.40.0",
"@typescript-eslint/parser": "^5.40.1",
"chai": "^4.3.6",
Expand Down Expand Up @@ -52,7 +57,7 @@
"/oclif.manifest.json",
"/schemas"
],
"homepage": "https://github.com/salesforcecli/plugin-api",
"homepage": "https://github.com/cristiand391/sf-plugin-api",
"keywords": [
"force",
"salesforce",
Expand All @@ -70,32 +75,22 @@
"topicSeparator": " ",
"devPlugins": [
"@oclif/plugin-help",
"@oclif/plugin-command-snapshot",
"@salesforce/plugin-command-reference"
],
"topics": {
"hello": {
"description": "Commands to say hello."
}
}
"@oclif/plugin-command-snapshot"
]
},
"repository": "salesforcecli/plugin-api",
"repository": "https://github.com/cristiand391/sf-plugin-api",
"scripts": {
"build": "sf-build",
"clean": "sf-clean",
"clean-all": "sf-clean all",
"clean:lib": "shx rm -rf lib && shx rm -rf coverage && shx rm -rf .nyc_output && shx rm -f oclif.manifest.json",
"compile": "sf-compile",
"docs": "sf-docs",
"format": "sf-format",
"lint": "sf-lint",
"lint": "eslint \"src/**/*.ts\"",
Copy link
Owner Author

Choose a reason for hiding this comment

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

sf-lint will fail as there's no test folder yet, replaced it to only look at the src folder.

"postpack": "shx rm -f oclif.manifest.json",
"posttest": "yarn lint && yarn test:deprecation-policy && yarn test:json-schema && yarn test:command-reference",
"prepack": "sf-prepack",
"prepare": "sf-install",
"pretest": "sf-compile-test",
"test": "sf-test",
"test:command-reference": "./bin/dev commandreference:generate --erroronwarnings",
"test:deprecation-policy": "./bin/dev snapshot:compare",
"test:json-schema": "./bin/dev schema:compare",
"test:nuts": "nyc mocha \"**/*.nut.ts\" --slow 4500 --timeout 600000 --parallel",
Expand Down
16 changes: 16 additions & 0 deletions schemas/env-api.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/EnvApiResult",
"definitions": {
"EnvApiResult": {
"type": "object",
"properties": {
"path": {
"type": "string"
}
},
"required": ["path"],
"additionalProperties": false
}
}
}
135 changes: 135 additions & 0 deletions src/commands/env/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { readFile } from 'node:fs/promises';
import got, { Headers, Method } from 'got';
import * as chalk from 'chalk';
import * as ProxyAgent from 'proxy-agent';
import { getProxyForUrl } from 'proxy-from-env';
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { SfError, Messages, Org } from '@salesforce/core';
import { CliUx } from '@oclif/core';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.load('@cristiand391/sf-plugin-api', 'env.api', [
'summary',
'description',
'examples',
'flags.target-org.summary',
'flags.method.summary',
'flags.include.summary',
'flags.header.summary',
'flags.body.summary',
'errors.invalid-http-header',
]);

export default class EnvApi extends SfCommand<string> {
public static summary = messages.getMessage('summary');
public static description = messages.getMessage('description');
public static examples = messages.getMessages('examples');
public static enableJsonFlag = false;
public static flags = {
'target-org': Flags.requiredOrg({
summary: messages.getMessage('flags.target-org.summary'),
char: 'o',
}),
include: Flags.boolean({
Copy link
Contributor

Choose a reason for hiding this comment

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

include could have the short character i to match curl

Copy link
Owner Author

Choose a reason for hiding this comment

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

Added ✅

curl also has -I/--head which makes it only prints the headers and skip the response, I'll add a flag for that later, sometimes you are interested on the headers and if the response is big you need to scroll to the top.

char: 'i',
summary: messages.getMessage('flags.include.summary'),
default: false,
}),
method: Flags.enum<Method>({
options: ['GET', 'POST', 'PUT', 'PATCH', 'HEAD', 'DELETE', 'OPTIONS', 'TRACE'],
Copy link
Owner Author

Choose a reason for hiding this comment

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

got accepts lowercase HTTP methods but I decided to only support the uppercase ones only, the --help output for the command adds the possible options so having a bunch seems like a lot of noise.

summary: messages.getMessage('flags.method.summary'),
char: 'X',
default: 'GET',
}),
header: Flags.string({
summary: messages.getMessage('flags.header.summary'),
char: 'H',
multiple: true,
}),
body: Flags.file({
summary: messages.getMessage('flags.body.summary'),
}),
};

public static args = [
{
name: 'endpoint',
description: 'Salesforce API endpoint.',
required: true,
},
];

private static getHeaders(keyValPair: string[]): Headers {
const headers = {};

for (const header of keyValPair) {
const split = header.split(':');
if (split.length !== 2) {
throw new SfError(messages.getMessage('errors.invalid-http-header', [header]), '', [
'Make sure the header is in a "key:value" format, e.g. "Accept: application/json"',
]);
}
headers[split[0]] = split[1];
}

return headers;
}

public async run(): Promise<string> {
const { flags, args } = await this.parse(EnvApi);

let body: Buffer;

if (flags.body) {
body = await readFile(flags.body);
Copy link
Contributor

Choose a reason for hiding this comment

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

If body is passed, it would be convenient if the appropriate Content-Type header was added for you automatically.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Good idea, the command pass the body file to got as a Buffer so some headers are set:
https://github.com/sindresorhus/got/blob/main/documentation/2-options.md#body

to get the content-type of the buffer we could use this
https://github.com/sindresorhus/file-type/

I'll add a new issue for this 👍🏼

}

const org = flags['target-org'];

await org.refreshAuth();

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

const res = await got(url, {
agent: { https: ProxyAgent(getProxyForUrl(url)) },
method: flags.method,
headers: {
Authorization: `Bearer ${org.getConnection().getConnectionOptions().accessToken}`,
...(flags.header ? EnvApi.getHeaders(flags.header) : {}),
},
body: flags.method === 'GET' ? undefined : body,
throwHttpErrors: false,
});

// Print HTTP response status and headers.
if (flags.include) {
let httpInfo = `HTTP/${res.httpVersion} ${res.statusCode} \n`;

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

try {
// Try to pretty-print JSON response.
CliUx.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;
}

return res.body;
}
}
25 changes: 0 additions & 25 deletions test/.eslintrc.js

This file was deleted.

7 changes: 0 additions & 7 deletions test/tsconfig.json

This file was deleted.

3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"extends": "@salesforce/dev-config/tsconfig",
"compilerOptions": {
"outDir": "lib",
"rootDir": "src"
"rootDir": "src",
"allowSyntheticDefaultImports": true
},
"include": ["./src/**/*.ts"]
}
Loading