@openapi-qraft/cli
Version:
CLI for generating typed TanStack Query React Hooks and services from OpenAPI Document, improving type safety in React apps
121 lines (102 loc) • 3.64 kB
text/typescript
import type { QraftCommandPlugin } from '@openapi-qraft/plugin/lib/QraftCommandPlugin';
import { QraftCommand } from '@openapi-qraft/plugin';
import { RedoclyConfigCommand } from '@openapi-qraft/plugin/lib/RedoclyConfigCommand';
import { Option, ParseOptions } from 'commander';
import { builtInPlugins } from './builtInPlugins.js';
export async function main(
processArgv: string[],
processArgvParseOptions?: ParseOptions
) {
const redoclyConfigParseResult = await new RedoclyConfigCommand().parseConfig(
qraft,
processArgv,
processArgvParseOptions
);
if (redoclyConfigParseResult?.length) return;
await qraft(processArgv, processArgvParseOptions);
}
async function qraft(
processArgv: string[],
processArgvParseOptions?: ParseOptions
) {
const command = new QraftCommand();
const { argv, plugins } = extractArgvPluginOptions(processArgv);
if (plugins) {
await setupPlugins(command, plugins);
} else {
// Default - set up the tanstack-query-react plugin
await setupPlugins(command, [
'tanstack-query-react',
] satisfies (keyof typeof builtInPlugins)[]);
// Option to display help with all available plugins
command.addOption(
new Option(
'--plugin <name_1> --plugin <name_2>',
`Specifies which generator plugins should be used for code generation`
)
.choices(Object.keys(builtInPlugins))
.argParser(() => {
// Fallback handler for when plugin parsing fails
throw new Error(
'The plugin option must be processed before command parsing and should not be directly passed to the commander'
);
})
);
}
await command.parseAsync(argv, processArgvParseOptions);
}
async function setupPlugins(command: QraftCommand, plugins: string[]) {
const pluginList: QraftCommandPlugin[] = [];
for (const pluginName of plugins) {
if (!(pluginName in builtInPlugins))
throw new Error(`Unknown plugin: '${pluginName}'`);
pluginList.push(
(await builtInPlugins[pluginName as keyof typeof builtInPlugins]())
.default
);
addCommandUsageWithPlugins(command, plugins);
}
await Promise.all(pluginList.map((plugin) => plugin.setupCommand(command)));
await Promise.all(
pluginList.map((plugin) => plugin.postSetupCommand?.(command, plugins))
);
}
/**
* Adds plugin usage instructions by calling `command.usage(...)`
*/
function addCommandUsageWithPlugins(command: QraftCommand, plugins: string[]) {
const pluginUsage = plugins.map((plugin) => `--plugin ${plugin}`).join(' ');
command.usage(`${pluginUsage} [input] [options]`);
}
/**
* Extracts multiple `--plugin <name>` options from `argv`
* and returns both the filtered `argv` and the extracted plugins list.
*/
export function extractArgvPluginOptions(argv: string[]) {
const pluginIndex = argv.indexOf('--plugin');
if (pluginIndex === -1) return { argv };
const filteredArgv: string[] = argv.slice(0, pluginIndex);
const plugins: string[] = [];
for (let i = pluginIndex; i < argv.length; i++) {
if (argv[i] === '--plugin') {
const pluginName = argv.at(i + 1);
if (!pluginName)
throw new Error(
`A plugin name must be specified after the '--plugin' option`
);
if (pluginName?.startsWith('--'))
throw new Error(
`Invalid plugin name: '${pluginName}'. Plugin names cannot start with '--'`
);
plugins.push(pluginName);
i++; // Skip next item
} else {
filteredArgv.push(argv[i]);
}
}
return {
argv: filteredArgv,
plugins,
};
}