2022-05-18 07:26:57 +00:00
|
|
|
import colors from 'colors';
|
2022-12-28 19:22:54 +00:00
|
|
|
import { ClientError, GraphQLClient } from 'graphql-request';
|
2022-05-18 07:26:57 +00:00
|
|
|
import symbols from 'log-symbols';
|
2023-01-23 09:30:38 +00:00
|
|
|
import { Command, Errors, Config as OclifConfig } from '@oclif/core';
|
2023-04-20 10:34:57 +00:00
|
|
|
import { Config, GetConfigurationValueType, ValidConfigurationKeys } from './helpers/config';
|
2022-05-18 07:26:57 +00:00
|
|
|
import { getSdk } from './sdk';
|
|
|
|
|
|
2023-04-20 10:34:57 +00:00
|
|
|
type OmitNever<T> = { [K in keyof T as T[K] extends never ? never : K]: T[K] };
|
|
|
|
|
|
2022-05-18 07:26:57 +00:00
|
|
|
export default abstract class extends Command {
|
|
|
|
|
protected _userConfig: Config;
|
|
|
|
|
|
2022-09-16 10:53:22 +00:00
|
|
|
protected constructor(argv: string[], config: OclifConfig) {
|
2022-05-18 07:26:57 +00:00
|
|
|
super(argv, config);
|
|
|
|
|
|
|
|
|
|
this._userConfig = new Config({
|
2022-10-04 12:30:21 +00:00
|
|
|
// eslint-disable-next-line no-process-env
|
2022-05-18 07:26:57 +00:00
|
|
|
filepath: process.env.HIVE_CONFIG,
|
|
|
|
|
rootDir: process.cwd(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
success(...args: any[]) {
|
|
|
|
|
this.log(colors.green(symbols.success), ...args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fail(...args: any[]) {
|
|
|
|
|
this.log(colors.red(symbols.error), ...args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
info(...args: any[]) {
|
|
|
|
|
this.log(colors.yellow(symbols.info), ...args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bolderize(msg: string) {
|
|
|
|
|
const findSingleQuotes = /'([^']+)'/gim;
|
|
|
|
|
const findDoubleQuotes = /"([^"]+)"/gim;
|
|
|
|
|
|
|
|
|
|
return msg
|
2022-05-24 13:31:53 +00:00
|
|
|
.replace(findSingleQuotes, (_: string, value: string) => colors.bold(value))
|
|
|
|
|
.replace(findDoubleQuotes, (_: string, value: string) => colors.bold(value));
|
2022-05-18 07:26:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-09-16 10:53:22 +00:00
|
|
|
* Get a value from arguments or flags first, then from env variables,
|
|
|
|
|
* then fallback to config.
|
|
|
|
|
* Throw when there's no value.
|
2022-05-18 07:26:57 +00:00
|
|
|
*
|
|
|
|
|
* @param key
|
|
|
|
|
* @param args all arguments or flags
|
|
|
|
|
* @param defaultValue default value
|
|
|
|
|
* @param message custom error message in case of no value
|
2022-09-16 10:53:22 +00:00
|
|
|
* @param env an env var name
|
2022-05-18 07:26:57 +00:00
|
|
|
*/
|
|
|
|
|
ensure<
|
2023-04-20 10:34:57 +00:00
|
|
|
TKey extends ValidConfigurationKeys,
|
2022-05-18 07:26:57 +00:00
|
|
|
TArgs extends {
|
2023-04-20 10:34:57 +00:00
|
|
|
[key in TKey]: GetConfigurationValueType<TKey>;
|
2022-05-18 07:26:57 +00:00
|
|
|
},
|
|
|
|
|
>({
|
|
|
|
|
key,
|
|
|
|
|
args,
|
2023-04-20 10:34:57 +00:00
|
|
|
legacyFlagName,
|
2022-05-18 07:26:57 +00:00
|
|
|
defaultValue,
|
|
|
|
|
message,
|
|
|
|
|
env,
|
|
|
|
|
}: {
|
|
|
|
|
args: TArgs;
|
2023-04-20 10:34:57 +00:00
|
|
|
key: TKey;
|
|
|
|
|
/** By default we try to match config names with flag names, but for legacy compatibility we need to provide the old flag name. */
|
|
|
|
|
legacyFlagName?: keyof OmitNever<{
|
|
|
|
|
// Symbol.asyncIterator to discriminate against any lol
|
|
|
|
|
[TArgKey in keyof TArgs]: typeof Symbol.asyncIterator extends TArgs[TArgKey]
|
|
|
|
|
? never
|
|
|
|
|
: string extends TArgs[TArgKey]
|
|
|
|
|
? TArgKey
|
|
|
|
|
: never;
|
|
|
|
|
}>;
|
|
|
|
|
|
|
|
|
|
defaultValue?: TArgs[keyof TArgs] | null;
|
2022-05-18 07:26:57 +00:00
|
|
|
message?: string;
|
|
|
|
|
env?: string;
|
2023-04-20 10:34:57 +00:00
|
|
|
}): NonNullable<GetConfigurationValueType<TKey>> | never {
|
|
|
|
|
if (args[key] != null) {
|
|
|
|
|
return args[key] as NonNullable<GetConfigurationValueType<TKey>>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (legacyFlagName && (args as any)[legacyFlagName] != null) {
|
|
|
|
|
return args[legacyFlagName] as any as NonNullable<GetConfigurationValueType<TKey>>;
|
2022-05-18 07:26:57 +00:00
|
|
|
}
|
|
|
|
|
|
2022-10-04 12:30:21 +00:00
|
|
|
// eslint-disable-next-line no-process-env
|
2022-05-18 07:26:57 +00:00
|
|
|
if (env && process.env[env]) {
|
2022-10-04 12:30:21 +00:00
|
|
|
// eslint-disable-next-line no-process-env
|
2023-04-20 10:34:57 +00:00
|
|
|
return process.env[env] as TArgs[keyof TArgs] as NonNullable<GetConfigurationValueType<TKey>>;
|
2022-05-18 07:26:57 +00:00
|
|
|
}
|
|
|
|
|
|
2023-04-20 10:34:57 +00:00
|
|
|
const userConfigValue = this._userConfig.get(key);
|
|
|
|
|
|
|
|
|
|
if (userConfigValue != null) {
|
|
|
|
|
return userConfigValue;
|
2022-05-18 07:26:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (defaultValue) {
|
|
|
|
|
return defaultValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (message) {
|
|
|
|
|
throw new Errors.CLIError(message);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-27 11:17:32 +00:00
|
|
|
throw new Errors.CLIError(`Missing "${String(key)}"`);
|
2022-05-18 07:26:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cleanRequestId(requestId?: string | null) {
|
|
|
|
|
return requestId ? requestId.split(',')[0].trim() : undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
registryApi(registry: string, token: string) {
|
|
|
|
|
return getSdk(
|
|
|
|
|
new GraphQLClient(registry, {
|
|
|
|
|
headers: {
|
2022-10-06 11:48:01 +00:00
|
|
|
Accept: 'application/json',
|
2022-11-21 09:44:25 +00:00
|
|
|
'User-Agent': `hive-cli/${this.config.version}`,
|
2022-07-01 10:15:57 +00:00
|
|
|
Authorization: `Bearer ${token}`,
|
2022-05-18 07:26:57 +00:00
|
|
|
'graphql-client-name': 'Hive CLI',
|
|
|
|
|
'graphql-client-version': this.config.version,
|
|
|
|
|
},
|
2022-11-24 10:00:41 +00:00
|
|
|
}),
|
2022-05-18 07:26:57 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-01 09:43:27 +00:00
|
|
|
handleFetchError(error: unknown): never {
|
|
|
|
|
if (typeof error === 'string') {
|
|
|
|
|
return this.error(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (error instanceof Error) {
|
|
|
|
|
if (isClientError(error)) {
|
|
|
|
|
const errors = error.response?.errors;
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(errors) && errors.length > 0) {
|
|
|
|
|
return this.error(errors[0].message, {
|
|
|
|
|
ref: this.cleanRequestId(error.response?.headers?.get('x-request-id')),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.error(error.message, {
|
|
|
|
|
ref: this.cleanRequestId(error.response?.headers?.get('x-request-id')),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.error(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.error(JSON.stringify(error));
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-18 07:26:57 +00:00
|
|
|
async require<
|
|
|
|
|
TFlags extends {
|
|
|
|
|
require: string[];
|
|
|
|
|
[key: string]: any;
|
2022-11-24 10:00:41 +00:00
|
|
|
},
|
2022-05-18 07:26:57 +00:00
|
|
|
>(flags: TFlags) {
|
|
|
|
|
if (flags.require && flags.require.length > 0) {
|
2022-11-24 10:00:41 +00:00
|
|
|
await Promise.all(
|
|
|
|
|
flags.require.map(mod => import(require.resolve(mod, { paths: [process.cwd()] }))),
|
|
|
|
|
);
|
2022-05-18 07:26:57 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-01 09:43:27 +00:00
|
|
|
|
|
|
|
|
function isClientError(error: Error): error is ClientError {
|
|
|
|
|
return 'response' in error;
|
|
|
|
|
}
|