fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #1172 from hey-api/feat/services-parser

refactor: move out legacy parser functions

authored by

Lubos and committed by
GitHub
25e0331c 4ff2429c

+353 -305
+4 -4
packages/openapi-ts/src/generate/__tests__/class.spec.ts
··· 3 3 import { describe, expect, it, vi } from 'vitest'; 4 4 5 5 import { setConfig } from '../../utils/config'; 6 - import { generateClientClass } from '../class'; 6 + import { generateLegacyClientClass } from '../class'; 7 7 import { mockTemplates, openApi } from './mocks'; 8 8 9 9 vi.mock('node:fs'); 10 10 11 - describe('generateClientClass', () => { 11 + describe('generateLegacyClientClass', () => { 12 12 it('writes to filesystem', async () => { 13 13 setConfig({ 14 14 client: { ··· 33 33 useOptions: true, 34 34 }); 35 35 36 - const client: Parameters<typeof generateClientClass>[2] = { 36 + const client: Parameters<typeof generateLegacyClientClass>[2] = { 37 37 models: [], 38 38 server: 'http://localhost:8080', 39 39 services: [], ··· 41 41 version: 'v1', 42 42 }; 43 43 44 - await generateClientClass(openApi, './dist', client, mockTemplates); 44 + await generateLegacyClientClass(openApi, './dist', client, mockTemplates); 45 45 46 46 expect(writeFileSync).toHaveBeenCalled(); 47 47 });
+9 -9
packages/openapi-ts/src/generate/__tests__/core.spec.ts
··· 4 4 import { beforeEach, describe, expect, it, vi } from 'vitest'; 5 5 6 6 import { setConfig } from '../../utils/config'; 7 - import { generateCore } from '../core'; 7 + import { generateLegacyCore } from '../core'; 8 8 import { mockTemplates } from './mocks'; 9 9 10 10 vi.mock('node:fs'); 11 11 12 - describe('generateCore', () => { 13 - let templates: Parameters<typeof generateCore>[2]; 12 + describe('generateLegacyCore', () => { 13 + let templates: Parameters<typeof generateLegacyCore>[2]; 14 14 beforeEach(() => { 15 15 templates = mockTemplates; 16 16 }); 17 17 18 18 it('writes to filesystem', async () => { 19 - const client: Parameters<typeof generateCore>[1] = { 19 + const client: Parameters<typeof generateLegacyCore>[1] = { 20 20 models: [], 21 21 server: 'http://localhost:8080', 22 22 services: [], ··· 47 47 useOptions: true, 48 48 }); 49 49 50 - await generateCore('/', client, templates); 50 + await generateLegacyCore('/', client, templates); 51 51 52 52 expect(writeFileSync).toHaveBeenCalledWith( 53 53 path.resolve('/', '/OpenAPI.ts'), ··· 76 76 }); 77 77 78 78 it('uses client server value for base', async () => { 79 - const client: Parameters<typeof generateCore>[1] = { 79 + const client: Parameters<typeof generateLegacyCore>[1] = { 80 80 models: [], 81 81 server: 'http://localhost:8080', 82 82 services: [], ··· 107 107 useOptions: true, 108 108 }); 109 109 110 - await generateCore('/', client, templates); 110 + await generateLegacyCore('/', client, templates); 111 111 112 112 expect(templates.core.settings).toHaveBeenCalledWith({ 113 113 $config: config, ··· 118 118 }); 119 119 120 120 it('uses custom value for base', async () => { 121 - const client: Parameters<typeof generateCore>[1] = { 121 + const client: Parameters<typeof generateLegacyCore>[1] = { 122 122 models: [], 123 123 server: 'http://localhost:8080', 124 124 services: [], ··· 150 150 useOptions: true, 151 151 }); 152 152 153 - await generateCore('/', client, templates); 153 + await generateLegacyCore('/', client, templates); 154 154 155 155 expect(templates.core.settings).toHaveBeenCalledWith({ 156 156 $config: config,
+1 -1
packages/openapi-ts/src/generate/__tests__/index.spec.ts
··· 48 48 }), 49 49 }; 50 50 51 - await generateIndexFile({ files }); 51 + generateIndexFile({ files }); 52 52 53 53 files.index.write(); 54 54
+3 -4
packages/openapi-ts/src/generate/__tests__/output.spec.ts
··· 4 4 5 5 import type { Client } from '../../types/client'; 6 6 import { setConfig } from '../../utils/config'; 7 - import { generateOutput } from '../output'; 7 + import { generateLegacyOutput } from '../output'; 8 8 import { mockTemplates, openApi } from './mocks'; 9 9 10 10 vi.mock('node:fs'); 11 11 12 - describe('generateOutput', () => { 12 + describe('generateLegacyOutput', () => { 13 13 it('writes to filesystem', async () => { 14 14 setConfig({ 15 15 client: { ··· 42 42 version: 'v1', 43 43 }; 44 44 45 - await generateOutput({ 45 + await generateLegacyOutput({ 46 46 client, 47 - context: undefined, 48 47 openApi, 49 48 templates: mockTemplates, 50 49 });
+4 -4
packages/openapi-ts/src/generate/__tests__/schemas.spec.ts
··· 6 6 import type { OpenApiV3Schema } from '../../openApi'; 7 7 import type { Files } from '../../types/utils'; 8 8 import { setConfig } from '../../utils/config'; 9 - import { generateSchemas } from '../schemas'; 9 + import { generateLegacySchemas } from '../schemas'; 10 10 import { openApi } from './mocks'; 11 11 12 12 vi.mock('node:fs'); 13 13 14 - describe('generateSchemas', () => { 14 + describe('generateLegacySchemas', () => { 15 15 it('writes to filesystem', async () => { 16 16 setConfig({ 17 17 client: { ··· 50 50 51 51 const files: Files = {}; 52 52 53 - await generateSchemas({ files, openApi }); 53 + await generateLegacySchemas({ files, openApi }); 54 54 55 55 files.schemas.write(); 56 56 ··· 103 103 104 104 const files: Files = {}; 105 105 106 - await generateSchemas({ files, openApi }); 106 + await generateLegacySchemas({ files, openApi }); 107 107 108 108 files.schemas.write(); 109 109
+8 -12
packages/openapi-ts/src/generate/__tests__/services.spec.ts
··· 7 7 import type { Files } from '../../types/utils'; 8 8 import { setConfig } from '../../utils/config'; 9 9 import { TypeScriptFile } from '../files'; 10 - import { generateServices } from '../services'; 10 + import { generateLegacyServices } from '../services'; 11 11 12 12 vi.mock('node:fs'); 13 13 14 - describe('generateServices', () => { 14 + describe('generateLegacyServices', () => { 15 15 it('writes to filesystem', async () => { 16 16 setConfig({ 17 17 client: { ··· 36 36 useOptions: false, 37 37 }); 38 38 39 - const client: Parameters<typeof generateServices>[0]['client'] = { 39 + const client: Parameters<typeof generateLegacyServices>[0]['client'] = { 40 40 models: [], 41 41 server: 'http://localhost:8080', 42 42 services: [ ··· 80 80 name: 'types.ts', 81 81 }); 82 82 83 - await generateServices({ 83 + await generateLegacyServices({ 84 84 client, 85 - context: undefined, 86 85 files, 87 86 }); 88 87 ··· 120 119 summary: null, 121 120 }; 122 121 123 - const client: Parameters<typeof generateServices>[0]['client'] = { 122 + const client: Parameters<typeof generateLegacyServices>[0]['client'] = { 124 123 models: [], 125 124 server: 'http://localhost:8080', 126 125 services: [ ··· 166 165 name: 'types.ts', 167 166 }); 168 167 169 - await generateServices({ 168 + await generateLegacyServices({ 170 169 client, 171 - context: undefined, 172 170 files, 173 171 }); 174 172 ··· 214 212 name: 'types.ts', 215 213 }); 216 214 217 - await generateServices({ 215 + await generateLegacyServices({ 218 216 client, 219 - context: undefined, 220 217 files, 221 218 }); 222 219 ··· 264 261 name: 'types.ts', 265 262 }); 266 263 267 - await generateServices({ 264 + await generateLegacyServices({ 268 265 client, 269 - context: undefined, 270 266 files, 271 267 }); 272 268
+4 -5
packages/openapi-ts/src/generate/__tests__/types.spec.ts
··· 5 5 6 6 import { setConfig } from '../../utils/config'; 7 7 import { TypeScriptFile } from '../files'; 8 - import { generateTypes } from '../types'; 8 + import { generateLegacyTypes } from '../types'; 9 9 10 10 vi.mock('node:fs'); 11 11 12 - describe('generateTypes', () => { 12 + describe('generateLegacyTypes', () => { 13 13 it('writes to filesystem', async () => { 14 14 setConfig({ 15 15 client: { ··· 34 34 useOptions: true, 35 35 }); 36 36 37 - const client: Parameters<typeof generateTypes>[0]['client'] = { 37 + const client: Parameters<typeof generateLegacyTypes>[0]['client'] = { 38 38 models: [ 39 39 { 40 40 $refs: [], ··· 73 73 }), 74 74 }; 75 75 76 - await generateTypes({ 76 + await generateLegacyTypes({ 77 77 client, 78 - context: undefined, 79 78 files, 80 79 }); 81 80
+1 -1
packages/openapi-ts/src/generate/class.ts
··· 18 18 * @param client Client containing models, schemas, and services 19 19 * @param templates The loaded handlebar templates 20 20 */ 21 - export const generateClientClass = async ( 21 + export const generateLegacyClientClass = async ( 22 22 openApi: OpenApi, 23 23 outputPath: string, 24 24 client: Client,
+18 -19
packages/openapi-ts/src/generate/client.ts
··· 1 1 import { copyFileSync } from 'node:fs'; 2 2 import path from 'node:path'; 3 3 4 - import { getConfig, isLegacyClient } from '../utils/config'; 4 + import type { Config } from '../types/config'; 5 5 import { ensureDirSync, relativeModulePath } from './utils'; 6 6 7 + /** 8 + * Returns path to the client module. When using client packages, this will be 9 + * simply the name of the package. When bundling a client, this will be a 10 + * relative path to the bundled client folder. 11 + */ 7 12 export const clientModulePath = ({ 13 + config, 8 14 sourceOutput, 9 15 }: { 16 + config: Config; 10 17 sourceOutput: string; 11 - }) => { 12 - const config = getConfig(); 13 - 18 + }): string => { 14 19 if (config.client.bundle) { 15 20 return relativeModulePath({ 16 21 moduleOutput: 'client', ··· 24 29 export const clientOptionsTypeName = () => 'Options'; 25 30 26 31 /** 27 - * (optional) Creates a `client.ts` file containing the same exports as the 28 - * client package. Creates a `client` directory containing the modules from 29 - * the client package. These files are generated only when `client.bundle` is 30 - * set to true. 32 + * Creates a `client` directory containing the same modules as the client package. 31 33 */ 32 - export const generateClient = async ( 33 - outputPath: string, 34 - moduleName: string, 35 - ) => { 36 - const config = getConfig(); 37 - 38 - if (isLegacyClient(config) || !config.client.bundle) { 39 - return; 40 - } 41 - 34 + export const generateClientBundle = ({ 35 + name, 36 + outputPath, 37 + }: { 38 + name: string; 39 + outputPath: string; 40 + }): void => { 42 41 // create directory for client modules 43 42 const dirPath = path.resolve(outputPath, 'client'); 44 43 ensureDirSync(dirPath); 45 44 46 - const clientModulePath = path.normalize(require.resolve(moduleName)); 45 + const clientModulePath = path.normalize(require.resolve(name)); 47 46 const clientModulePathComponents = clientModulePath.split(path.sep); 48 47 const clientSrcPath = [ 49 48 ...clientModulePathComponents.slice(
+1 -1
packages/openapi-ts/src/generate/core.ts
··· 18 18 * @param client Client containing models, schemas, and services 19 19 * @param templates The loaded handlebar templates 20 20 */ 21 - export const generateCore = async ( 21 + export const generateLegacyCore = async ( 22 22 outputPath: string, 23 23 client: Client, 24 24 templates: Templates,
+1 -5
packages/openapi-ts/src/generate/indexFile.ts
··· 3 3 import { getConfig } from '../utils/config'; 4 4 import { TypeScriptFile } from './files'; 5 5 6 - export const generateIndexFile = async ({ 7 - files, 8 - }: { 9 - files: Files; 10 - }): Promise<void> => { 6 + export const generateIndexFile = ({ files }: { files: Files }): void => { 11 7 const config = getConfig(); 12 8 13 9 files.index = new TypeScriptFile({
+99 -67
packages/openapi-ts/src/generate/output.ts
··· 4 4 import type { OpenApi } from '../openApi'; 5 5 import type { Client } from '../types/client'; 6 6 import type { Files } from '../types/utils'; 7 - import { getConfig } from '../utils/config'; 7 + import { getConfig, isLegacyClient } from '../utils/config'; 8 8 import type { Templates } from '../utils/handlebars'; 9 - import { generateClientClass } from './class'; 10 - import { generateClient } from './client'; 11 - import { generateCore } from './core'; 9 + import { generateLegacyClientClass } from './class'; 10 + import { generateClientBundle } from './client'; 11 + import { generateLegacyCore } from './core'; 12 12 import { generateIndexFile } from './indexFile'; 13 - import { generatePlugins } from './plugins'; 14 - import { generateSchemas } from './schemas'; 15 - import { generateServices } from './services'; 16 - import { generateResponseTransformers } from './transformers'; 17 - import { generateTypes } from './types'; 13 + import { generateLegacyPlugins } from './plugins'; 14 + import { generateLegacySchemas } from './schemas'; 15 + import { generateLegacyServices, generateServices } from './services'; 16 + import { generateLegacyTransformers } from './transformers'; 17 + import { generateLegacyTypes, generateTypes } from './types'; 18 18 19 19 /** 20 20 * Write our OpenAPI client, using the given templates at the given output ··· 22 22 * @param client Client containing models, schemas, and services 23 23 * @param templates Templates wrapper with all loaded Handlebars templates 24 24 */ 25 - export const generateOutput = async ({ 25 + export const generateLegacyOutput = async ({ 26 26 client, 27 - context, 28 27 openApi, 29 28 templates, 30 29 }: { 31 - client: Client | undefined; 32 - context: IRContext | undefined; 30 + client: Client; 33 31 openApi: OpenApi; 34 32 templates: Templates; 35 33 }): Promise<void> => { 36 34 const config = getConfig(); 37 35 38 - // TODO: parser - handle IR 36 + // TODO: parser - move to config.input 39 37 if (client) { 40 38 if (config.services.include && config.services.asClass) { 41 39 const regexp = new RegExp(config.services.include); ··· 54 52 55 53 const files: Files = {}; 56 54 57 - await generateClient(outputPath, config.client.name); 55 + if (!isLegacyClient(config) && config.client.bundle) { 56 + await generateClientBundle({ name: config.client.name, outputPath }); 57 + } 58 58 59 59 // types.gen.ts 60 - await generateTypes({ 61 - client, 62 - context, 63 - files, 64 - }); 60 + await generateLegacyTypes({ client, files }); 65 61 66 62 // schemas.gen.ts 67 - await generateSchemas({ files, openApi }); 63 + await generateLegacySchemas({ files, openApi }); 68 64 69 65 // transformers 70 - // TODO: parser - handle IR 71 - if (client) { 72 - if ( 73 - config.services.export && 74 - client.services.length && 75 - config.types.dates === 'types+transform' 76 - ) { 77 - await generateResponseTransformers({ 78 - client, 79 - onNode: (node) => { 80 - files.types?.add(node); 81 - }, 82 - onRemoveNode: () => { 83 - files.types?.removeNode(); 84 - }, 85 - }); 86 - } 66 + if ( 67 + config.services.export && 68 + client.services.length && 69 + config.types.dates === 'types+transform' 70 + ) { 71 + await generateLegacyTransformers({ 72 + client, 73 + onNode: (node) => { 74 + files.types?.add(node); 75 + }, 76 + onRemoveNode: () => { 77 + files.types?.removeNode(); 78 + }, 79 + }); 87 80 } 88 81 89 82 // services.gen.ts 90 - await generateServices({ 91 - client, 92 - context, 93 - files, 94 - }); 83 + await generateLegacyServices({ client, files }); 95 84 96 85 // deprecated files 97 - if (client) { 98 - await generateClientClass(openApi, outputPath, client, templates); 99 - await generateCore( 100 - path.resolve(config.output.path, 'core'), 101 - client, 102 - templates, 103 - ); 104 - } 86 + await generateLegacyClientClass(openApi, outputPath, client, templates); 87 + await generateLegacyCore( 88 + path.resolve(config.output.path, 'core'), 89 + client, 90 + templates, 91 + ); 105 92 93 + // TODO: parser - remove after moving types, services, transformers, and schemas into plugin 106 94 // index.ts. Any files generated after this won't be included in exports 107 95 // from the index file. 108 - await generateIndexFile({ files }); 96 + generateIndexFile({ files }); 109 97 110 98 // plugins 111 - await generatePlugins({ 112 - client, 113 - context, 114 - files, 115 - }); 99 + await generateLegacyPlugins({ client, files }); 116 100 117 101 Object.entries(files).forEach(([name, file]) => { 118 102 if (config.dryRun) { ··· 125 109 file.write('\n\n'); 126 110 } 127 111 }); 112 + }; 128 113 129 - if (context) { 130 - Object.entries(context.files).forEach(([name, file]) => { 131 - if (config.dryRun) { 132 - return; 133 - } 114 + export const generateOutput = async ({ context }: { context: IRContext }) => { 115 + const outputPath = path.resolve(context.config.output.path); 116 + 117 + if (context.config.client.bundle) { 118 + generateClientBundle({ 119 + name: context.config.client.name, 120 + outputPath, 121 + }); 122 + } 123 + 124 + // types.gen.ts 125 + generateTypes({ context }); 126 + 127 + // schemas.gen.ts 128 + // await generateLegacySchemas({ files, openApi }); 129 + 130 + // transformers 131 + if ( 132 + context.config.services.export && 133 + // client.services.length && 134 + context.config.types.dates === 'types+transform' 135 + ) { 136 + // await generateLegacyTransformers({ 137 + // client, 138 + // onNode: (node) => { 139 + // files.types?.add(node); 140 + // }, 141 + // onRemoveNode: () => { 142 + // files.types?.removeNode(); 143 + // }, 144 + // }); 145 + } 146 + 147 + // services.gen.ts 148 + generateServices({ context }); 149 + 150 + // TODO: parser - remove after moving types, services, transformers, and schemas into plugin 151 + // index.ts. Any files generated after this won't be included in exports 152 + // from the index file. 153 + generateIndexFile({ files: context.files }); 134 154 135 - if (name === 'index') { 136 - file.write(); 137 - } else { 138 - file.write('\n\n'); 139 - } 155 + // plugins 156 + for (const plugin of context.config.plugins) { 157 + plugin.handler({ 158 + context, 159 + plugin: plugin as never, 140 160 }); 141 161 } 162 + 163 + Object.entries(context.files).forEach(([name, file]) => { 164 + if (context.config.dryRun) { 165 + return; 166 + } 167 + 168 + if (name === 'index') { 169 + file.write(); 170 + } else { 171 + file.write('\n\n'); 172 + } 173 + }); 142 174 };
+7 -19
packages/openapi-ts/src/generate/plugins.ts
··· 1 1 import path from 'node:path'; 2 2 3 - import type { IRContext } from '../ir/context'; 4 3 import type { Client } from '../types/client'; 5 4 import type { Files } from '../types/utils'; 6 5 import { getConfig, isLegacyClient } from '../utils/config'; 7 6 import { TypeScriptFile } from './files'; 8 7 9 - export const generatePlugins = async ({ 8 + export const generateLegacyPlugins = async ({ 10 9 client, 11 10 files, 12 - context, 13 11 }: { 14 - client: Client | undefined; 15 - context: IRContext | undefined; 12 + client: Client; 16 13 files: Files; 17 14 }) => { 18 15 const config = getConfig(); ··· 32 29 dir: outputDir, 33 30 name: `${outputParts[outputParts.length - 1]}.ts`, 34 31 }); 35 - 36 - if (context) { 37 - plugin.handler_experimental({ 38 - context, 39 - files, 40 - plugin: plugin as never, 41 - }); 42 - } else if (client) { 43 - plugin.handler({ 44 - client, 45 - files, 46 - plugin: plugin as never, 47 - }); 48 - } 32 + plugin.handlerLegacy({ 33 + client, 34 + files, 35 + plugin: plugin as never, 36 + }); 49 37 } 50 38 };
+1 -1
packages/openapi-ts/src/generate/schemas.ts
··· 68 68 return `${validName}Schema`; 69 69 }; 70 70 71 - export const generateSchemas = async ({ 71 + export const generateLegacySchemas = async ({ 72 72 files, 73 73 openApi, 74 74 }: {
+95 -56
packages/openapi-ts/src/generate/services.ts
··· 36 36 type OnNode = (node: Node) => void; 37 37 type OnImport = (name: string) => void; 38 38 39 + const servicesId = 'services'; 40 + 39 41 export const generateImport = ({ 40 42 meta, 41 43 onImport, ··· 794 796 onNode(statement); 795 797 }; 796 798 797 - const checkPrerequisites = ({ 798 - context, 799 - files, 800 - }: { 801 - context: IRContext | undefined; 802 - files: Files; 803 - }) => { 804 - if (!context) { 805 - const config = getConfig(); 799 + const checkLegacyPrerequisites = ({ files }: { files: Files }) => { 800 + const config = getConfig(); 806 801 807 - if (!config.client.name) { 808 - throw new Error( 809 - '🚫 client needs to be set to generate services - which HTTP client do you want to use?', 810 - ); 811 - } 812 - 813 - if (!files.types) { 814 - throw new Error( 815 - '🚫 types need to be exported to generate services - enable type generation', 816 - ); 817 - } 802 + if (!config.client.name) { 803 + throw new Error( 804 + '🚫 client needs to be set to generate services - which HTTP client do you want to use?', 805 + ); 806 + } 818 807 819 - return; 808 + if (!files.types) { 809 + throw new Error( 810 + '🚫 types need to be exported to generate services - enable type generation', 811 + ); 820 812 } 813 + }; 821 814 815 + const checkPrerequisites = ({ context }: { context: IRContext }) => { 822 816 if (!context.config.client.name) { 823 817 throw new Error( 824 818 '🚫 client needs to be set to generate services - which HTTP client do you want to use?', ··· 832 826 } 833 827 }; 834 828 835 - export const generateServices = async ({ 829 + export const generateLegacyServices = async ({ 836 830 client, 837 - context, 838 831 files, 839 832 }: { 840 - client: Client | undefined; 841 - context: IRContext | undefined; 833 + client: Client; 842 834 files: Files; 843 835 }): Promise<void> => { 844 836 const config = getConfig(); ··· 847 839 return; 848 840 } 849 841 850 - checkPrerequisites({ context, files }); 842 + checkLegacyPrerequisites({ files }); 851 843 852 844 const isLegacy = isLegacyClient(config); 853 845 ··· 861 853 // Import required packages and core files. 862 854 if (!isLegacy) { 863 855 files.services.import({ 864 - module: clientModulePath({ sourceOutput: servicesOutput }), 856 + module: clientModulePath({ config, sourceOutput: servicesOutput }), 865 857 name: 'createClient', 866 858 }); 867 859 files.services.import({ 868 - module: clientModulePath({ sourceOutput: servicesOutput }), 860 + module: clientModulePath({ config, sourceOutput: servicesOutput }), 869 861 name: 'createConfig', 870 862 }); 871 863 files.services.import({ 872 864 asType: true, 873 - module: clientModulePath({ sourceOutput: servicesOutput }), 865 + module: clientModulePath({ config, sourceOutput: servicesOutput }), 874 866 name: clientOptionsTypeName(), 875 867 }); 876 868 } else { ··· 944 936 files.services.add(statement); 945 937 } 946 938 947 - if (client) { 948 - for (const service of client.services) { 949 - processService({ 950 - client, 951 - onClientImport: (imported) => { 952 - files.services.import({ 953 - module: clientModulePath({ sourceOutput: servicesOutput }), 954 - name: imported, 955 - }); 956 - }, 957 - onImport: (imported) => { 958 - files.services.import({ 959 - // this detection could be done safer, but it shouldn't cause any issues 960 - asType: !imported.endsWith('Transformer'), 961 - module: `./${files.types.getName(false)}`, 962 - name: imported, 963 - }); 964 - }, 965 - onNode: (node) => { 966 - files.services.add(node); 967 - }, 968 - service, 969 - }); 970 - } 971 - return; 939 + for (const service of client.services) { 940 + processService({ 941 + client, 942 + onClientImport: (imported) => { 943 + files.services.import({ 944 + module: clientModulePath({ config, sourceOutput: servicesOutput }), 945 + name: imported, 946 + }); 947 + }, 948 + onImport: (imported) => { 949 + files.services.import({ 950 + // this detection could be done safer, but it shouldn't cause any issues 951 + asType: !imported.endsWith('Transformer'), 952 + module: `./${files.types.getName(false)}`, 953 + name: imported, 954 + }); 955 + }, 956 + onNode: (node) => { 957 + files.services.add(node); 958 + }, 959 + service, 960 + }); 972 961 } 962 + }; 973 963 974 - if (!context) { 964 + export const generateServices = ({ context }: { context: IRContext }) => { 965 + // TODO: parser - once services are a plugin, this logic can be simplified 966 + if (!context.config.services.export) { 975 967 return; 976 968 } 977 969 970 + checkPrerequisites({ context }); 971 + 972 + const file = context.createFile({ 973 + id: servicesId, 974 + path: 'services', 975 + }); 976 + const servicesOutput = file.getName(false); 977 + 978 + // import required packages and core files 979 + file.import({ 980 + module: clientModulePath({ 981 + config: context.config, 982 + sourceOutput: servicesOutput, 983 + }), 984 + name: 'createClient', 985 + }); 986 + file.import({ 987 + module: clientModulePath({ 988 + config: context.config, 989 + sourceOutput: servicesOutput, 990 + }), 991 + name: 'createConfig', 992 + }); 993 + file.import({ 994 + asType: true, 995 + module: clientModulePath({ 996 + config: context.config, 997 + sourceOutput: servicesOutput, 998 + }), 999 + name: clientOptionsTypeName(), 1000 + }); 1001 + 1002 + // define client first 1003 + const statement = compiler.constVariable({ 1004 + exportConst: true, 1005 + expression: compiler.callExpression({ 1006 + functionName: 'createClient', 1007 + parameters: [ 1008 + compiler.callExpression({ 1009 + functionName: 'createConfig', 1010 + }), 1011 + ], 1012 + }), 1013 + name: 'client', 1014 + }); 1015 + file.add(statement); 1016 + 978 1017 // TODO: parser - generate services 979 1018 for (const path in context.ir.paths) { 980 1019 const pathItem = context.ir.paths[path as keyof IRPathsObject]; ··· 989 1028 namespace: 'type', 990 1029 }); 991 1030 if (identifier.name) { 992 - files.services.import({ 1031 + file.import({ 993 1032 // this detection could be done safer, but it shouldn't cause any issues 994 1033 asType: !identifier.name.endsWith('Transformer'), 995 1034 module: `./${context.file({ id: 'types' })!.getName(false)}`,
+2 -1
packages/openapi-ts/src/generate/transformers.ts
··· 243 243 }; 244 244 }; 245 245 246 - export const generateResponseTransformers = async ({ 246 + // handles only response transformers for now 247 + export const generateLegacyTransformers = async ({ 247 248 client, 248 249 onNode, 249 250 onRemoveNode,
+20 -24
packages/openapi-ts/src/generate/types.ts
··· 1613 1613 return type; 1614 1614 }; 1615 1615 1616 - export const generateTypes = async ({ 1616 + export const generateLegacyTypes = async ({ 1617 1617 client, 1618 - context, 1619 1618 files, 1620 1619 }: { 1621 - client: Client | undefined; 1622 - context: IRContext | undefined; 1620 + client: Client; 1623 1621 files: Files; 1624 1622 }): Promise<void> => { 1625 - if (client) { 1626 - const config = getConfig(); 1627 - 1628 - if (config.types.export) { 1629 - files.types = new TypeScriptFile({ 1630 - dir: config.output.path, 1631 - name: 'types.ts', 1632 - }); 1633 - } 1623 + const config = getConfig(); 1634 1624 1635 - const onNode: TypesProps['onNode'] = (node) => { 1636 - files.types?.add(node); 1637 - }; 1625 + if (config.types.export) { 1626 + files.types = new TypeScriptFile({ 1627 + dir: config.output.path, 1628 + name: 'types.ts', 1629 + }); 1630 + } 1638 1631 1639 - for (const model of client.models) { 1640 - processModel({ client, model, onNode }); 1641 - } 1632 + const onNode: TypesProps['onNode'] = (node) => { 1633 + files.types?.add(node); 1634 + }; 1642 1635 1643 - processServiceTypes({ client, onNode }); 1644 - return; 1636 + for (const model of client.models) { 1637 + processModel({ client, model, onNode }); 1645 1638 } 1646 1639 1647 - if (!context) { 1648 - return; 1649 - } 1640 + processServiceTypes({ client, onNode }); 1641 + }; 1650 1642 1643 + export const generateTypes = ({ context }: { context: IRContext }): void => { 1651 1644 // TODO: parser - once types are a plugin, this logic can be simplified 1652 1645 if (!context.config.types.export) { 1653 1646 return; ··· 1680 1673 } 1681 1674 } 1682 1675 1676 + // TODO: parser - once types are a plugin, this logic can be simplified 1677 + // provide config option on types to generate path types and services 1678 + // will set it to true if needed 1683 1679 if (context.config.services.export || context.config.types.tree) { 1684 1680 for (const path in context.ir.paths) { 1685 1681 const pathItem = context.ir.paths[path as keyof IRPathsObject];
+9 -9
packages/openapi-ts/src/index.ts
··· 3 3 import { loadConfig } from 'c12'; 4 4 import { sync } from 'cross-spawn'; 5 5 6 - import { generateOutput } from './generate/output'; 6 + import { generateLegacyOutput, generateOutput } from './generate/output'; 7 7 import type { IRContext } from './ir/context'; 8 - import { parse, parseExperimental } from './openApi'; 8 + import { parseExperimental, parseLegacy } from './openApi'; 9 9 import type { ParserConfig } from './openApi/config'; 10 10 import { 11 11 operationFilterFn, ··· 363 363 }); 364 364 } 365 365 366 + // fallback to legacy parser 366 367 if (!context) { 367 - const parsed = parse({ 368 + const parsed = parseLegacy({ 368 369 openApi, 369 370 parserConfig, 370 371 }); ··· 375 376 logClientMessage(); 376 377 377 378 Performance.start('generator'); 378 - await generateOutput({ 379 - client, 380 - context, 381 - openApi, 382 - templates, 383 - }); 379 + if (context) { 380 + await generateOutput({ context }); 381 + } else if (client) { 382 + await generateLegacyOutput({ client, openApi, templates }); 383 + } 384 384 Performance.end('generator'); 385 385 386 386 Performance.start('postprocess');
+1
packages/openapi-ts/src/ir/context.ts
··· 48 48 * to the newly created file. 49 49 */ 50 50 public createFile(file: ContextFile): TypeScriptFile { 51 + // TODO: parser - handle attempt to create duplicate 51 52 const outputParts = file.path.split('/'); 52 53 const outputDir = path.resolve( 53 54 this.config.output.path,
+10 -8
packages/openapi-ts/src/openApi/__tests__/index.spec.ts
··· 1 1 import { afterEach, describe, expect, it, vi } from 'vitest'; 2 2 3 - import { type OpenApi, parse } from '..'; 3 + import { type OpenApi, parseLegacy } from '..'; 4 4 import type { ParserConfig } from '../config'; 5 5 import * as parseV2 from '../v2'; 6 6 import * as parseV3 from '../v3'; ··· 32 32 paths: {}, 33 33 swagger: '2', 34 34 }; 35 - parse({ openApi: spec, parserConfig }); 35 + parseLegacy({ openApi: spec, parserConfig }); 36 36 expect(spy).toHaveBeenCalledWith(spec); 37 37 38 38 const spec2: OpenApi = { ··· 43 43 paths: {}, 44 44 swagger: '2.0', 45 45 }; 46 - parse({ openApi: spec2, parserConfig }); 46 + parseLegacy({ openApi: spec2, parserConfig }); 47 47 expect(spy).toHaveBeenCalledWith(spec2); 48 48 }); 49 49 ··· 58 58 openapi: '3', 59 59 paths: {}, 60 60 }; 61 - parse({ openApi: spec, parserConfig }); 61 + parseLegacy({ openApi: spec, parserConfig }); 62 62 expect(spy).toHaveBeenCalledWith(spec); 63 63 64 64 const spec2: OpenApi = { ··· 69 69 openapi: '3.0', 70 70 paths: {}, 71 71 }; 72 - parse({ openApi: spec2, parserConfig }); 72 + parseLegacy({ openApi: spec2, parserConfig }); 73 73 expect(spy).toHaveBeenCalledWith(spec2); 74 74 75 75 const spec3: OpenApi = { ··· 80 80 openapi: '3.1.0', 81 81 paths: {}, 82 82 }; 83 - parse({ openApi: spec3, parserConfig }); 83 + parseLegacy({ openApi: spec3, parserConfig }); 84 84 expect(spy).toHaveBeenCalledWith(spec3); 85 85 }); 86 86 87 87 it('throws on unknown version', () => { 88 - // @ts-expect-error 89 - expect(() => parse({ openApi: { foo: 'bar' }, parserConfig })).toThrow( 88 + expect(() => 89 + // @ts-expect-error 90 + parseLegacy({ openApi: { foo: 'bar' }, parserConfig }), 91 + ).toThrow( 90 92 `Unsupported OpenAPI specification: ${JSON.stringify({ foo: 'bar' }, null, 2)}`, 91 93 ); 92 94 });
+1 -1
packages/openapi-ts/src/openApi/index.ts
··· 35 35 * all the models, services and schema's we should output. 36 36 * @param openApi The OpenAPI spec that we have loaded from disk. 37 37 */ 38 - export function parse({ 38 + export function parseLegacy({ 39 39 openApi, 40 40 parserConfig, 41 41 }: {
+1 -1
packages/openapi-ts/src/plugins/@hey-api/schemas/config.ts
··· 2 2 3 3 export const defaultConfig: Required<PluginConfig> = { 4 4 handler: () => {}, 5 - handler_experimental: () => {}, 5 + handlerLegacy: () => {}, 6 6 name: '@hey-api/schemas', 7 7 output: 'schemas', 8 8 };
+2 -2
packages/openapi-ts/src/plugins/@hey-api/schemas/types.ts
··· 1 - import type { PluginHandler, PluginHandlerExperimental } from '../../types'; 1 + import type { PluginHandler, PluginLegacyHandler } from '../../types'; 2 2 3 3 interface Config { 4 4 /** ··· 14 14 15 15 export interface PluginConfig extends Config { 16 16 handler: PluginHandler<Config>; 17 - handler_experimental?: PluginHandlerExperimental<Config>; 17 + handlerLegacy: PluginLegacyHandler<Config>; 18 18 } 19 19 20 20 export interface UserConfig extends Omit<Config, 'output'> {}
+1 -1
packages/openapi-ts/src/plugins/@hey-api/services/config.ts
··· 2 2 3 3 export const defaultConfig: Required<PluginConfig> = { 4 4 handler: () => {}, 5 - handler_experimental: () => {}, 5 + handlerLegacy: () => {}, 6 6 name: '@hey-api/services', 7 7 output: 'services', 8 8 };
+2 -2
packages/openapi-ts/src/plugins/@hey-api/services/types.ts
··· 1 - import type { PluginHandler, PluginHandlerExperimental } from '../../types'; 1 + import type { PluginHandler, PluginLegacyHandler } from '../../types'; 2 2 3 3 interface Config { 4 4 /** ··· 14 14 15 15 export interface PluginConfig extends Config { 16 16 handler: PluginHandler<Config>; 17 - handler_experimental?: PluginHandlerExperimental<Config>; 17 + handlerLegacy: PluginLegacyHandler<Config>; 18 18 } 19 19 20 20 export interface UserConfig extends Omit<Config, 'output'> {}
+1 -1
packages/openapi-ts/src/plugins/@hey-api/types/config.ts
··· 2 2 3 3 export const defaultConfig: Required<PluginConfig> = { 4 4 handler: () => {}, 5 - handler_experimental: () => {}, 5 + handlerLegacy: () => {}, 6 6 name: '@hey-api/types', 7 7 output: 'types', 8 8 };
+2 -2
packages/openapi-ts/src/plugins/@hey-api/types/types.ts
··· 1 - import type { PluginHandler, PluginHandlerExperimental } from '../../types'; 1 + import type { PluginHandler, PluginLegacyHandler } from '../../types'; 2 2 3 3 interface Config { 4 4 /** ··· 14 14 15 15 export interface PluginConfig extends Config { 16 16 handler: PluginHandler<Config>; 17 - handler_experimental?: PluginHandlerExperimental<Config>; 17 + handlerLegacy: PluginLegacyHandler<Config>; 18 18 } 19 19 20 20 export interface UserConfig extends Omit<Config, 'output'> {}
+13 -10
packages/openapi-ts/src/plugins/@tanstack/query-core/plugin.ts
··· 32 32 import { getConfig } from '../../../utils/config'; 33 33 import { getServiceName } from '../../../utils/postprocess'; 34 34 import { transformServiceName } from '../../../utils/transform'; 35 - import type { PluginHandler, PluginHandlerExperimental } from '../../types'; 35 + import type { PluginHandler, PluginLegacyHandler } from '../../types'; 36 36 import type { PluginConfig as ReactQueryPluginConfig } from '../react-query'; 37 37 import type { PluginConfig as SolidQueryPluginConfig } from '../solid-query'; 38 38 import type { PluginConfig as SvelteQueryPluginConfig } from '../svelte-query'; ··· 686 686 return queryKeyLiteral; 687 687 }; 688 688 689 - export const handler: PluginHandler< 689 + export const handlerLegacy: PluginLegacyHandler< 690 690 | ReactQueryPluginConfig 691 691 | SolidQueryPluginConfig 692 692 | SvelteQueryPluginConfig ··· 699 699 700 700 file.import({ 701 701 asType: true, 702 - module: clientModulePath({ sourceOutput: plugin.output }), 702 + module: clientModulePath({ config, sourceOutput: plugin.output }), 703 703 name: clientOptionsTypeName(), 704 704 }); 705 705 ··· 1310 1310 } 1311 1311 }; 1312 1312 1313 - export const handler_experimental: PluginHandlerExperimental< 1313 + export const handler: PluginHandler< 1314 1314 | ReactQueryPluginConfig 1315 1315 | SolidQueryPluginConfig 1316 1316 | SvelteQueryPluginConfig 1317 1317 | VueQueryPluginConfig 1318 - > = ({ context, files, plugin }) => { 1319 - checkPrerequisites({ files }); 1318 + > = ({ context, plugin }) => { 1319 + checkPrerequisites({ files: context.files }); 1320 1320 1321 - const file = files[plugin.name]; 1321 + const file = context.createFile({ 1322 + id: plugin.name, 1323 + path: plugin.output, 1324 + }); 1322 1325 1323 1326 // file.import({ 1324 1327 // asType: true, 1325 - // module: clientModulePath({ sourceOutput: plugin.output }), 1328 + // module: clientModulePath({ config: context.config, sourceOutput: plugin.output }), 1326 1329 // name: clientOptionsTypeName(), 1327 1330 // }); 1328 1331 1329 1332 // const typesModulePath = relativeModulePath({ 1330 - // moduleOutput: files.types.getName(false), 1333 + // moduleOutput: context.files.types.getName(false), 1331 1334 // sourceOutput: plugin.output, 1332 1335 // }); 1333 1336 ··· 1528 1531 } 1529 1532 1530 1533 // const servicesModulePath = relativeModulePath({ 1531 - // moduleOutput: files.services.getName(false), 1534 + // moduleOutput: context.files.services.getName(false), 1532 1535 // sourceOutput: plugin.output, 1533 1536 // }); 1534 1537
+2 -2
packages/openapi-ts/src/plugins/@tanstack/react-query/config.ts
··· 1 - import { handler, handler_experimental } from '../query-core/plugin'; 1 + import { handler, handlerLegacy } from '../query-core/plugin'; 2 2 import type { PluginConfig } from './types'; 3 3 4 4 export const defaultConfig: Required<PluginConfig> = { 5 5 handler, 6 - handler_experimental, 6 + handlerLegacy, 7 7 infiniteQueryOptions: true, 8 8 mutationOptions: true, 9 9 name: '@tanstack/react-query',
+2 -2
packages/openapi-ts/src/plugins/@tanstack/react-query/types.ts
··· 1 - import type { PluginHandler, PluginHandlerExperimental } from '../../types'; 1 + import type { PluginHandler, PluginLegacyHandler } from '../../types'; 2 2 3 3 interface Config { 4 4 /** ··· 30 30 31 31 export interface PluginConfig extends Config { 32 32 handler: PluginHandler<Config>; 33 - handler_experimental?: PluginHandlerExperimental<Config>; 33 + handlerLegacy: PluginLegacyHandler<Config>; 34 34 } 35 35 36 36 export interface UserConfig extends Omit<Config, 'output'> {}
+2 -2
packages/openapi-ts/src/plugins/@tanstack/solid-query/config.ts
··· 1 - import { handler, handler_experimental } from '../query-core/plugin'; 1 + import { handler, handlerLegacy } from '../query-core/plugin'; 2 2 import type { PluginConfig } from './types'; 3 3 4 4 export const defaultConfig: Required<PluginConfig> = { 5 5 handler, 6 - handler_experimental, 6 + handlerLegacy, 7 7 infiniteQueryOptions: true, 8 8 mutationOptions: true, 9 9 name: '@tanstack/solid-query',
+2 -2
packages/openapi-ts/src/plugins/@tanstack/solid-query/types.ts
··· 1 - import type { PluginHandler, PluginHandlerExperimental } from '../../types'; 1 + import type { PluginHandler, PluginLegacyHandler } from '../../types'; 2 2 3 3 interface Config { 4 4 /** ··· 30 30 31 31 export interface PluginConfig extends Config { 32 32 handler: PluginHandler<Config>; 33 - handler_experimental?: PluginHandlerExperimental<Config>; 33 + handlerLegacy: PluginLegacyHandler<Config>; 34 34 } 35 35 36 36 export interface UserConfig extends Omit<Config, 'output'> {}
+2 -2
packages/openapi-ts/src/plugins/@tanstack/svelte-query/config.ts
··· 1 - import { handler, handler_experimental } from '../query-core/plugin'; 1 + import { handler, handlerLegacy } from '../query-core/plugin'; 2 2 import type { PluginConfig } from './types'; 3 3 4 4 export const defaultConfig: Required<PluginConfig> = { 5 5 handler, 6 - handler_experimental, 6 + handlerLegacy, 7 7 infiniteQueryOptions: true, 8 8 mutationOptions: true, 9 9 name: '@tanstack/svelte-query',
+2 -2
packages/openapi-ts/src/plugins/@tanstack/svelte-query/types.ts
··· 1 - import type { PluginHandler, PluginHandlerExperimental } from '../../types'; 1 + import type { PluginHandler, PluginLegacyHandler } from '../../types'; 2 2 3 3 interface Config { 4 4 /** ··· 30 30 31 31 export interface PluginConfig extends Config { 32 32 handler: PluginHandler<Config>; 33 - handler_experimental?: PluginHandlerExperimental<Config>; 33 + handlerLegacy: PluginLegacyHandler<Config>; 34 34 } 35 35 36 36 export interface UserConfig extends Omit<Config, 'output'> {}
+2 -2
packages/openapi-ts/src/plugins/@tanstack/vue-query/config.ts
··· 1 - import { handler, handler_experimental } from '../query-core/plugin'; 1 + import { handler, handlerLegacy } from '../query-core/plugin'; 2 2 import type { PluginConfig } from './types'; 3 3 4 4 export const defaultConfig: Required<PluginConfig> = { 5 5 handler, 6 - handler_experimental, 6 + handlerLegacy, 7 7 infiniteQueryOptions: true, 8 8 mutationOptions: true, 9 9 name: '@tanstack/vue-query',
+2 -2
packages/openapi-ts/src/plugins/@tanstack/vue-query/types.ts
··· 1 - import type { PluginHandler, PluginHandlerExperimental } from '../../types'; 1 + import type { PluginHandler, PluginLegacyHandler } from '../../types'; 2 2 3 3 interface Config { 4 4 /** ··· 30 30 31 31 export interface PluginConfig extends Config { 32 32 handler: PluginHandler<Config>; 33 - handler_experimental?: PluginHandlerExperimental<Config>; 33 + handlerLegacy: PluginLegacyHandler<Config>; 34 34 } 35 35 36 36 export interface UserConfig extends Omit<Config, 'output'> {}
+5 -8
packages/openapi-ts/src/plugins/types.ts
··· 2 2 import type { Client } from '../types/client'; 3 3 import type { Files } from '../types/utils'; 4 4 5 - export type PluginHandler<PluginConfig> = (args: { 5 + export type PluginLegacyHandler<PluginConfig> = (args: { 6 6 client: Client; 7 7 files: Files; 8 - plugin: Omit<Required<PluginConfig>, 'handler' | 'handler_experimental'>; 8 + plugin: Omit<Required<PluginConfig>, 'handler' | 'handlerLegacy'>; 9 9 }) => void; 10 10 11 - export type PluginHandlerExperimental<PluginConfig> = (args: { 11 + export type PluginHandler<PluginConfig> = (args: { 12 12 context: IRContext; 13 - files: Files; 14 - plugin: Omit<Required<PluginConfig>, 'handler' | 'handler_experimental'>; 13 + plugin: Omit<Required<PluginConfig>, 'handler' | 'handlerLegacy'>; 15 14 }) => void; 16 15 17 16 type KeyTypes = string | number | symbol; ··· 28 27 > = { 29 28 [K in U]: { 30 29 handler: PluginHandler<Required<Extract<T, { name: K }>>>; 31 - handler_experimental?: PluginHandlerExperimental< 32 - Required<Extract<T, { name: K }>> 33 - >; 30 + handlerLegacy: PluginLegacyHandler<Required<Extract<T, { name: K }>>>; 34 31 name: string; 35 32 output?: string; 36 33 };
+3 -3
packages/openapi-ts/src/plugins/zod/config.ts
··· 1 - import { handler } from './plugin'; 1 + import { handlerLegacy } from './plugin'; 2 2 import type { PluginConfig } from './types'; 3 3 4 4 export const defaultConfig: Required<PluginConfig> = { 5 - handler, 6 - handler_experimental: () => {}, 5 + handler: () => {}, 6 + handlerLegacy, 7 7 name: 'zod', 8 8 output: 'zod', 9 9 };
+2 -2
packages/openapi-ts/src/plugins/zod/plugin.ts
··· 1 1 import { compiler } from '../../compiler'; 2 2 import type { TypeScriptFile } from '../../generate/files'; 3 3 import type { Client, Model } from '../../types/client'; 4 - import type { PluginHandler } from '../types'; 4 + import type { PluginLegacyHandler } from '../types'; 5 5 import type { PluginConfig } from './types'; 6 6 7 7 interface TypesProps { ··· 189 189 } 190 190 }; 191 191 192 - export const handler: PluginHandler<PluginConfig> = ({ 192 + export const handlerLegacy: PluginLegacyHandler<PluginConfig> = ({ 193 193 client, 194 194 files, 195 195 plugin,
+2 -2
packages/openapi-ts/src/plugins/zod/types.ts
··· 1 - import type { PluginHandler, PluginHandlerExperimental } from '../types'; 1 + import type { PluginHandler, PluginLegacyHandler } from '../types'; 2 2 3 3 interface Config { 4 4 /** ··· 14 14 15 15 export interface PluginConfig extends Config { 16 16 handler: PluginHandler<Config>; 17 - handler_experimental?: PluginHandlerExperimental<Config>; 17 + handlerLegacy: PluginLegacyHandler<Config>; 18 18 } 19 19 20 20 export interface UserConfig extends Omit<Config, 'output'> {}
+3 -3
packages/openapi-ts/src/utils/__tests__/postprocess.spec.ts
··· 1 1 import { describe, expect, it } from 'vitest'; 2 2 3 - import { parse } from '../../openApi'; 3 + import { parseLegacy } from '../../openApi'; 4 4 import type { ParserConfig } from '../../openApi/config'; 5 5 import { getServiceName, postProcessClient } from '../postprocess'; 6 6 ··· 35 35 36 36 describe('getServices', () => { 37 37 it('should create a unnamed service if tags are empty', () => { 38 - const parserClient = parse({ 38 + const parserClient = parseLegacy({ 39 39 openApi: { 40 40 info: { 41 41 title: 'x', ··· 69 69 70 70 describe('getServices', () => { 71 71 it('should create a unnamed service if tags are empty', () => { 72 - const parserClient = parse({ 72 + const parserClient = parseLegacy({ 73 73 openApi: { 74 74 info: { 75 75 title: 'x',
+1 -1
packages/openapi-ts/test/sample.cjs
··· 9 9 name: '@hey-api/client-fetch', 10 10 }, 11 11 // debug: true, 12 - // experimental_parser: true, 12 + experimental_parser: true, 13 13 // input: './test/spec/v3-transforms.json', 14 14 // input: './test/spec/v3.json', 15 15 input: './test/spec/3.1.0/full.json',