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

W-16501195 Wr/api request graphql #6

Merged
merged 18 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
113 changes: 49 additions & 64 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,55 +1,7 @@
**NOTE: This template for sf plugins is not yet official. Please consult with the Platform CLI team before using this template.**

# plugin-template-sf

[![NPM](https://img.shields.io/npm/v/@salesforce/plugin-template-sf.svg?label=@salesforce/plugin-template-sf)](https://www.npmjs.com/package/@salesforce/plugin-template-sf) [![Downloads/week](https://img.shields.io/npm/dw/@salesforce/plugin-template-sf.svg)](https://npmjs.org/package/@salesforce/plugin-template-sf) [![License](https://img.shields.io/badge/License-BSD%203--Clause-brightgreen.svg)](https://raw.githubusercontent.com/salesforcecli/plugin-template-sf/main/LICENSE.txt)

## Using the template

This repository provides a template for creating a plugin for the Salesforce CLI. To convert this template to a working plugin:

1. Please get in touch with the Platform CLI team. We want to help you develop your plugin.
2. Generate your plugin:

```
sf plugins install dev
sf dev generate plugin

git init -b main
git add . && git commit -m "chore: initial commit"
```

3. Create your plugin's repo in the salesforcecli github org
4. When you're ready, replace the contents of this README with the information you want.

## Learn about `sf` plugins

Salesforce CLI plugins are based on the [oclif plugin framework](https://oclif.io/docs/introduction). Read the [plugin developer guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_plugins.meta/sfdx_cli_plugins/cli_plugins_architecture_sf_cli.htm) to learn about Salesforce CLI plugin development.

This repository contains a lot of additional scripts and tools to help with general Salesforce node development and enforce coding standards. You should familiarize yourself with some of the [node developer packages](#tooling) used by Salesforce. There is also a default circleci config using the [release management orb](https://github.com/forcedotcom/npm-release-management-orb) standards.

Additionally, there are some additional tests that the Salesforce CLI will enforce if this plugin is ever bundled with the CLI. These test are included by default under the `posttest` script and it is required to keep these tests active in your plugin if you plan to have it bundled.

### Tooling

- [@salesforce/core](https://github.com/forcedotcom/sfdx-core)
- [@salesforce/kit](https://github.com/forcedotcom/kit)
- [@salesforce/sf-plugins-core](https://github.com/salesforcecli/sf-plugins-core)
- [@salesforce/ts-types](https://github.com/forcedotcom/ts-types)
- [@salesforce/ts-sinon](https://github.com/forcedotcom/ts-sinon)
- [@salesforce/dev-config](https://github.com/forcedotcom/dev-config)
- [@salesforce/dev-scripts](https://github.com/forcedotcom/dev-scripts)

# Everything past here is only a suggestion as to what should be in your specific plugin's description

This plugin is bundled with the [Salesforce CLI](https://developer.salesforce.com/tools/sfdxcli). For more information on the CLI, read the [getting started guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_intro.htm).

We always recommend using the latest version of these commands bundled with the CLI, however, you can install a specific version or tag if needed.

## Install

```bash
sf plugins install @salesforce/plugin-template-sf@x.y.z
sf plugins install @salesforce/plugin-api
```

## Issues
Expand Down Expand Up @@ -107,43 +59,76 @@ sf plugins

<!-- commands -->

- [`sf hello world`](#sf-hello-world)
- [`sf api request graphql`](#sf-api-request-graphql)
- [`sf api request rest ENDPOINT`](#sf-api-request-rest-endpoint)

## `sf hello world`
## `sf api request graphql`

Say hello.
Summary of a command.

```
USAGE
$ sf hello world [--json] [--flags-dir <value>] [-n <value>]
$ sf api request graphql -o <value> --body file [--json] [--flags-dir <value>] [-S Example: report.xlsx | -i]

FLAGS
-n, --name=<value> [default: World] The name of the person you'd like to say hello to.
-S, --stream-to-file=Example: report.xlsx Stream responses to a file.
-i, --include Include the HTTP response status and headers in the output.
-o, --target-org=<value> (required) Username or alias of the target org. Not required if the
`target-org` configuration variable is already set.
--body=file (required) File to use as the body for the request. Specify "-" to read
from standard input.

GLOBAL FLAGS
--flags-dir=<value> Import flag values from a directory.
--json Format output as json.

DESCRIPTION
Say hello.
Summary of a command.

Say hello either to the world or someone you know.
More information about a command. Don't repeat the summary.

EXAMPLES
Say hello to the world:
$ sf api request graphql
```

_See code: [src/commands/api/request/graphql.ts](https://github.com/salesforcecli/plugin-api/blob/v1.0.0/src/commands/api/request/graphql.ts)_

$ sf hello world
## `sf api request rest ENDPOINT`

Make an authenticated HTTP request to Salesforce REST API and print the response.

```
USAGE
$ sf api request rest ENDPOINT -o username [--flags-dir <value>] [-i | -S Example: report.xlsx] [-X
GET|POST|PUT|PATCH|HEAD|DELETE|OPTIONS|TRACE] [-H key:value...] [--body file]

Say hello to someone you know:
ARGUMENTS
ENDPOINT Salesforce API endpoint

FLAGS
-H, --header=key:value... HTTP header in "key:value" format.
-S, --stream-to-file=Example: report.xlsx Stream responses to a file.
-X, --method=<option> [default: GET] HTTP method for the request.
<options: GET|POST|PUT|PATCH|HEAD|DELETE|OPTIONS|TRACE>
-i, --include Include the HTTP response status and headers in the output.
-o, --target-org=username (required) Username or alias of the target org. Not required if the
`target-org` configuration variable is already set.
--body=file File to use as the body for the request. Specify "-" to read from standard
input; specify "" for an empty body.

GLOBAL FLAGS
--flags-dir=<value> Import flag values from a directory.

EXAMPLES
List information about limits in the org with alias "my-org":

$ sf hello world --name Astro
$ sf api request rest 'services/data/v56.0/limits' --target-org my-org

FLAG DESCRIPTIONS
-n, --name=<value> The name of the person you'd like to say hello to.
Get the response in XML format by specifying the "Accept" HTTP header:

This person can be anyone in the world!
$ sf api request rest 'services/data/v56.0/limits' --target-org my-org --header 'Accept: application/xml',
```

_See code: [src/commands/hello/world.ts](https://github.com/salesforcecli/plugin-template-sf/blob/1.1.14/src/commands/hello/world.ts)_
_See code: [src/commands/api/request/rest.ts](https://github.com/salesforcecli/plugin-api/blob/v1.0.0/src/commands/api/request/rest.ts)_

<!-- commandsstop -->
8 changes: 8 additions & 0 deletions command-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
[
{
"alias": [],
"command": "api:request:graphql",
"flagAliases": [],
"flagChars": ["S", "i", "o"],
"flags": ["body", "flags-dir", "include", "json", "stream-to-file", "target-org"],
"plugin": "@salesforce/plugin-api"
},
{
"alias": [],
"command": "api:request:rest",
Expand Down
35 changes: 35 additions & 0 deletions messages/graphql.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# summary

Summary of a command.

# description

More information about a command. Don't repeat the summary.

# flags.name.summary

Description of a flag.

# flags.name.description

More information about a flag. Don't repeat the summary.

# examples

- <%= config.bin %> <%= command.id %>

# flags.include.summary

Include the HTTP response status and headers in the output.

# flags.header.summary

HTTP header in "key:value" format.

# flags.stream-to-file.summary

Stream responses to a file.

# flags.body.summary

File to use as the body for the request. Specify "-" to read from standard input.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@salesforce/core": "^8.4.0",
"@salesforce/kit": "^3.2.1",
"@salesforce/sf-plugins-core": "^11.3.2",
"@salesforce/ts-types": "^2.0.12",
"ansis": "^3.3.2",
"got": "^13.0.0",
"proxy-agent": "^6.4.0"
Expand Down
109 changes: 109 additions & 0 deletions src/commands/api/request/graphql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright (c) 2023, 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 fs, { createWriteStream } from 'node:fs';
import { EOL } from 'node:os';
import * as os from 'node:os';
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages, Org, SfError } from '@salesforce/core';
import { ProxyAgent } from 'proxy-agent';
import ansis from 'ansis';
import got from 'got';
import type { AnyJson } from '@salesforce/ts-types';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-api', 'graphql');

export default class Graphql extends SfCommand<void> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');
public static readonly state = 'beta';

public static readonly flags = {
'target-org': Flags.requiredOrg(),
'stream-to-file': Flags.string({
summary: messages.getMessage('flags.stream-to-file.summary'),
helpValue: 'Example: report.xlsx',
char: 'S',
exclusive: ['include'],
}),
include: Flags.boolean({
char: 'i',
summary: messages.getMessage('flags.include.summary'),
default: false,
exclusive: ['stream-to-file'],
}),
body: Flags.string({
summary: messages.getMessage('flags.body.summary'),
helpValue: 'file',
required: true,
}),
};

public async run(): Promise<void> {
const { flags } = await this.parse(Graphql);

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

await org.refreshAuth();
const apiVersion = await org.retrieveMaxApiVersion();

const url = `${org.getField<string>(Org.Fields.INSTANCE_URL)}/services/data/v${apiVersion}/graphql`;

const options = {
agent: { https: new ProxyAgent() },
headers: {
'Content-type': 'application/json',
Authorization: `Bearer ${org.getConnection(apiVersion).getConnectionOptions().accessToken!}`,
...{},
},
body: `{"query":"${fs.readFileSync(flags.body, 'utf8').replaceAll(os.EOL, '\\n')}", "variables": {}}`,
throwHttpErrors: false,
followRedirect: false,
};

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

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.post(url, options);

// 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 += `${ansis.blue.bold(header)}: ${res.headers[header] as string}${EOL}`;
}
this.log(httpInfo);
}

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

if (res.statusCode >= 400) {
process.exitCode = 1;
}
}
}
}
2 changes: 1 addition & 1 deletion src/commands/api/request/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export class Rest extends SfCommand<void> {
}),
};

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

for (const header of keyValPair) {
Expand Down
Loading