UNPKG

api

Version:

Magical SDK generation from an OpenAPI definition 🪄

195 lines (176 loc) • 6.28 kB
import type { SupportedLanguages } from '../codegen'; import { Command, Option } from 'commander'; import figures from 'figures'; import Oas from 'oas'; import ora from 'ora'; import Fetcher from '../../fetcher'; import codegen from '../codegen'; import promptTerminal from '../lib/prompt'; import logger from '../logger'; import Storage from '../storage'; // @todo log logs to `.api/.logs` and have `.logs` ignored const cmd = new Command(); cmd .name('install') .description('install an API SDK into your codebase') .argument('<uri>', 'an API to install') .option('-i, --identifier <identifier>', 'API identifier (eg. `@api/petstore`)') .addOption( new Option('-l, --lang <language>', 'SDK language').choices([ 'js', // User generally wants JS, we'll prompt if they want CJS or ESM files. 'js-cjs', 'js-esm', 'ts', ]), ) .addOption(new Option('-y, --yes', 'Automatically answer "yes" to any prompts printed')) .action(async (uri: string, options: { identifier?: string; lang: string; yes?: boolean }) => { let language: SupportedLanguages; if (options.lang) { language = options.lang as SupportedLanguages; } else { ({ value: language } = await promptTerminal({ type: 'select', name: 'value', message: 'What language would you like to generate an SDK for?', choices: [ { title: 'TypeScript', value: 'ts' }, { title: 'JavaScript', value: 'js' }, ], initial: 1, })); } // Because our TS generation outputs raw TS source files we don't need to worry about CJS/ESM. if (language === 'js') { ({ value: language } = await promptTerminal({ type: 'select', name: 'value', message: 'How are your project imports and exports structured?', choices: [ { title: 'CommonJS', description: 'require/exports', value: 'cjs' }, { title: 'ECMAScript Modules', description: 'import/export', value: 'esm' }, ], initial: 0, format: sel => (sel === 'cjs' ? 'js-cjs' : 'js-esm'), })); } // @todo let them know that we're going to be creating a `.api/ directory // @todo detect if they have a gitigore and .npmignore and if .api woudl be ignored by that // @todo don't support swagger files without upconverting them if (Storage.isInLockFile({ source: uri })) { // @todo // logger(`It looks like you already have this API installed. Would you like to update it?`); } let identifier; if (options.identifier) { // `Storage.isIdentifierValid` will throw an exception if an identifier is invalid. if (Storage.isIdentifierValid(options.identifier)) { identifier = options.identifier; } } else if (Fetcher.isAPIRegistryUUID(uri)) { identifier = Fetcher.getProjectPrefixFromRegistryUUID(uri); } else { ({ value: identifier } = await promptTerminal({ type: 'text', name: 'value', message: 'What would you like to identify this API as? This will be how you import the SDK. (e.g. entering `petstore` would result in `@api/petstore`)', validate: value => { if (!value) { return false; } try { return Storage.isIdentifierValid(value, true); } catch (err) { return err.message; } }, })); } if (!identifier) { logger('You must tell us what you would like to identify this API as in order to install it.', true); process.exit(1); } let spinner = ora('Fetching your API').start(); const storage = new Storage(uri, identifier); const oas = await storage .load() .then(res => { spinner.succeed(spinner.text); return res; }) .then(Oas.init) .catch(err => { // @todo cleanup installed files spinner.fail(spinner.text); logger(err.message, true); process.exit(1); }); // @todo look for a prettier config and if we find one ask them if we should use it spinner = ora('Generating your SDK').start(); const generator = codegen(language, oas, './openapi.json', identifier); const sdkSource = await generator .generator() .then(res => { spinner.succeed(spinner.text); return res; }) .catch(err => { // @todo cleanup installed files spinner.fail(spinner.text); logger(err.message, true); process.exit(1); }); spinner = ora('Saving your SDK into your codebase').start(); await storage .saveSourceFiles(sdkSource) .then(() => { spinner.succeed(spinner.text); }) .catch(err => { // @todo cleanup installed files spinner.fail(spinner.text); logger(err.message, true); process.exit(1); }); if (generator.hasRequiredPackages()) { logger(`${figures.warning} This generator requires some packages to be installed alongside it:`); Object.entries(generator.requiredPackages).forEach(([pkg, pkgInfo]) => { logger(` ${figures.pointerSmall} ${pkg}: ${pkgInfo.reason} ${pkgInfo.url}`); }); if (!options.yes) { await promptTerminal({ type: 'confirm', name: 'value', message: 'OK to proceed with package installation?', initial: true, }).then(({ value }) => { if (!value) { // @todo cleanup installed files logger('Installation cancelled.', true); process.exit(1); } }); } spinner = ora('Installing required packages').start(); try { await generator.installer(storage); spinner.succeed(spinner.text); } catch (err) { // @todo cleanup installed files spinner.fail(spinner.text); logger(err.message, true); process.exit(1); } } logger('🚀 All done!'); }) .addHelpText( 'after', ` Examples: $ api install @developers/v2.0#nysezql0wwo236 $ api install https://raw.githubusercontent.com/readmeio/oas-examples/main/3.0/json/petstore-simple.json $ api install ./petstore.json`, ); export default cmd;