@@ -8,45 +8,139 @@ import * as TJS from 'typescript-json-schema';
8
8
import type { PartialConfig } from './getConfig' ;
9
9
import { getConfig } from './getConfig' ;
10
10
11
- export default ( configs ?: PartialConfig ) =>
12
- getConfig ( configs ) . forEach ( ( config ) => {
13
- const tree = getDirentTree ( config . input ) ;
14
-
15
- const createFilePaths = ( tree : DirentTree ) : string [ ] => {
16
- return tree . children . flatMap ( ( child ) =>
17
- child . isDir ? createFilePaths ( child . tree ) : tree . path ,
18
- ) ;
19
- } ;
11
+ export const toOpenAPI = ( params : {
12
+ input : string ;
13
+ template ?: OpenAPIV3_1 . Document | string ;
14
+ } ) : string => {
15
+ const tree = getDirentTree ( params . input ) ;
16
+
17
+ const createFilePaths = ( tree : DirentTree ) : string [ ] => {
18
+ return tree . children . flatMap ( ( child ) =>
19
+ child . isDir ? createFilePaths ( child . tree ) : tree . path ,
20
+ ) ;
21
+ } ;
20
22
21
- const paths = createFilePaths ( tree ) ;
23
+ const paths = createFilePaths ( tree ) ;
22
24
23
- const typeFile = `${ paths
24
- . map (
25
- ( p , i ) => `import type { Methods as Methods${ i } } from '${ p . replace ( config . input , '.' ) } '` ,
26
- )
27
- . join ( '\n' ) }
25
+ const typeFile = `${ paths
26
+ . map ( ( p , i ) => `import type { Methods as Methods${ i } } from '${ p . replace ( params . input , '.' ) } '` )
27
+ . join ( '\n' ) }
28
28
29
29
type AllMethods = [${ paths . map ( ( _ , i ) => `Methods${ i } ` ) . join ( ', ' ) } ]` ;
30
30
31
- const typeFilePath = join ( config . input , '@tmp-type.ts' ) ;
31
+ const typeFilePath = join ( params . input , `@openapi-${ Date . now ( ) } .ts` ) ;
32
+
33
+ writeFileSync ( typeFilePath , typeFile , 'utf8' ) ;
34
+
35
+ const compilerOptions : TJS . CompilerOptions = {
36
+ strictNullChecks : true ,
37
+ rootDir : process . cwd ( ) ,
38
+ baseUrl : process . cwd ( ) ,
39
+ // @ts -expect-error dont match ScriptTarget
40
+ target : 'ES2022' ,
41
+ } ;
42
+
43
+ const program = TJS . getProgramFromFiles ( [ typeFilePath ] , compilerOptions ) ;
44
+ const schema = TJS . generateSchema ( program , 'AllMethods' , { required : true } ) ;
45
+ const doc : OpenAPIV3_1 . Document = {
46
+ ...( typeof params . template === 'string'
47
+ ? JSON . parse ( readFileSync ( params . template , 'utf8' ) )
48
+ : params . template ) ,
49
+ paths : { } ,
50
+ components : { schemas : schema ?. definitions as any } ,
51
+ } ;
52
+
53
+ unlinkSync ( typeFilePath ) ;
54
+
55
+ ( schema ?. items as TJS . Definition [ ] ) ?. forEach ( ( def , i ) => {
56
+ const parameters : { name : string ; in : 'path' | 'query' ; required : boolean ; schema : any } [ ] = [ ] ;
57
+
58
+ let path = paths [ i ] ;
59
+
60
+ if ( path . includes ( '/_' ) ) {
61
+ parameters . push (
62
+ ...path
63
+ . split ( '/' )
64
+ . filter ( ( p ) => p . startsWith ( '_' ) )
65
+ . map ( ( p ) => ( {
66
+ name : p . slice ( 1 ) . split ( '@' ) [ 0 ] ,
67
+ in : 'path' as const ,
68
+ required : true ,
69
+ schema : [ 'number' , 'string' ] . includes ( p . slice ( 1 ) . split ( '@' ) [ 1 ] )
70
+ ? { type : p . slice ( 1 ) . split ( '@' ) [ 1 ] }
71
+ : { anyOf : [ { type : 'number' } , { type : 'string' } ] } ,
72
+ } ) ) ,
73
+ ) ;
32
74
33
- writeFileSync ( typeFilePath , typeFile , 'utf8' ) ;
75
+ path = path . replace ( / \/ _ ( [ ^ / @ ] + ) ( @ [ ^ / ] + ) ? / g, '/{$1}' ) ;
76
+ }
77
+
78
+ path = path . replace ( params . input , '' ) || '/' ;
79
+
80
+ doc . paths ! [ path ] = Object . entries ( def . properties ! ) . reduce ( ( dict , [ method , val ] ) => {
81
+ const params = [ ...parameters ] ;
82
+ // const required = ((val as TJS.Definition).required ?? []) as (keyof AspidaMethodParams)[];
83
+ const props = ( val as TJS . Definition ) . properties as {
84
+ [ Key in keyof AspidaMethodParams ] : TJS . Definition ;
85
+ } ;
86
+
87
+ if ( props . query ) {
88
+ const def = ( props . query . properties ??
89
+ schema ?. definitions ?. [ props . query . $ref ! . split ( '/' ) . at ( - 1 ) ! ] ) as TJS . Definition ;
90
+
91
+ params . push (
92
+ ...Object . entries ( def ) . map ( ( [ name , value ] ) => ( {
93
+ name,
94
+ in : 'query' as const ,
95
+ required : props . query ?. required ?. includes ( name ) ?? false ,
96
+ schema : value ,
97
+ } ) ) ,
98
+ ) ;
99
+ }
34
100
35
- const compilerOptions : TJS . CompilerOptions = {
36
- strictNullChecks : true ,
37
- rootDir : process . cwd ( ) ,
38
- baseUrl : process . cwd ( ) ,
39
- // @ts -expect-error dont match ScriptTarget
40
- target : 'ES2022' ,
41
- } ;
101
+ const reqFormat = props . reqFormat ?. $ref ;
102
+ const reqContentType =
103
+ ( ( props . reqHeaders ?. properties ?. [ 'content-type' ] as TJS . Definition ) ?. const ??
104
+ reqFormat ?. includes ( 'FormData' ) )
105
+ ? 'multipart/form-data'
106
+ : reqFormat ?. includes ( 'URLSearchParams' )
107
+ ? 'application/x-www-form-urlencoded'
108
+ : 'application/json' ;
109
+ const resContentType =
110
+ ( ( props . resHeaders ?. properties ?. [ 'content-type' ] as TJS . Definition ) ?. const as string ) ??
111
+ 'application/json' ;
112
+
113
+ return {
114
+ ...dict ,
115
+ [ method ] : {
116
+ tags : path === '/' ? undefined : path . split ( '/{' ) [ 0 ] . replace ( / ^ \/ / , '' ) . split ( '/' ) ,
117
+ parameters : params ,
118
+ requestBody :
119
+ props . reqBody === undefined
120
+ ? undefined
121
+ : { content : { [ reqContentType ] : { schema : props . reqBody } } } ,
122
+ responses :
123
+ props . resBody === undefined
124
+ ? undefined
125
+ : {
126
+ [ ( props . status ?. const as string ) ?? '2XX' ] : {
127
+ content : { [ resContentType ] : { schema : props . resBody } } ,
128
+ } ,
129
+ } ,
130
+ } ,
131
+ } ;
132
+ } , { } ) ;
133
+ } ) ;
134
+
135
+ return JSON . stringify ( doc , null , 2 ) . replaceAll ( '#/definitions' , '#/components/schemas' ) ;
136
+ } ;
42
137
43
- const program = TJS . getProgramFromFiles ( [ typeFilePath ] , compilerOptions ) ;
44
- const schema = TJS . generateSchema ( program , 'AllMethods' , { required : true } ) ;
138
+ export default ( configs ?: PartialConfig ) =>
139
+ getConfig ( configs ) . forEach ( ( config ) => {
45
140
const existingDoc : OpenAPIV3_1 . Document | undefined = existsSync ( config . output )
46
141
? JSON . parse ( readFileSync ( config . output , 'utf8' ) )
47
142
: undefined ;
48
-
49
- const doc : OpenAPIV3_1 . Document = {
143
+ const template : OpenAPIV3_1 . Document = {
50
144
openapi : '3.1.0' ,
51
145
info : {
52
146
title : `${ config . output . split ( '/' ) . at ( - 1 ) ?. replace ( '.json' , '' ) } api` ,
@@ -55,95 +149,8 @@ type AllMethods = [${paths.map((_, i) => `Methods${i}`).join(', ')}]`;
55
149
servers : config . baseURL ? [ { url : config . baseURL } ] : undefined ,
56
150
...existingDoc ,
57
151
paths : { } ,
58
- components : { schemas : schema ?. definitions as any } ,
152
+ components : { } ,
59
153
} ;
60
154
61
- unlinkSync ( typeFilePath ) ;
62
-
63
- ( schema ?. items as TJS . Definition [ ] ) ?. forEach ( ( def , i ) => {
64
- const parameters : { name : string ; in : 'path' | 'query' ; required : boolean ; schema : any } [ ] =
65
- [ ] ;
66
-
67
- let path = paths [ i ] ;
68
-
69
- if ( path . includes ( '/_' ) ) {
70
- parameters . push (
71
- ...path
72
- . split ( '/' )
73
- . filter ( ( p ) => p . startsWith ( '_' ) )
74
- . map ( ( p ) => ( {
75
- name : p . slice ( 1 ) . split ( '@' ) [ 0 ] ,
76
- in : 'path' as const ,
77
- required : true ,
78
- schema : [ 'number' , 'string' ] . includes ( p . slice ( 1 ) . split ( '@' ) [ 1 ] )
79
- ? { type : p . slice ( 1 ) . split ( '@' ) [ 1 ] }
80
- : { anyOf : [ { type : 'number' } , { type : 'string' } ] } ,
81
- } ) ) ,
82
- ) ;
83
-
84
- path = path . replace ( / \/ _ ( [ ^ / @ ] + ) ( @ [ ^ / ] + ) ? / g, '/{$1}' ) ;
85
- }
86
-
87
- path = path . replace ( config . input , '' ) || '/' ;
88
-
89
- doc . paths ! [ path ] = Object . entries ( def . properties ! ) . reduce ( ( dict , [ method , val ] ) => {
90
- const params = [ ...parameters ] ;
91
- // const required = ((val as TJS.Definition).required ?? []) as (keyof AspidaMethodParams)[];
92
- const props = ( val as TJS . Definition ) . properties as {
93
- [ Key in keyof AspidaMethodParams ] : TJS . Definition ;
94
- } ;
95
-
96
- if ( props . query ) {
97
- const def = ( props . query . properties ??
98
- schema ?. definitions ?. [ props . query . $ref ! . split ( '/' ) . at ( - 1 ) ! ] ) as TJS . Definition ;
99
-
100
- params . push (
101
- ...Object . entries ( def ) . map ( ( [ name , value ] ) => ( {
102
- name,
103
- in : 'query' as const ,
104
- required : props . query ?. required ?. includes ( name ) ?? false ,
105
- schema : value ,
106
- } ) ) ,
107
- ) ;
108
- }
109
-
110
- const reqFormat = props . reqFormat ?. $ref ;
111
- const reqContentType =
112
- ( ( props . reqHeaders ?. properties ?. [ 'content-type' ] as TJS . Definition ) ?. const ??
113
- reqFormat ?. includes ( 'FormData' ) )
114
- ? 'multipart/form-data'
115
- : reqFormat ?. includes ( 'URLSearchParams' )
116
- ? 'application/x-www-form-urlencoded'
117
- : 'application/json' ;
118
- const resContentType =
119
- ( ( props . resHeaders ?. properties ?. [ 'content-type' ] as TJS . Definition ) ?. const as string ) ??
120
- 'application/json' ;
121
-
122
- return {
123
- ...dict ,
124
- [ method ] : {
125
- tags : path === '/' ? undefined : path . split ( '/{' ) [ 0 ] . replace ( / ^ \/ / , '' ) . split ( '/' ) ,
126
- parameters : params ,
127
- requestBody :
128
- props . reqBody === undefined
129
- ? undefined
130
- : { content : { [ reqContentType ] : { schema : props . reqBody } } } ,
131
- responses :
132
- props . resBody === undefined
133
- ? undefined
134
- : {
135
- [ ( props . status ?. const as string ) ?? '2XX' ] : {
136
- content : { [ resContentType ] : { schema : props . resBody } } ,
137
- } ,
138
- } ,
139
- } ,
140
- } ;
141
- } , { } ) ;
142
- } ) ;
143
-
144
- writeFileSync (
145
- config . output ,
146
- JSON . stringify ( doc , null , 2 ) . replaceAll ( '#/definitions' , '#/components/schemas' ) ,
147
- 'utf8' ,
148
- ) ;
155
+ writeFileSync ( config . output , toOpenAPI ( { input : config . input , template } ) , 'utf8' ) ;
149
156
} ) ;
0 commit comments