[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/subsquid/squid-cli/develop/src/command.ts [Back]  [Original]

import { Command, Flags } from '@oclif/core';
import { FailedFlagValidationError } from '@oclif/core/lib/parser/errors';
import chalk from 'chalk';
import inquirer from 'inquirer';
import { isNil, uniqBy } from 'lodash';

import {
  ApiError,
  getOrganization,
  getSquid,
  listOrganizations,
  listSquids,
  listUserSquids,
  Squid,
  SquidRequest,
} from './api';
import { getTTY } from './tty';
import { formatSquidReference, printSquid } from './utils';

export const SUCCESS_CHECK_MARK = chalk.green('');

export abstract class CliCommand extends Command {
  static baseFlags = {
    interactive: Flags.boolean({
      description: 'Enable interactive mode. Use --no-interactive to disable prompts for CI/scripts.',
      required: false,
      default: true,
      allowNo: true,
    }),
  };

  logSuccess(message: string) {
    this.log(SUCCESS_CHECK_MARK + message);
  }

  logQuestion(message: string) {
    this.log(chalk.green(`? `) + message);
  }

  logDimmed(message: string) {
    this.log(chalk.dim(message));
  }

  validateSquidNameFlags(flags: { reference?: any; name?: any }) {
    if (flags.reference || flags.name) return;

    throw new FailedFlagValidationError({
      failed: [
        {
          name: 'squid name',
          validationFn: 'validateSquidName',
          reason: [
            'One of the following must be provided: --reference or --name',
            '',
            'Examples:',
            '  sqd  --reference my-squid@v1',
            '  sqd  --name my-squid --slot ',
          ].join('\n'),
          status: 'failed',
        },
      ],
      parse: {},
    });
  }

  async catch(error: any) {
    if (error instanceof ApiError) {
      const { request, body } = error;

      switch (request.status) {
        case 401:
          return this.error(
            `Authentication failure. Please obtain a new deployment key at https://app.subsquid.io and follow the instructions`,
          );
        case 400:
          if (body?.invalidFields) {
            const messages = body.invalidFields.map(function (obj: any, index: number) {
              return `${index + 1}) ${chalk.bold('"' + obj.path.join('.') + '"')}  ${obj.message}`;
            });
            return this.error(`Validation error:\n${messages.join('\n')}`);
          }
          return this.error(body?.error || body?.message || `Validation error ${body}`);
        case 404:
          const defaultErrorStart = `cannot ${request.method.toLowerCase()}`;

          if (
            body.error.toLowerCase().startsWith(defaultErrorStart) ||
            body.message?.toLowerCase().startsWith(defaultErrorStart)
          ) {
            const url = `${chalk.bold(request.method)} ${chalk.bold(request.url)}`;

            return this.error(
              `Unknown API endpoint ${url}. Check that your are using the latest version of the Squid CLI. If the problem persists, please contact support.`,
            );
          } else {
            return this.error(body.error);
          }
        case 405:
          return this.error(body?.error || body?.message || 'Method not allowed');
        case 502:
        case 503:
        case 504:
          return this.error('The API is currently unavailable. Please try again later');
        default:
          return this.error(
            [
              `Unknown network error occurred`,
              `==================`,
              `Status: ${request.status}`,
              `Body:\n${JSON.stringify(body)}`,
            ].join('\n'),
          );
      }
    }

    throw error;
  }

  async findSquid(req: SquidRequest) {
    try {
      return await getSquid(req);
    } catch (e) {
      if (e instanceof ApiError && e.request.status === 404) {
        return null;
      }

      throw e;
    }
  }

  async findOrThrowSquid({ organization, squid }: SquidRequest) {
    const res = await this.findSquid({ organization, squid });
    if (!res) {
      throw new Error(
        `The squid ${formatSquidReference(typeof squid === 'string' ? squid : squid, { colored: true })} is not found`,
      );
    }

    return res;
  }

  async promptOrganization(
    code: string | null | undefined,
    { using, interactive }: { using?: string; interactive?: boolean } = {},
  ) {
    if (code) {
      return await getOrganization({ organization: { code } });
    }

    const organizations = await listOrganizations();

    return await this.getOrganizationPrompt(organizations, { using, interactive });
  }

  async promptSquidOrganization(
    code: string | null | undefined,
    name: string,
    {
      using,
      interactive,
    }: {
      using?: string;
      interactive?: boolean;
    } = {},
  ) {
    if (code) {
      return await getOrganization({ organization: { code } });
    }

    const squids = await listUserSquids({ name });

    let organizations = squids.map((s) => s.organization).filter((o) => !isNil(o));
    organizations = uniqBy(organizations, (o) => o.code);

    if (organizations.length === 0) {
      return this.error(`You have no organizations with squid "${name}".`);
    }

    return await this.getOrganizationPrompt(organizations, { using, interactive });
  }

  async promptSquid(organization: { code: string }, { interactive }: { interactive?: boolean } = {}): Promise {
    const squids = await listSquids({ organization });

    if (squids.length === 0) {
      return this.error(`No squids found in organization "${organization.code}".`);
    }

    if (squids.length === 1) {
      return squids[0];
    }

    const { stdin, stdout } = getTTY();
    if (!stdin || !stdout || !interactive) {
      return this.error(
        [
          `Organization "${organization.code}" has ${squids.length} squids:`,
          ...squids.map((s) => `  - ${formatSquidReference({ name: s.name, slot: s.slot })}`),
          ``,
          `Please specify the squid using "--reference" or "--name" flag.`,
          `Example: sqd  --reference ${squids[0].name}@${squids[0].slot}`,
        ].join('\n'),
      );
    }

    const prompt = inquirer.createPromptModule({ input: stdin, output: stdout });
    const { squid } = await prompt([
      {
        name: 'squid',
        type: 'list',
        message: 'Please choose a squid:',
        choices: squids.map((s) => {
          const ref = formatSquidReference({ name: s.name, slot: s.slot });
          const tags = s.tags.length ? ` (${s.tags.map((t) => t.name).join(', ')})` : '';
          return {
            name: `${ref}${tags}`,
            value: s,
          };
        }),
      },
    ]);

    stdin.destroy();
    stdout.destroy();

    return squid;
  }

  private async getOrganizationPrompt(
    organizations: T[],
    {
      using = 'using "--org" flag',
      interactive,
    }: {
      using?: string;
      interactive?: boolean;
    },
  ): Promise {
    if (organizations.length === 0) {
      return this.error(`You have no organizations. Please create organization first.`);
    } else if (organizations.length === 1) {
      return organizations[0];
    }

    const { stdin, stdout } = getTTY();
    if (!stdin || !stdout || !interactive) {
      return this.error(
        [
          `You have ${organizations.length} organizations:`,
          ...organizations.map((o) => `  - ${o.code}`),
          ``,
          `Please specify one of them explicitly ${using}.`,
          `Example: sqd  --org ${organizations[0].code}`,
        ].join('\n'),
      );
    }

    const prompt = inquirer.createPromptModule({ input: stdin, output: stdout });
    const { organization } = await prompt([
      {
        name: 'organization',
        type: 'list',
        message: `Please choose an organization:`,
        choices: organizations.map((o) => {
          return {
            name: o.name ? `${o.name} (${o.code})` : o.code,
            value: o,
          };
        }),
      },
    ]);

    // Hack to prevent opened descriptors to block event loop before exit
    stdin.destroy();
    stdout.destroy();

    return organization;
  }
}

export * as SqdFlags from './flags';

Web Proxy Viewer  |  New URL  |  Original Page