openapi-ts icon indicating copy to clipboard operation
openapi-ts copied to clipboard

Multiple configurations/specifying a configuration file

Open slahn opened this issue 1 year ago • 5 comments

Description

Our projects needs clients/models for multiple different OpenAPI specifications.

Is it possible to specify multiple configurations in the config file, or to instruct the CLI to load a specific config file?

If this is already possible, some mention in the documentation would be helpful.

slahn avatar May 13 '24 09:05 slahn

Hey, this isn't currently possible, see https://github.com/hey-api/openapi-ts/discussions/444. Are you able to share how you work around this today?

mrlubos avatar May 13 '24 10:05 mrlubos

Currently working around this by not using a config file, and having multiple invocations with command line options in package.json.

slahn avatar May 13 '24 12:05 slahn

I looked into this and the current blocker is that c12 does not support it. I'll see if I get a reply in the issue and if not, I will fork that package and add support. https://github.com/unjs/c12/issues/92

mrlubos avatar May 15 '24 00:05 mrlubos

This is the workaround I'm using. Define multiple config values and switch on an env var.

import { defineConfig } from '@hey-api/openapi-ts';

const APIv1 = defineConfig({
  input: 'https://myapi.com/v1/openapi.json',
  output: 'api/v1',
  client: '@hey-api/client-fetch',
});

const APIv2 = defineConfig({
  input: 'https://myapi.com/v2/openapi.json',
  output: 'api/v2',
  client: '@hey-api/client-fetch',
});


let configExport;
switch (process.env.API_SPEC) {
    case "APIv1": {
        configExport = APIv1;
        break;
    }
    case "APIv2": {
        configExport = APIv2;
        break;
    }
    default:
        configExport = APIv1;
}

export default configExport;

Then commands can be run simply with:

API_SPEC=APIv1 npx @hey-api/openapi-ts

ecline123 avatar Jun 26 '24 21:06 ecline123

Our solution

openapi-generator.ts

import { execSync } from 'child_process';
import { Command } from 'commander';
import { existsSync, unlinkSync, writeFileSync } from 'fs';

type GenerateConfigOptions = {
  input: string;
  outputPath: string;
  clientName: string;
  httpClient: 'fetch' | 'axios';
  enumType?: 'typescript' | 'javascript';
  asClass: boolean;
};

function generateConfig(options: GenerateConfigOptions) {
  const typesConfig = options.enumType
    ? `types: { enums: '${options.enumType}' },`
    : '';
  return `
    import { defineConfig } from '@hey-api/openapi-ts';
    
    export default defineConfig({
      input: '${options.input}',
      output: {
        path: '${options.outputPath}',
        format: 'prettier',
        lint: 'eslint',
      },
      name: '${options.clientName}',
      client: '${options.httpClient}',
      ${typesConfig}
      services: {
        asClass: ${options.asClass},
      },
    });
  `;
}

const program = new Command();

program
  .requiredOption(
    '--input <urlOrPath>',
    'Path to the input OpenAPI specification file',
  )
  .requiredOption('--client-name <string>', 'Name of the client to generate')
  .requiredOption(
    '--output-path <path>',
    'Directory path where the generated client will be saved',
  )
  .requiredOption(
    '--http-client <httpClient>',
    'HTTP client to use (fetch or axios)',
    (value) => {
      if (!['fetch', 'axios'].includes(value)) {
        throw new Error('Invalid HTTP client. Must be "fetch" or "axios".');
      }
      return value;
    },
  )
  .requiredOption(
    '--as-class <asClass>',
    'Generate services as classes if true',
    (value) => {
      if (!['true', 'false'].includes(value)) {
        throw new Error(
          'Invalid value for asClass. Must be either true or false.',
        );
      }
      return Boolean(value === 'true');
    },
  )
  .option(
    '--enum-type <enumType>',
    'Type of enums to generate (typescript or javascript) (optional)',
    (value) => {
      if (value !== 'typescript' && value !== 'javascript') {
        throw new Error(
          'Invalid enum type. Must be either "typescript" or "javascript".',
        );
      }
      return value;
    },
  );

program.parse(process.argv);

const options = program.opts() as GenerateConfigOptions;

const configFilePath = './openapi-ts.config.ts';

if (existsSync(configFilePath)) {
  try {
    unlinkSync(configFilePath);
    console.log(`Existing file ${configFilePath} has been removed.`);
  } catch (error) {}
}

const generatedConfig = generateConfig(options);

try {
  writeFileSync(configFilePath, generatedConfig);
  console.log(`Configuration file generated at ${configFilePath}`);

  try {
    execSync('yarn openapi-ts', { stdio: 'inherit' });
    console.log('OpenAPI client generated successfully.');
  } catch (error) {
    console.error('Error generating OpenAPI client:', error);
    unlinkSync(configFilePath);
    process.exit(1);
  }
  unlinkSync(configFilePath);
} catch (error) {
  console.error(`Error writing to file ${configFilePath}:`, error);
  unlinkSync(configFilePath);
  process.exit(1);
}

To use it:

yarn ts-node openapi-generator.ts \
  --input $URL/api-json \
  --client-name MyFancyClient \
  --output-path src/my-fancy-client \
  --http-client fetch \
  --as-class true

If the openapi-ts cli was 1:1 with the config options in openapi-ts.config.ts, this issue won't be relevant anymore. So maybe the openapi-ts cli flags can be generated by the config? Maybe its worth looking into it instead of waiting for c12 to add multi config

utajum avatar Jul 12 '24 05:07 utajum