Skip to content

Commit

Permalink
fix(ama-sdk): support of URL as spec-path input
Browse files Browse the repository at this point in the history
  • Loading branch information
kpanot committed Apr 5, 2024
1 parent 35b60cd commit 7c3ce9f
Show file tree
Hide file tree
Showing 9 changed files with 301 additions and 205 deletions.
12 changes: 6 additions & 6 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,33 @@ packageExtensions:
"@types/node": ^20.0.0
esbuild: ~0.20.0
eslint: ^8.57.0
nx: ~18.1.0
nx: ~18.2.0
rxjs: ^7.8.1
typescript: ~5.4.2
"@nx/devkit@^18.0.0":
dependencies:
nx: ~18.1.0
nx: ~18.2.0
typescript: ~5.4.2
"@nx/eslint-plugin@^18.0.0":
dependencies:
"@typescript-eslint/parser": ^7.2.0
eslint: ^8.57.0
"@nx/eslint@^18.0.0":
dependencies:
nx: ~18.1.0
nx: ~18.2.0
"@nx/jest@^18.0.0":
dependencies:
nx: ~18.1.0
nx: ~18.2.0
typescript: ~5.4.2
"@nx/js@^18.0.0":
dependencies:
"@types/node": ^20.0.0
nx: ~18.1.0
nx: ~18.2.0
typescript: ~5.4.2
"@nx/webpack@^18.0.0":
dependencies:
"@types/node": ^20.0.0
nx: ~18.1.0
nx: ~18.2.0
typescript: ~5.4.2
"jsonc-eslint-parser@*":
dependencies:
Expand Down
7 changes: 3 additions & 4 deletions packages/@ama-sdk/create/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
/* eslint-disable no-console */

import { execSync, spawnSync } from 'node:child_process';
import * as url from 'node:url';
import { dirname, join, relative, resolve } from 'node:path';
import { dirname, join, parse, relative, resolve } from 'node:path';
import * as minimist from 'minimist';

const packageManagerEnv = process.env.npm_config_user_agent?.split('/')[0];
Expand Down Expand Up @@ -73,7 +72,7 @@ const schematicArgs = [
const resolveTargetDirectory = resolve(process.cwd(), targetDirectory);

const run = () => {
const isSpecPathUrl = url.URL.canParse(argv['spec-path']);
const isSpecRelativePath = !!argv['spec-path'] && !parse(argv['spec-path']).root;

const runner = process.platform === 'win32' ? `${packageManager}.cmd` : packageManager;
const steps: { args: string[]; cwd?: string; runner?: string }[] = [
Expand All @@ -88,7 +87,7 @@ const run = () => {
binPath,
`${schematicsPackage}:typescript-core`,
...schematicArgs,
'--spec-path', isSpecPathUrl ? argv['spec-path'] : relative(resolveTargetDirectory, resolve(process.cwd(), argv['spec-path']))
'--spec-path', isSpecRelativePath ? relative(resolveTargetDirectory, resolve(process.cwd(), argv['spec-path'])) : argv['spec-path']
],
cwd: resolveTargetDirectory
}] : [])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ export type CodegenTaskOptions = {
* As is, the CodeGenerator does not implement any actual code generation and needs to be extended to be functional
* @see {@link OpenApiCliGenerator}
*/
export class CodeGenerator<T extends CodegenTaskOptions> {
export abstract class CodeGenerator<T extends CodegenTaskOptions> {
/**
* Refers to the name the {@link Task} will be identified in a {@link Rule} ${@link SchematicContext}
*/
protected generatorName = '';
protected abstract generatorName: string;

/**
* Configure the code generation task
Expand Down Expand Up @@ -73,9 +73,7 @@ export class CodeGenerator<T extends CodegenTaskOptions> {
/**
* Returns the generator specific default options
*/
protected getDefaultOptions(): T {
throw new Error('No implementation, please target an implementation');
}
protected abstract getDefaultOptions(): T;

/**
* Returns the schematic that will run the code generator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,28 @@ describe('Typescript Core Generator', () => {
expect(tree.readContent('/readme.md')).toContain('Based on OpenAPI spec 1.0.0');
});

it('should update openapitools file with yaml', async () => {
const runner = new SchematicTestRunner('@ama-sdk/schematics', collectionPath);
const tree = await runner.runSchematic('typescript-core', {
specPath: path.join(__dirname, '..', '..', '..', 'testing', 'MOCK_swagger.yaml')
}, baseTree);
const content: any = tree.readJson('/openapitools.json');

expect(content['generator-cli'].generators['test-sdk-sdk'].inputSpec.endsWith('openapi.yaml')).toBe(true);
expect(tree.exists('/openapi.yaml')).toBe(true);
});

it('should update openapitools file with json', async () => {
const runner = new SchematicTestRunner('@ama-sdk/schematics', collectionPath);
const tree = await runner.runSchematic('typescript-core', {
specPath: path.join(__dirname, '..', '..', '..', 'testing', 'MOCK_swagger.json')
}, baseTree);
const content: any = tree.readJson('/openapitools.json');

expect(content['generator-cli'].generators['test-sdk-sdk'].inputSpec.endsWith('openapi.json')).toBe(true);
expect(tree.exists('/openapi.json')).toBe(true);
});

it('should clean previous install', async () => {
baseTree.create('/src/api/my-apy/test.ts', 'fake module');
const runner = new SchematicTestRunner('@ama-sdk/schematics', collectionPath);
Expand Down
76 changes: 55 additions & 21 deletions packages/@ama-sdk/schematics/schematics/typescript/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import {
url
} from '@angular-devkit/schematics';
import type { Operation, PathObject } from '@ama-sdk/core';
import { readFileSync } from 'node:fs';
import { existsSync, readFileSync } from 'node:fs';
import * as path from 'node:path';
import { URL } from 'node:url';
import * as semver from 'semver';
import * as sway from 'sway';

Expand All @@ -24,6 +25,7 @@ import { OpenApiCliGenerator } from '../../code-generator/open-api-cli-generator

const JAVA_OPTIONS = ['specPath', 'specConfigPath', 'globalProperty', 'outputPath'];
const OPEN_API_TOOLS_OPTIONS = ['generatorName', 'output', 'inputSpec', 'config', 'globalProperty'];
const LOCAL_SPEC_FILENAME = 'openapi';

interface OpenApiToolsGenerator {
/** Location of the OpenAPI spec, as URL or file */
Expand Down Expand Up @@ -166,24 +168,50 @@ const getGeneratorOptions = (tree: Tree, context: SchematicContext, options: NgG
*/
function ngGenerateTypescriptSDKFn(options: NgGenerateTypescriptSDKCoreSchematicsSchema): Rule {

return (tree, context) => {
return async (tree, context) => {
const targetPath = options.directory || '';
const generatorOptions = getGeneratorOptions(tree, context, options);
let isJson = false;
let specDefaultPath = path.posix.join(targetPath, `${LOCAL_SPEC_FILENAME}.json`);
specDefaultPath = existsSync(specDefaultPath) ? specDefaultPath : path.posix.join(targetPath, `${LOCAL_SPEC_FILENAME}.yaml`);
generatorOptions.specPath ||= specDefaultPath;

let specContent!: string;
if (URL.canParse(generatorOptions.specPath) && (new URL(generatorOptions.specPath)).protocol.startsWith('http')) {
specContent = await (await fetch(generatorOptions.specPath)).text();
} else {
specContent = readFileSync(generatorOptions.specPath, {encoding: 'utf-8'}).toString();
}

try {
JSON.parse(specContent);
isJson = true;
} catch (e) {
isJson = false;
}
const defaultFileName = `${LOCAL_SPEC_FILENAME}.${isJson ? 'json' : 'yaml'}`;
specDefaultPath = path.posix.join(targetPath, defaultFileName);
generatorOptions.specPath = specDefaultPath;

if (tree.exists(specDefaultPath)) {
tree.overwrite(specDefaultPath, specContent);
} else {
tree.create(specDefaultPath, specContent);
}

/**
* rule to clear previous SDK generation
*/
const clearGeneratedCode = () => {
const clearGeneratedCode: Rule = () => {
treeGlob(tree, path.posix.join(targetPath, 'src', 'api', '**', '*.ts')).forEach((file) => tree.delete(file));
treeGlob(tree, path.posix.join(targetPath, 'src', 'models', 'base', '**', '!(index).ts')).forEach((file) => tree.delete(file));
treeGlob(tree, path.posix.join(targetPath, 'src', 'spec', '!(operation-adapter|index).ts')).forEach((file) => tree.delete(file));
return tree;
};

const generateOperationFinder = async (): Promise<PathObject[]> => {
const swayOptions = {
definition: path.resolve(generatorOptions.specPath!)
};
const definition: any = isJson ? tree.readJson(specDefaultPath) : (await import('js-yaml')).load(tree.readText(specDefaultPath));
const swayOptions = { definition };
const swayApi = await sway.create(swayOptions);
const extraction = swayApi.getPaths().map((obj) => ({
path: obj.path,
Expand All @@ -196,7 +224,7 @@ function ngGenerateTypescriptSDKFn(options: NgGenerateTypescriptSDKCoreSchematic
/**
* rule to update readme and generate mandatory code source
*/
const generateSource = async () => {
const generateSource: Rule = async () => {
const pathObjects = await generateOperationFinder();
const swayOperationAdapter = `[${pathObjects.map((pathObj) => getPathObjectTemplate(pathObj)).join(',')}]`;

Expand All @@ -212,40 +240,46 @@ function ngGenerateTypescriptSDKFn(options: NgGenerateTypescriptSDKCoreSchematic
};

/**
* Update local swagger spec file
* Update readme version
*/
const updateSpec = () => {
const updateSpecVersion: Rule = () => {
const readmeFile = path.posix.join(targetPath, 'readme.md');
const specContent = readFileSync(generatorOptions.specPath!).toString();
if (tree.exists(readmeFile)) {
const specVersion = /version: *([0-9]+\.[0-9]+\.[0-9]+(?:-[^ ]+)?)/.exec(specContent);

if (specVersion) {
const readmeContent = tree.read(readmeFile)!.toString('utf8');
tree.overwrite(readmeFile, readmeContent.replace(/Based on (OpenAPI|Swagger) spec .*/i, `Based on $1 spec ${specVersion[1]}`));
tree.overwrite(readmeFile, readmeContent.replace(/Based on (.+) spec .*/i, `Based on $1 spec ${specVersion[1]}`));
}
}
return tree;
};

if (tree.exists(path.posix.join(targetPath, 'swagger-spec.yaml'))) {
tree.overwrite(path.posix.join(targetPath, 'swagger-spec.yaml'), specContent);
} else {
tree.create(path.posix.join(targetPath, 'swagger-spec.yaml'), specContent);
const adaptDefaultFile: Rule = () => {
const openApiToolsPath = path.posix.join(targetPath, 'openapitools.json');
if (tree.exists(openApiToolsPath)) {
const openApiTools: any = tree.readJson(openApiToolsPath);
Object.keys(openApiTools['generator-cli']?.generators)
.filter((key) => !openApiTools['generator-cli'].generators[key].inputSpec)
.forEach((key) => openApiTools['generator-cli'].generators[key].inputSpec = `./${defaultFileName}`);
tree.overwrite(openApiToolsPath, JSON.stringify(openApiTools, null, 2));
}
return () => tree;
return tree;
};

const runGeneratorRule = () => {
return () => (new OpenApiCliGenerator(options)).getGeneratorRunSchematic(
const runGeneratorRule: Rule = () => {
return (new OpenApiCliGenerator(options)).getGeneratorRunSchematic(
(options.generatorKey && JAVA_OPTIONS.every((optionName) => options[optionName] === undefined)) ?
{generatorKey: options.generatorKey, ...(options.openapiNormalizer ? {openapiNormalizer: options.openapiNormalizer} : {})} : generatorOptions,
{rootDirectory: options.directory || undefined}
{ generatorKey: options.generatorKey, ...(options.openapiNormalizer ? { openapiNormalizer: options.openapiNormalizer } : {}) } : generatorOptions,
{ rootDirectory: options.directory || undefined }
);
};

return chain([
clearGeneratedCode,
generateSource,
updateSpec,
updateSpecVersion,
adaptDefaultFile,
runGeneratorRule
]);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"<%=projectName%>-<%=projectPackageName%>": {
"generatorName": "typescriptFetch",
"output": ".",
"inputSpec": "./swagger-spec.yaml"
"inputSpec": ""
}
}
}
Expand Down
Loading

0 comments on commit 7c3ce9f

Please sign in to comment.