jsforce
Version:
Salesforce API Library for JavaScript
186 lines (166 loc) • 4.88 kB
text/typescript
import { exec } from 'child_process';
import { stripVTControlCharacters } from 'util';
import Connection from '../connection';
import { Registry, ConnectionConfig, ClientConfig } from './types';
import { Schema } from '../types';
type SfdxCommandOutput = {
status: number;
name?: string;
message?: string;
result?: any;
};
type SfdxOrgList = {
nonScratchOrgs: SfdxOrgInfo[];
scratchOrgs: SfdxOrgInfo[];
};
type SfdxOrgInfo = {
orgId: string;
accessToken: string;
instanceUrl: string;
loginUrl: string;
username: string;
clientId: string;
isDevHub: boolean;
connectedStatus: string;
lastUsed: string;
alias?: string;
};
function isNotNullOrUndefined<T>(v: T | null | undefined): v is T {
return v != null;
}
/**
*
*/
export class SfdxRegistry implements Registry {
_cliPath: string | undefined;
_orgList: Promise<SfdxOrgList> | undefined;
_orgInfoMap: { [name: string]: Promise<SfdxOrgInfo> } = {};
_defaultOrgInfo: Promise<SfdxOrgInfo> | undefined;
constructor({ cliPath }: { cliPath?: string }) {
this._cliPath = cliPath;
}
_createCommand(
command: string,
options: { [option: string]: any } = {},
args: string[] = [],
) {
return `${
this._cliPath ? this._cliPath + '/' : ''
}sfdx ${command} ${Object.keys(options)
.map(
(option) =>
`${option.length > 1 ? '--' : '-'}${option}${
options[option] != null ? ' ' + options[option] : ''
}`,
)
.join(' ')} --json ${args.join(' ')}`;
}
async _execCommand<T>(
command: string,
options: { [option: string]: any } = {},
args: string[] = [],
) {
const cmd = this._createCommand(command, options, args);
const buf = await new Promise<string>((resolve, reject) => {
exec(cmd, (err, ret) => {
if (err && !ret) {
reject(err);
} else {
resolve(ret);
}
});
});
const body = stripVTControlCharacters(buf.toString());
let ret: SfdxCommandOutput;
try {
ret = JSON.parse(body) as SfdxCommandOutput;
} catch (e) {
throw new Error(`Unexpectedd output from Sfdx cli: ${body}`);
}
if (ret.status === 0 && ret.result) {
return ret.result as T;
} else {
const err = new Error(ret.message as string);
err.name = ret.name as string;
throw err;
}
}
async _getOrgList() {
if (!this._orgList) {
this._orgList = this._execCommand<SfdxOrgList>('force:org:list');
}
return this._orgList;
}
async getConnectionNames() {
const { nonScratchOrgs, scratchOrgs } = await this._getOrgList();
return [
...nonScratchOrgs.map((o) => o.alias).filter(isNotNullOrUndefined),
...scratchOrgs.map((o) => o.alias).filter(isNotNullOrUndefined),
...nonScratchOrgs.map((o) => o.username),
...scratchOrgs.map((o) => o.username),
];
}
async getConnection<S extends Schema = Schema>(name?: string) {
const config = await this.getConnectionConfig(name);
return config ? new Connection<S>(config) : null;
}
async _getOrgInfo(username?: string) {
const options = username ? { u: username } : {};
if (!username || !this._orgInfoMap[username]) {
const pOrgInfo = this._execCommand<SfdxOrgInfo>(
'force:org:display',
options,
);
this._memoOrgInfo(pOrgInfo, username);
}
const orgInfo = username
? this._orgInfoMap[username]
: this._defaultOrgInfo;
if (!orgInfo) {
throw new Error('no orginfo found');
}
return orgInfo;
}
_memoOrgInfo(pOrgInfo: Promise<SfdxOrgInfo>, username?: string) {
const pOrgInfo_ = pOrgInfo.then((orgInfo) => {
this._orgInfoMap[orgInfo.username] = pOrgInfo_;
if (orgInfo.alias) {
this._orgInfoMap[orgInfo.alias] = pOrgInfo_;
}
return orgInfo;
});
if (username) {
this._orgInfoMap[username] = pOrgInfo_;
} else {
this._defaultOrgInfo = pOrgInfo_;
}
}
async getConnectionConfig(name?: string) {
const orgInfo = await this._getOrgInfo(name);
if (!orgInfo) {
return null;
}
const { accessToken, instanceUrl, loginUrl } = orgInfo;
return { accessToken, instanceUrl, loginUrl };
}
async saveConnectionConfig(_name: string, _connConfig: ConnectionConfig) {
// nothing to do
}
async setDefaultConnection(_name: string) {
// nothing to do
}
async removeConnectionConfig(name: string) {
await this._execCommand('force:org:delete', { u: name });
}
// eslint-disable-next-line @typescript-eslint/require-await
async getClientConfig(_name: string) {
return null;
}
// eslint-disable-next-line @typescript-eslint/require-await
async getClientNames() {
return [];
}
async registerClientConfig(_name: string, _clientConfig: ClientConfig) {
// nothing to do
}
}