Skip to content

Commit

Permalink
feat: add api request graphql command, UTs, NUTs
Browse files Browse the repository at this point in the history
* feat: add api request graphql command, UTs, NUTs

* test: add logging

* refactor: move test files, local project

* chore: populate force-app

* chore: remove logging

* chore: update --body flag to read file/stdin/value

* chore: share flags/methods

* chore: fix shared method, got method

* chore: --api-version

* chore: example/classes

* chore: add .gitkeep in sample proj

* test: add NUT with --body and directly passing in grapql

---------

Co-authored-by: mshanemc <shane.mclaughlin@salesforce.com>
  • Loading branch information
WillieRuemmele and mshanemc authored Aug 22, 2024
1 parent 86fac95 commit 04adffe
Show file tree
Hide file tree
Showing 19 changed files with 601 additions and 163 deletions.
164 changes: 88 additions & 76 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,59 +59,119 @@ sf plugins

<!-- commands -->

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

## `sf api request rest ENDPOINT`
## `sf api request graphql`

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

````
USAGE
$ sf api request rest ENDPOINT -o <value> [--flags-dir <value>] [--api-version <value>] [-i | -S Example:
report.xlsx] [-X GET|POST|PUT|PATCH|HEAD|DELETE|OPTIONS|TRACE] [-H key:value...] [--body file]
ARGUMENTS
ENDPOINT Salesforce API endpoint
$ sf api request graphql -o <value> --body file [--json] [--flags-dir <value>] [-S Example: report.xlsx | -i]
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=<value> (required) Username or alias of the target org. Not required if the
`target-org` configuration variable is already set.
--api-version=<value> Override the api version used for api requests made by this command
--body=file File to use as the body for the request. Specify "-" to read from standard
input; specify "" for an empty body.
--body=file (required) File or content with GraphQL statement. Specify "-" to read from
standard input.
GLOBAL FLAGS
--flags-dir=<value> Import flag values from a directory.
--json Format output as json.
DESCRIPTION
Execute GraphQL statements
Run any valid GraphQL statement via the /graphql
[API](https://developer.salesforce.com/docs/platform/graphql/guide/graphql-about.html)
EXAMPLES
- List information about limits in the org with alias "my-org":
sf api request rest 'limits' --target-org my-org
- List all endpoints
sf api request rest '/'
- Get the response in XML format by specifying the "Accept" HTTP header:
sf api request rest 'limits' --target-org my-org --header 'Accept: application/xml'
- POST to create an Account object
sf api request rest 'sobjects/account' --body "{\"Name\" : \"Account from REST API\",\"ShippingCity\" : \"Boise\"}" --method POST
- or with a file 'info.json' containing
```json
{
"Name": "Demo",
"ShippingCity": "Boise"
- Runs the graphql query directly via the command line
sf api request graphql --body '{ "query": "query accounts { uiapi { query { Account { edges { node { Id \n Name { value } } } } } } }" }'
- Runs a mutation to create an Account, with an `example.txt` file, containing
```text
mutation AccountExample{
uiapi {
AccountCreate(input: {
Account: {
Name: "Trailblazer Express"
}
}) {
Record {
Id
Name {
value
}
}
}
}
}
````

$ sf api request graphql --body example.txt
will create a new account returning specified fields (Id, Name)

```
_See code: [src/commands/api/request/graphql.ts](https://github.com/salesforcecli/plugin-api/blob/v1.1.0/src/commands/api/request/graphql.ts)_
## `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 <value> [--flags-dir <value>] [--api-version <value>] [-i | -S Example:
report.xlsx] [-X GET|POST|PUT|PATCH|HEAD|DELETE|OPTIONS|TRACE] [-H key:value...] [--body file]

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=<value> (required) Username or alias of the target org. Not required if the
`target-org` configuration variable is already set.
--api-version=<value> Override the api version used for api requests made by this command
--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 api request rest 'limits' --target-org my-org
- List all endpoints
sf api request rest '/'
- Get the response in XML format by specifying the "Accept" HTTP header:
sf api request rest 'limits' --target-org my-org --header 'Accept: application/xml'
- POST to create an Account object
sf api request rest 'sobjects/account' --body "{\"Name\" : \"Account from REST API\",\"ShippingCity\" : \"Boise\"}" --method POST
- or with a file 'info.json' containing

```json
{
"Name": "Demo",
"ShippingCity": "Boise"
}
```

$ sf api request rest 'sobjects/account' --body info.json --method POST

- Update object
sf api request rest 'sobjects/account/<Account ID>' --body "{\"BillingCity\": \"San Francisco\"}" --method PATCH

```
_See code: [src/commands/api/request/rest.ts](https://github.com/salesforcecli/plugin-api/blob/0.1.0/src/commands/api/request/rest.ts)_
_See code: [src/commands/api/request/rest.ts](https://github.com/salesforcecli/plugin-api/blob/v1.1.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": ["api-version", "body", "flags-dir", "include", "json", "stream-to-file", "target-org"],
"plugin": "@salesforce/plugin-api"
},
{
"alias": [],
"command": "api:request:rest",
Expand Down
46 changes: 46 additions & 0 deletions messages/graphql.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# summary

Execute GraphQL statements

# description

Run any valid GraphQL statement via the /graphql [API](https://developer.salesforce.com/docs/platform/graphql/guide/graphql-about.html)

# examples

- Runs the graphql query directly via the command line

<%= config.bin %> <%= command.id %> --body "query accounts { uiapi { query { Account { edges { node { Id \n Name { value } } } } } } }"

- Runs a mutation to create an Account, with an `example.txt` file, containing

```text
mutation AccountExample{
uiapi {
AccountCreate(input: {
Account: {
Name: "Trailblazer Express"
}
}) {
Record {
Id
Name {
value
}
}
}
}
}
```

<%= config.bin %> <%= command.id %> --body example.txt

will create a new account returning specified fields (Id, Name)

# flags.header.summary

HTTP header in "key:value" format.

# flags.body.summary

File or content with GraphQL statement. Specify "-" to read from standard input.
8 changes: 0 additions & 8 deletions messages/rest.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ Make an authenticated HTTP request to Salesforce REST API and print the response

<%= config.bin %> <%= command.id %> 'sobjects/account/<Account ID>' --body "{\"BillingCity\": \"San Francisco\"}" --method PATCH

# flags.include.summary

Include the HTTP response status and headers in the output.

# flags.method.summary

HTTP method for the request.
Expand All @@ -47,10 +43,6 @@ HTTP method for the request.

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; specify "" for an empty body.
7 changes: 7 additions & 0 deletions messages/shared.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# flags.include.summary

Include the HTTP response status and headers in the output.

# flags.stream-to-file.summary

Stream responses to a file.
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
63 changes: 63 additions & 0 deletions src/commands/api/request/graphql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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 from 'node:fs';
import * as os from 'node:os';
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages, Org, SFDX_HTTP_HEADERS } from '@salesforce/core';
import { ProxyAgent } from 'proxy-agent';
import { includeFlag, sendAndPrintRequest, streamToFileFlag } from '../../../shared/shared.js';

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(),
'api-version': Flags.orgApiVersion(),
'stream-to-file': streamToFileFlag,
include: includeFlag,
body: Flags.string({
summary: messages.getMessage('flags.body.summary'),
allowStdin: true,
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'];
const apiVersion = flags['api-version'] ?? (await org.retrieveMaxApiVersion());
const body = `{"query":"${(fs.existsSync(flags.body) ? fs.readFileSync(flags.body, 'utf8') : flags.body)
.replaceAll(os.EOL, '\\n')
.replaceAll('"', '\\"')}"}`;
const url = new URL(`${org.getField<string>(Org.Fields.INSTANCE_URL)}/services/data/v${apiVersion}/graphql`);

await org.refreshAuth();

const options = {
agent: { https: new ProxyAgent() },
headers: {
...SFDX_HTTP_HEADERS,
Authorization: `Bearer ${org.getConnection(apiVersion).getConnectionOptions().accessToken!}`,
},
body,
throwHttpErrors: false,
followRedirect: false,
};

await sendAndPrintRequest({ streamFile, url, options, include: flags.include, this: this });
}
}
Loading

0 comments on commit 04adffe

Please sign in to comment.