CLI Core for TIBCO Cloud™
If you want to develop a CLI plugin for TIBCO Cloud™, use this package to get some features OOTB.
Using this package won't only give features but will help in maintaining a unified, consistent user experience for the end user.
Table of Contents
- Get started
- Command Class
- BaseCommand
- TCBaseCommand
- Logging to the terminal
- Error Handling
- HTTP Requests
- HTTPRequest
- TCRequest
- UX
- API Documentation
- Issue
- License
Get started
-
Since this CLI is based on the oclif framework, check out oclif and then install it on your machine.
npm install -g oclif@1.18.0
-
Install TIBCO Cloud CLI on your machine.
-
Generate the CLI plugin and install CLI core package.
oclif plugin <your plugin name>
cd <your plugin name>
npm i @tibco-software/cic-cli-core
-
Create topic (group) for your commands.
oclif command <Your topic name>:hello
-
Link your plugin to the TIBCO Cloud CLI.
tibco plugins:link <absolute path of your plugin>
-
Test your hello command.
tibco <your topic name>:hello
NOTE: The TIBCO Cloud CLI works on oclif 1.18.x version. Please consider using the same version of oclif while developing the plugin.
Commands
BaseCommand
Extend this class when you want to create any command which does something on the machine locally or when you want to communicate with third party tools. (Not TIBCO Cloud)
import { flags } from '@oclif/command';
import { BaseCommand } from '@tibco-software/cic-cli-core';
export default class GenDirCommand extends BaseCommand {
static description = 'Creates directory';
static examples = [`tibco fs:gen-dir --path . mydir`];
static flags: flags.input<any> & typeof BaseCommand.flags = {
...BaseCommand.flags,
path: flags.string({
char: 'p',
description: 'Path other the cwd',
required: false,
}),
};
static args = [{ name: 'dirName' }];
async init() {
await super.init();
}
async run() {
let httpReq = this.getHTTPRequest();
}
async catch(err: Error) {
return super.catch(err);
}
async finally(err: Error) {
return super.finally(err);
}
}
TCBaseCommand
Extend this class when you want to make API requests to TIBCO Cloud and manage profile configurations.
NOTE: TCBaseCommand extends BaseCommand
import { flags } from '@oclif/command';
import { TCBaseCommand } from '@tibco-software/cic-cli-core';
export default class ShowAppsCommand extends TCBaseCommand {
static description = 'Show Apps on TIBCO Cloud';
static examples = [`tibco tc:show-apps --all`];
static flags: flags.input<any> & typeof TCBaseCommand.flags = {
...TCBaseCommand.flags,
all: flags.boolean({
char: 'a',
description: 'show other owner apps',
required: false,
}),
};
async init() {
await super.init();
}
async run() {
let httpReq = this.getHTTPRequest();
let tcReq = this.getTCRequest();
let config = this.getProfileConfig();
this.saveProfileConfig(config);
this.reloadProfileConfig();
}
async catch(err: Error) {
return super.catch(err);
}
async finally(err: Error) {
return super.finally(err);
}
}
Alternatively you can also import Requests module from package
import { HTTPRequest, TCRequest } from '@tibco-software/cic-cli-core';
let req = new HTTPRequest('commandName', 'pluginName');
let tcReq = new TCRequest(profile, 'clientId', 'commandName', 'pluginName');
Logging to terminal
If you are in Command class then simply use OOTB methods of logging.
async run() {
this.log("Hello world");
this.warn("File is getting replaced");
this.debug("File not found, generating file");
this.error("Failed to open app");
}
In case, you are not in Command class then use Logger
import { Logger, CLIBaseError } from '@tibco-software/cic-cli-core';
Logger.log('Hello world');
Logger.warn('File is getting replaced');
Logger.debug('File not found, generating file');
throw new CLIBaseError('Failed to open an app');
let debug = Logger.extendDebugger("interceptor");
debug("My debugging namespace");
Error handling
Use CLIBaseError
class to throw errors. These errors are friendly and won't show a traceback unless debugging is enabled with env variable DEBUG=*
or CLI_NAME_DEBUG=1
.
You can extend CLIBaseError
class to create more error classes.
For e.g. we created HTTPError class
export class HTTPError extends CLIBaseError {
httpCode?: string | number;
httpHeaders?: { [index: string]: any };
httpResponse?: any;
constructor(message: string, httpCode?: string | number, httpResponse?: any, httpHeaders?: { [index: string]: any }) {
super(message);
this.httpCode = httpCode;
this.httpHeaders = httpHeaders;
this.httpResponse = httpResponse;
this.name = 'HTTPError';
}
}
HTTP Requests
Requests module adds a wrapper on top of axios to simplify http requests
It will add some basic info to http requests -
- add proxies if needed
- User-Agent header
- timeout (30s) - If the request takes longer than
timeout
, the request will be aborted. - connection (close)
Use HTTPRequest
class to make requests to any third party tools.
Use TCRequest
class while making request to TIBCO Cloud. TCRequest
will take care of tokens, renew token when expired, add regions in endpoint based on the profile.
Check axios options for options parameter in all request methods
HTTPRequest
doRequest
import {HTTPRequest} from '@tibco-software/cic-cli-core';
...
let req = new HTTPRequest('cmdName', 'pluginName');
let resp = await req.doRequest('https://api.mydomain.com/v1/apps/');
let resp2 = await req.doRequest('/v1/apps/scale', {baseURL: 'https://api.mydomain.com'}, {name:'myapp', count:2});
let resp3 = await req.doRequest('https://api.mydomain.com/v1/apps/replace', {method: 'PUT'}, {});
Logger.log(resp.body);
Logger.log(resp.headers);
Logger.log(resp.statusCode);
doRequest
will throw error (instance of HTTPError
) if response statusCode is in 4xx or 5xx.
getAxiosClient
Use this method if you want to make an http request instead of cli-core package making it for you
It will return an axios
instance with some basic options added to it.
For axios
instance methods checkout this part of axios methods
.
let req = new HTTPRequest('cmdName', 'PluginName');
let client = req.getAxiosClient();
let axiosResp = await client.get('https://api.mydomain.com/v1/apps/', {});
Logger.log(axiosResp.data);
Logger.log(axiosResp.status);
Logger.log(axiosResp.headers);
upload
Use this method to upload files when required Content-Type
is multipart/form-data
.
Pass data parameter in {key: value}
format and path of a file as value with a prefix file://
.
You can also show progress bar on terminal by just passing last parameter true
to the function.
It is recommended to show progress bar when file size more than 64KB.
let req = new HTTPRequest('cmdName', 'PluginName');
let data = {
artifact: 'file:///Users/foo/Desktop/artifacts.json',
app: 'file:///Users/foo/Desktop/myapp.tgz',
name: 'MyApp',
};
let resp = await req.upload('https://api.mydomain.com/v1/apps/new', data, {}, true);
download
Use this method in case you need to download files.
You can also show progress bar on terminal by just passing last parameter true
ot the function.
let req = new HTTPRequest('cmdName', 'PluginName');
let isDownloaded = await req.download('https://api.mydomain.com/v1/apps/pull', '/Users/foo/Desktop/', {}, true);
if (isDownloaded === true) {
Logger.log('File downloaded successfully');
}
TCRequest
This module uses HTTPRequest
to make calls to TIBCO Cloud.
It takes care of tokens, renews them if expired before making a call. It also adds region to the endpoint based on current profile of the end user.
https://api.cloud.tibco.com
is considered as a base URL when only path is passed to the url parameter of functions.
doRequest
import { TCRequest } from '@tibco-software/cic-cli-core';
let tcReq = new TCRequest(profile, 'clientId', 'commandName', 'pluginName');
let res = await tcReq.doRequest('/v1/apps/');
await tcReq.doRequest('/v1/apps');
await tcReq.doRequest('/userinfo', { baseURL: 'https://dev.tibco.com' });
await tcReq.doRequest('/userinfo', { baseURL: 'https://dev.tibco.com' });
await tcReq.doRequest('/v1/apps/', {} as AxiosRequestConfig, { otherOrgs: true });
getAxiosClient
Same as HTTPRequest's getAxiosClient, this one adds region to url, token to the header and returns axios instance
upload and download
Both methods are same as HTTPRequest's methods, these only adds region to url, token to the header and before making requests
UX
With ux module you can create different prompts, spinner, tables (resized according to the terminal size) and open app from terminal.
prompts
import { ux } from '@tibco-software/cic-cli-core';
await ux.prompt('Enter your name ?');
await ux.prompt('Enter your password?', 'password');
await ux.promptChoices('Total countries in the world', ['123', '195', '165', '187']);
await ux.promptMultiSelectChoices('Select your favourite ice cream flavour', [
{ name: 'Vanilla', value: 'vanilla' },
{ name: 'Chocolate', value: 'chocolate' },
{ name: 'Butter Pecan', value: 'butter-pecan' },
{ name: 'Pistachio', value: 'pistachio' },
{ name: 'Strawberry', value: 'strawberry' },
{ name: 'Other', value: 'other' },
]);
await ux.promptChoicesWithSearch('Select you favourite colors', [
'Black',
'Green',
'Yellow',
'Red',
'White',
'Silver',
'Maroon',
'Grey',
'Orange',
'Brown',
'Blue',
'Cyan',
'Magenta',
]);

It is always better to configure flag for the prompts. This helps when user wants to automates their tasks using commands and can pass answers in the flags to the prompts.
You can directly pass flags as answer to prompt method and they won't prompt question to the terminal.
...
static flags = {
name: flags.string({required: false})
}
async run() {
const { flags } = this.parse(MyCommand);
await ux.prompt("Enter application name", "input", flags.name);
}
spinner
async run() {
const spinner = await ux.spinner();
spinner.start("Downloading executable");
await this.sleep(3000);
spinner.succeed("Downloaded");
spinner.start("Installing application");
await this.sleep(3000);
spinner.warn("No permission to register an app");
await this.sleep(3000);
spinner.start("Retrying app installation");
await this.sleep(3000);
spinner.fail("Failed");
}
sleep(ms: number) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms);
});

progress bar
Give format of progress bar which will contain either inbuilt tokens or custom tokens.
For e.g: :bar :percent :myCustomToken
Inbuilt tokens
| | |
---|
:bar | the progress bar itself | |
:current | current tick number | |
:total | total ticks | |
:elapsed | time elapsed in seconds | |
:percent | completion percentage | |
:eta | estimated completion time in seconds | |
:rate | rate of ticks per second | |
...
async run() {
let bar = await ux.getProgressBar(":bar :percent | :myCustomToken", 100);
for (let i = 0; i < 100; i++) {
await this.sleep(200);
bar.tick(1, { myCustomToken: `Printing custom token ${i}`});
}
}
sleep(ms: number) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms);
});
}

showTable
Table gets adjusted as per the width of the terminal perhaps if terminal width is too narrow then content may overlap.
let colors = [
{
color: "red",
value: "#f00",
},
{
color: "green",
value: "#0f0",
},
...
];
await ux.showTable(colors, 'Color codes');
open
This is just a wrapper method around package open
API documentation
To see in details API documentation, check it out here
Issues
In case you find any issue, raise it here via the "Issues" tab of this GitHub repository.
License
BSD