Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use override in uninstall command #800

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/api/src/bin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Command } from 'commander';

import commands from './commands/index.js';
import logger from './logger.js';
import * as pkg from './packageInfo.js';

(async () => {
Expand All @@ -17,5 +18,8 @@ import * as pkg from './packageInfo.js';
program.addCommand(cmd);
});

await program.parseAsync(process.argv);
await program.parseAsync(process.argv).catch(err => {
if (err.message) logger(err.message, true);
process.exit(1);
});
})();
98 changes: 41 additions & 57 deletions packages/api/src/commands/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import { emphasize } from 'emphasize';
import figures from 'figures';
import Oas from 'oas';
import ora from 'ora';
import prompts from 'prompts';
import uslug from 'uslug';

import { SupportedLanguages, codegenFactory } from '../codegen/factory.js';
import Fetcher from '../fetcher.js';
import promptTerminal from '../lib/prompt.js';
import { buildCodeSnippetForOperation, getSuggestedOperation } from '../lib/suggestedOperations.js';
import logger from '../logger.js';
import logger, { oraOptions } from '../logger.js';
import Storage from '../storage.js';

interface Options {
Expand All @@ -21,36 +22,26 @@ interface Options {
yes?: boolean;
}

async function getLanguage(options: Options) {
let language: SupportedLanguage;
if (options.lang) {
language = options.lang;
} else {
({ value: language } = await promptTerminal({
type: 'select',
name: 'value',
message: 'What language would you like to generate an SDK for?',
choices: [{ title: 'JavaScript', value: SupportedLanguages.JS }],
initial: 1,
}));
}
async function getLanguage() {
const { lang }: { lang: SupportedLanguage } = await promptTerminal({
type: 'select',
name: 'lang',
message: 'What language would you like to generate an SDK for?',
choices: [{ title: 'JavaScript', value: SupportedLanguages.JS }],
initial: 1,
});

return language;
return lang;
}

async function getIdentifier(oas: Oas, uri: string, options: Options) {
async function getIdentifier(oas: Oas, uri: string) {
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)) {
if (Fetcher.isAPIRegistryUUID(uri)) {
identifier = Fetcher.getProjectPrefixFromRegistryUUID(uri);
} else {
({ value: identifier } = await promptTerminal({
({ identifier } = await promptTerminal({
type: 'text',
name: 'value',
name: 'identifier',
initial: oas.api?.info?.title ? uslug(oas.api.info.title, { lower: true }) : undefined,
message:
'What would you like to identify this API as? This will be how you use the SDK. (e.g. entering `petstore` would result in `@api/petstore`)',
Expand Down Expand Up @@ -92,7 +83,9 @@ cmd
)
.addOption(new Option('-y, --yes', 'Automatically answer "yes" to any prompts printed'))
.action(async (uri: string, options: Options) => {
const language = await getLanguage(options);
prompts.override(options);

const language = await getLanguage();

// @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
Expand All @@ -103,7 +96,7 @@ cmd
// logger(`It looks like you already have this API installed. Would you like to update it?`);
}

let spinner = ora('Fetching your API definition').start();
let spinner = ora({ text: 'Fetching your API definition', ...oraOptions() }).start();
const storage = new Storage(uri, language);

const oas = await storage
Expand All @@ -120,14 +113,12 @@ cmd
.catch(err => {
// @todo cleanup installed files
spinner.fail(spinner.text);
logger(err.message, true);
process.exit(1);
throw err;
});

const identifier = await getIdentifier(oas, uri, options);
const identifier = await getIdentifier(oas, uri);
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);
throw new Error('You must tell us what you would like to identify this API as in order to install it.');
}

// Now that we've got an identifier we can save their spec and generate the directory structure
Expand All @@ -136,7 +127,7 @@ cmd
await storage.save(oas.api);

// @todo look for a prettier config and if we find one ask them if we should use it
spinner = ora('Generating your SDK').start();
spinner = ora({ text: 'Generating your SDK', ...oraOptions() }).start();
const generator = codegenFactory(language, oas, '../openapi.json', identifier);
const sdkSource = await generator
.generate()
Expand All @@ -147,11 +138,10 @@ cmd
.catch(err => {
// @todo cleanup installed files
spinner.fail(spinner.text);
logger(err.message, true);
process.exit(1);
throw err;
});

spinner = ora('Saving your SDK into your codebase').start();
spinner = ora({ text: 'Saving your SDK into your codebase', ...oraOptions() }).start();
await storage
.saveSourceFiles(sdkSource)
.then(() => {
Expand All @@ -160,8 +150,7 @@ cmd
.catch(err => {
// @todo cleanup installed files
spinner.fail(spinner.text);
logger(err.message, true);
process.exit(1);
throw err;
});

if (generator.hasRequiredPackages()) {
Expand All @@ -175,42 +164,37 @@ cmd
logger(msg);
});

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);
}
});
}
await promptTerminal({
type: 'confirm',
name: 'yes',
message: 'OK to proceed with package installation?',
initial: true,
}).then(({ yes }) => {
if (!yes) {
// @todo cleanup installed files
throw new Error('Installation cancelled.');
}
});

spinner = ora('Installing required packages').start();
spinner = ora({ text: 'Installing required packages', ...oraOptions() }).start();
try {
await generator.install(storage);
spinner.succeed(spinner.text);
} catch (err) {
// @todo cleanup installed files
spinner.fail(spinner.text);
logger(err.message, true);
process.exit(1);
throw err;
}
}

spinner = ora('Compiling your SDK').start();
spinner = ora({ text: 'Compiling your SDK', ...oraOptions() }).start();
try {
await generator.compile(storage);
spinner.succeed(spinner.text);
} catch (err) {
// @todo cleanup installed files
spinner.fail(spinner.text);
logger(err.message, true);
process.exit(1);
throw err;
}

logger('');
Expand Down
46 changes: 22 additions & 24 deletions packages/api/src/commands/uninstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import path from 'node:path';
import chalk from 'chalk';
import { Command, Option } from 'commander';
import ora from 'ora';
import prompts from 'prompts';

import { SupportedLanguages, uninstallerFactory } from '../codegen/factory.js';
import promptTerminal from '../lib/prompt.js';
import logger from '../logger.js';
import logger, { oraOptions } from '../logger.js';
import Storage from '../storage.js';

interface Options {
Expand All @@ -26,33 +27,32 @@ cmd

const entry = Storage.getFromLockfile(identifier);
if (!entry) {
logger(
throw new Error(
`You do not appear to have ${identifier} installed. You can run \`npx api list\` to see what SDKs are present.`,
true,
);
process.exit(1);
}

storage.setLanguage(entry?.language);
storage.setIdentifier(identifier);

const directory = path.relative(process.cwd(), storage.getIdentifierStorageDir());
if (!options.yes) {
await promptTerminal({
type: 'confirm',
name: 'value',
message: `Are you sure you want to uninstall ${chalk.yellow(identifier)}? This will delete the ${chalk.yellow(
directory,
)} directory and potentially any changes you may have made there.`,
initial: true,
}).then(({ value }) => {
if (!value) {
process.exit(1);
}
});
}

let spinner = ora(`Uninstalling ${chalk.grey(identifier)}`).start();
// funnels `--yes` option into prompt
prompts.override(options);
await promptTerminal({
type: 'confirm',
name: 'yes',
message: `Are you sure you want to uninstall ${chalk.yellow(identifier)}? This will delete the ${chalk.yellow(
directory,
)} directory and potentially any changes you may have made there.`,
initial: true,
}).then(({ yes }) => {
if (!yes) {
throw new Error('Uninstallation cancelled.');
}
});

let spinner = ora({ text: `Uninstalling ${chalk.grey(identifier)}`, ...oraOptions() }).start();

// If we have a known package name for this then we can uninstall it from within cooresponding
// package manager.
Expand All @@ -65,21 +65,19 @@ cmd
})
.catch(err => {
spinner.fail(spinner.text);
logger(err.message, true);
process.exit(1);
throw err;
});
}

spinner = ora(`Removing ${chalk.grey(directory)}`).start();
spinner = ora({ text: `Removing ${chalk.grey(directory)}`, ...oraOptions() }).start();
await storage
.remove()
.then(() => {
spinner.succeed(spinner.text);
})
.catch(err => {
spinner.fail(spinner.text);
logger(err.message, true);
process.exit(1);
throw err;
});

logger('🚀 All done!');
Expand Down
9 changes: 9 additions & 0 deletions packages/api/src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/* eslint-disable no-console */
import type { Options as OraOptions } from 'ora';

import chalk from 'chalk';

export default function logger(log: string, error?: boolean) {
Expand All @@ -8,3 +10,10 @@ export default function logger(log: string, error?: boolean) {
console.log(log);
}
}

export function oraOptions() {
// Disables spinner in tests so it doesn't pollute test output
const opts: OraOptions = { isSilent: process.env.NODE_ENV === 'test' };

return opts;
}
24 changes: 24 additions & 0 deletions packages/api/test/commands/__snapshots__/install.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`install command > should print help screen 1`] = `
"Usage: install [options] <uri>

install an API SDK into your codebase

Arguments:
uri an API to install

Options:
-i, --identifier <identifier> API identifier (eg. \`@api/petstore\`)
-l, --lang <language> SDK language (choices: \\"js\\", default: \\"js\\")
-y, --yes Automatically answer \\"yes\\" to any prompts
printed
-h, --help display help for command


Examples:
$ npx api install @developers/v2.0#nysezql0wwo236
$ npx api install https://raw.githubusercontent.com/readmeio/oas-examples/main/3.0/json/petstore-simple.json
$ npx api install ./petstore.json
"
`;
25 changes: 25 additions & 0 deletions packages/api/test/commands/__snapshots__/list.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`install command > should list installed SDKs 1`] = `
"petstore

package name (private): @api/petstore

language: js

source: @petstore/v1.0#n6kvf10vakpemvplx

installer version: 7.0.0-beta.3

created at: 2023-10-25T00:00:00.000Z"
`;

exports[`install command > should print help screen 1`] = `
"Usage: list|ls [options]

list any installed API SDKs

Options:
-h, --help display help for command
"
`;
19 changes: 19 additions & 0 deletions packages/api/test/commands/__snapshots__/uninstall.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`install command > should print help screen 1`] = `
"Usage: uninstall [options] <identifier>

uninstall an SDK from your codebase

Arguments:
identifier the SDK to uninstall

Options:
-y, --yes Automatically answer \\"yes\\" to any prompts printed
-h, --help display help for command


Examples:
$ npx api uninstall petstore
"
`;
Loading