apollo-elements/apollo-elements

View on GitHub
packages/create/prompt.ts

Summary

Maintainability
A
0 mins
Test Coverage
import type { BaseOptions, AppOptions, ComponentOptions } from './options';

import { app } from './app';
import { component } from './component';

import prompts from 'prompts';

import BANNER from './banner';

import Yargs from 'yargs';

export type PromptOptions<T> =
  Partial<T> & BaseOptions;

const ERR_BAD_CE_TAG_NAME =
  'Custom element tag names must contain a hyphen (-)';

export async function promptApp(options: PromptOptions<AppOptions>): Promise<AppOptions> {
  return {
    ...options,
    ...await prompts([{
      type: () => options?.uri == null ? 'text' : null,
      name: 'uri',
      message: 'What is the URI to your GraphQL endpoint?',
      initial: options?.uri ?? '/graphql',
    }, {
      type: () => options?.yes == null ? 'confirm' : null,
      name: 'yes',
      message: 'Use default package.json fields (e.g. author, license, etc)',
      initial: options?.yes ?? true,
    }, {
      type: () => options?.install == null ? 'confirm' : null,
      name: 'install',
      message: 'Install dependencies?',
      initial: true,
    }, {
      type: () => options?.start == null ? 'confirm' : null,
      name: 'start',
      message: 'Launch when ready?',
      initial: true,
    }]),
  };
}

export async function promptComponent(
  options?: PromptOptions<ComponentOptions>
): Promise<ComponentOptions> {
  return {
    ...options,
    ...await prompts([{
      type: () => options?.type == null ? 'select' : null,
      name: 'type',
      message: 'What kind of component is it?',
      initial: 0,
      choices: [
        { title: 'Query', value: 'query' },
        { title: 'Mutation', value: 'mutation' },
        { title: 'Subscription', value: 'subscription' },
      ],
    }, {
      type: () => options?.name == null ? 'text' : null,
      name: 'name',
      message: 'What is the component\'s tag name?',
      initial: options?.name ?? '',
      validate: name => name.includes('-') || ERR_BAD_CE_TAG_NAME,
    }, {
      type: () => options?.subdir == null ? 'text' : null,
      name: 'subdir',
      initial: options?.subdir ?? '',
      message: 'Sub directory. Leave blank to scaffold to src/components',
    }]),
  } as ComponentOptions;
}

export async function prompt(): Promise<void> {
  const pkgManager: 'npm'|'yarn' =
      process.argv0 === 'npm' ? process.argv0
    : process.argv0 === 'yarn' ? process.argv0
    : 'npm';

  const { argv } = Yargs
    .scriptName(`${pkgManager ?? 'npm'} init @apollo-elements`)
    .usage('$0 [<cmd>] [args]')
    .option('pkgManager', {
      type: 'string',
      default: pkgManager,
      description: 'Preferred package manager',
    })
    .option('schemaPath', {
      type: 'string',
      default: null,
      demandOption: false,
      description: `Optional schema path for imports. Use to import from a package or specific file.`,
    })
    .option('skipCodegen', {
      type: 'boolean',
      default: false,
      demandOption: false,
      description: 'Skip the codegen phase',
    })
    .command<AppOptions>('app', 'Generate an Apollo Elements Skeleton App', yargs => void yargs
      .option('uri', {
        alias: 'u',
        type: 'string',
        description: 'URI to your GraphQL endpoint',
      })
      .option('yes', {
        alias: 'y',
        type: 'boolean',
        description: 'Use default package.json fields (e.g. author, license)',
      })
      .option('install', {
        alias: 'i',
        type: 'boolean',
        description: 'Automatically install dependencies',
      })
      .option('start', {
        alias: 's',
        type: 'boolean',
        description: 'Launch the dev server after scaffolding',
      })
      .help())
    .command<ComponentOptions>('component', 'Generate an Apollo Element', yargs => void yargs
      .option('name', {
        alias: 'n',
        type: 'string',
        description: 'Custom element tag name',
      })
      .option('operationName', {
        alias: 'o',
        type: 'string',
        description: 'GraphQL Operation name',
      })
      .option('subdir', {
        alias: 'd',
        type: 'string',
        description: 'Optional subdir under src/components',
      })
      .option('type', {
        alias: 't',
        type: 'string',
        choices: ['query', 'mutation', 'subscription'],
        description: 'Element Type',
        default: 'query',
      })
      .option('yes', {
        type: 'boolean',
        alias: 'y',
        default: false,
        description: 'Overwrite files without prompting',
      })
      .option('fields', {
        type: 'string',
        default: null,
        demandOption: false,
        description: 'Optional custom fields e.g. `id name picture { alt url }`',
      })
      .option('variables', {
        type: 'string',
        default: null,
        demandOption: false,
        description: 'Optional custom variables e.g. `input: $UpdateUserInput`',
      })
      .option('sharedCssPath', {
        type: 'string',
        default: null,
        demandOption: false,
        description: `Optional path for shared CSS file. Use empty string '' to disable`,
      })
      .help())
    .help()
    .check(({ name }) => {
      if (typeof name === 'string' && !name.includes('-'))
        throw new Error(ERR_BAD_CE_TAG_NAME);
      else
        return true;
    });

  try {
    if (argv._.includes('app'))
      return await promptApp(argv).then(app);
    else if (argv._.includes('component'))
      return await promptComponent(argv).then(component);
    else {
      console.log(BANNER);

      const { generate } = await prompts({
        name: 'generate',
        message: 'What would you like to generate?',
        type: 'select',
        choices: [{
          title: 'App',
          value: 'app',
          description: 'Scaffold an app project',
        }, {
          title: 'Component',
          value: 'component',
          description: 'Scaffold a component for an existing app',
        }],
      });

      const commandAppendedArgv: typeof argv = {
        ...argv,
        _: [generate, ...argv._ ?? []],
      };

      switch (generate) {
        case 'app':
          return await promptApp(commandAppendedArgv)
            .then(app);
        case 'component':
          return await promptComponent(commandAppendedArgv)
            .then(component);
      }
    }
  } catch (error) {
    if (error.command.includes('build:codegen'))
      return;
    else
      console.error(error);
  }
}