@@ -2,7 +2,13 @@ import fsp from 'fs/promises';
2
2
3
3
import yaml from 'js-yaml' ;
4
4
5
- import { checkForCache , exists , run , toAbsolutePath } from './common' ;
5
+ import {
6
+ BUNDLE_WITH_DOC ,
7
+ checkForCache ,
8
+ exists ,
9
+ run ,
10
+ toAbsolutePath ,
11
+ } from './common' ;
6
12
import { createSpinner } from './oraLog' ;
7
13
import type { Spec } from './types' ;
8
14
@@ -13,10 +19,17 @@ const ALGOLIASEARCH_LITE_OPERATIONS = [
13
19
'post' ,
14
20
] ;
15
21
16
- async function propagateTagsToOperations (
17
- bundledPath : string ,
18
- client : string
19
- ) : Promise < boolean > {
22
+ async function propagateTagsToOperations ( {
23
+ bundledPath,
24
+ withDoc,
25
+ clientName,
26
+ alias,
27
+ } : {
28
+ bundledPath : string ;
29
+ withDoc : boolean ;
30
+ clientName : string ;
31
+ alias ?: string ;
32
+ } ) : Promise < void > {
20
33
if ( ! ( await exists ( bundledPath ) ) ) {
21
34
throw new Error ( `Bundled file not found ${ bundledPath } .` ) ;
22
35
}
@@ -25,9 +38,41 @@ async function propagateTagsToOperations(
25
38
await fsp . readFile ( bundledPath , 'utf8' )
26
39
) as Spec ;
27
40
28
- for ( const pathMethods of Object . values ( bundledSpec . paths ) ) {
29
- for ( const specMethod of Object . values ( pathMethods ) ) {
30
- specMethod . tags = [ client ] ;
41
+ let bundledDocSpec : Spec | undefined ;
42
+ if ( withDoc ) {
43
+ bundledDocSpec = yaml . load ( await fsp . readFile ( bundledPath , 'utf8' ) ) as Spec ;
44
+ }
45
+ const tagsDefinitions = bundledSpec . tags ;
46
+
47
+ for ( const [ pathKey , pathMethods ] of Object . entries ( bundledSpec . paths ) ) {
48
+ for ( const [ method , specMethod ] of Object . entries ( pathMethods ) ) {
49
+ // In the main bundle we need to have only the clientName
50
+ // because open-api-generator will use this to determine the name of the client
51
+ specMethod . tags = [ clientName ] ;
52
+
53
+ if (
54
+ ! withDoc ||
55
+ ! bundledDocSpec ||
56
+ ! bundledDocSpec . paths [ pathKey ] [ method ] . tags
57
+ ) {
58
+ continue ;
59
+ }
60
+
61
+ // Checks that specified tags are well defined at root level
62
+ for ( const tag of bundledDocSpec . paths [ pathKey ] [ method ] . tags ) {
63
+ if ( tag === clientName || ( alias && tag === alias ) ) {
64
+ return ;
65
+ }
66
+
67
+ const tagExists = tagsDefinitions
68
+ ? tagsDefinitions . find ( ( t ) => t . name === tag )
69
+ : null ;
70
+ if ( ! tagExists ) {
71
+ throw new Error (
72
+ `Tag "${ tag } " in "client[${ clientName } ] -> operation[${ specMethod . operationId } ]" is not defined`
73
+ ) ;
74
+ }
75
+ }
31
76
}
32
77
}
33
78
@@ -38,32 +83,38 @@ async function propagateTagsToOperations(
38
83
} )
39
84
) ;
40
85
41
- return true ;
86
+ if ( withDoc ) {
87
+ const pathToDoc = bundledPath . replace ( '.yml' , '.doc.yml' ) ;
88
+ await fsp . writeFile (
89
+ pathToDoc ,
90
+ yaml . dump ( bundledDocSpec , {
91
+ noRefs : true ,
92
+ } )
93
+ ) ;
94
+ }
42
95
}
43
96
44
97
async function lintCommon ( verbose : boolean , useCache : boolean ) : Promise < void > {
98
+ const spinner = createSpinner ( 'linting common spec' , verbose ) . start ( ) ;
99
+
45
100
let hash = '' ;
46
101
const cacheFile = toAbsolutePath ( `specs/dist/common.cache` ) ;
47
102
if ( useCache ) {
48
- const { cacheExists, hash : newCache } = await checkForCache (
49
- {
50
- job : 'common specs' ,
51
- folder : toAbsolutePath ( 'specs/' ) ,
52
- generatedFiles : [ ] ,
53
- filesToCache : [ 'common' ] ,
54
- cacheFile,
55
- } ,
56
- verbose
57
- ) ;
103
+ const { cacheExists, hash : newCache } = await checkForCache ( {
104
+ folder : toAbsolutePath ( 'specs/' ) ,
105
+ generatedFiles : [ ] ,
106
+ filesToCache : [ 'common' ] ,
107
+ cacheFile,
108
+ } ) ;
58
109
59
110
if ( cacheExists ) {
111
+ spinner . succeed ( "job skipped, cache found for 'common' spec" ) ;
60
112
return ;
61
113
}
62
114
63
115
hash = newCache ;
64
116
}
65
117
66
- const spinner = createSpinner ( 'linting common spec' , verbose ) . start ( ) ;
67
118
await run ( `yarn specs:lint common` , { verbose } ) ;
68
119
69
120
if ( hash ) {
@@ -78,17 +129,21 @@ async function lintCommon(verbose: boolean, useCache: boolean): Promise<void> {
78
129
* Creates a lite search spec with the `ALGOLIASEARCH_LITE_OPERATIONS` methods
79
130
* from the `search` spec.
80
131
*/
81
- async function buildLiteSpec (
82
- spec : string ,
83
- bundledPath : string ,
84
- outputFormat : string ,
85
- verbose : boolean
86
- ) : Promise < void > {
87
- const searchSpec = yaml . load (
132
+ async function buildLiteSpec ( {
133
+ spec,
134
+ bundledPath,
135
+ outputFormat,
136
+ } : {
137
+ spec : string ;
138
+ bundledPath : string ;
139
+ outputFormat : string ;
140
+ } ) : Promise < void > {
141
+ const parsed = yaml . load (
88
142
await fsp . readFile ( toAbsolutePath ( bundledPath ) , 'utf8' )
89
143
) as Spec ;
90
144
91
- searchSpec . paths = Object . entries ( searchSpec . paths ) . reduce (
145
+ // Filter methods.
146
+ parsed . paths = Object . entries ( parsed . paths ) . reduce (
92
147
( acc , [ path , operations ] ) => {
93
148
for ( const [ method , operation ] of Object . entries ( operations ) ) {
94
149
if (
@@ -105,95 +160,97 @@ async function buildLiteSpec(
105
160
) ;
106
161
107
162
const liteBundledPath = `specs/bundled/${ spec } .${ outputFormat } ` ;
108
- await fsp . writeFile ( toAbsolutePath ( liteBundledPath ) , yaml . dump ( searchSpec ) ) ;
109
-
110
- if (
111
- ! ( await propagateTagsToOperations ( toAbsolutePath ( liteBundledPath ) , spec ) )
112
- ) {
113
- throw new Error (
114
- `Unable to propage tags to operations for \`${ spec } \` spec.`
115
- ) ;
116
- }
163
+ await fsp . writeFile ( toAbsolutePath ( liteBundledPath ) , yaml . dump ( parsed ) ) ;
117
164
118
- await run ( `yarn specs:fix bundled/${ spec } .${ outputFormat } ` , {
119
- verbose,
165
+ await propagateTagsToOperations ( {
166
+ bundledPath : toAbsolutePath ( liteBundledPath ) ,
167
+ clientName : spec ,
168
+ // Lite does not need documentation because it's just a subset
169
+ withDoc : false ,
120
170
} ) ;
121
171
}
122
172
173
+ /**
174
+ * Build spec file.
175
+ */
123
176
async function buildSpec (
124
177
spec : string ,
125
178
outputFormat : string ,
126
179
verbose : boolean ,
127
180
useCache : boolean
128
181
) : Promise < void > {
129
- const shouldBundleLiteSpec = spec === 'algoliasearch-lite' ;
130
- const client = shouldBundleLiteSpec ? 'search' : spec ;
131
- const cacheFile = toAbsolutePath ( `specs/dist/${ client } .cache` ) ;
182
+ const isLite = spec === 'algoliasearch-lite' ;
183
+ // In case of lite we use a the `search` spec as a base because only its bundled form exists.
184
+ const specBase = isLite ? 'search' : spec ;
185
+ const cacheFile = toAbsolutePath ( `specs/dist/${ spec } .cache` ) ;
132
186
let hash = '' ;
133
187
134
- createSpinner ( `'${ client } ' spec` , verbose ) . start ( ) . info ( ) ;
188
+ const spinner = createSpinner ( `starting '${ spec } ' spec` , verbose ) . start ( ) ;
135
189
136
190
if ( useCache ) {
137
- const generatedFiles = [ `bundled/ ${ client } .yml` ] ;
138
-
139
- if ( shouldBundleLiteSpec ) {
140
- generatedFiles . push ( `bundled/${ spec } .yml` ) ;
191
+ spinner . text = `checking cache for ' ${ specBase } '` ;
192
+ const generatedFiles = [ `bundled/ ${ spec } .yml` ] ;
193
+ if ( ! isLite && BUNDLE_WITH_DOC ) {
194
+ generatedFiles . push ( `bundled/${ spec } .doc. yml` ) ;
141
195
}
142
196
143
- const { cacheExists, hash : newCache } = await checkForCache (
144
- {
145
- job : `'${ client } ' specs` ,
146
- folder : toAbsolutePath ( 'specs/' ) ,
147
- generatedFiles,
148
- filesToCache : [ client , 'common' ] ,
149
- cacheFile,
150
- } ,
151
- verbose
152
- ) ;
197
+ const { cacheExists, hash : newCache } = await checkForCache ( {
198
+ folder : toAbsolutePath ( 'specs/' ) ,
199
+ generatedFiles,
200
+ filesToCache : [ specBase , 'common' ] ,
201
+ cacheFile,
202
+ } ) ;
153
203
154
204
if ( cacheExists ) {
205
+ spinner . succeed ( `job skipped, cache found for '${ specBase } '` ) ;
155
206
return ;
156
207
}
157
208
209
+ spinner . text = `cache not found for '${ specBase } '` ;
158
210
hash = newCache ;
159
211
}
160
212
161
- const spinner = createSpinner ( `building ${ client } spec` , verbose ) . start ( ) ;
162
- const bundledPath = `specs/bundled/${ client } .${ outputFormat } ` ;
213
+ // First linting the base
214
+ spinner . text = `linting '${ spec } ' spec` ;
215
+ await run ( `yarn specs:fix ${ specBase } ` , { verbose } ) ;
216
+
217
+ // Then bundle the file
218
+ const bundledPath = `specs/bundled/${ spec } .${ outputFormat } ` ;
163
219
await run (
164
- `yarn openapi bundle specs/${ client } /spec.yml -o ${ bundledPath } --ext ${ outputFormat } ` ,
220
+ `yarn openapi bundle specs/${ specBase } /spec.yml -o ${ bundledPath } --ext ${ outputFormat } ` ,
165
221
{ verbose }
166
222
) ;
167
223
168
- if ( ! ( await propagateTagsToOperations ( toAbsolutePath ( bundledPath ) , client ) ) ) {
169
- spinner . fail ( ) ;
170
- throw new Error (
171
- `Unable to propage tags to operations for \`${ client } \` spec.`
172
- ) ;
224
+ // Add the correct tags to be able to generate the proper client
225
+ if ( ! isLite ) {
226
+ await propagateTagsToOperations ( {
227
+ bundledPath : toAbsolutePath ( bundledPath ) ,
228
+ clientName : spec ,
229
+ withDoc : BUNDLE_WITH_DOC ,
230
+ } ) ;
231
+ } else {
232
+ await buildLiteSpec ( {
233
+ spec,
234
+ bundledPath : toAbsolutePath ( bundledPath ) ,
235
+ outputFormat,
236
+ } ) ;
173
237
}
174
238
175
- spinner . text = `linting ${ client } spec` ;
176
- await run ( `yarn specs:fix ${ client } ` , { verbose } ) ;
177
-
178
- spinner . text = `validating ${ client } spec` ;
179
- await run ( `yarn openapi lint specs/bundled/${ client } .${ outputFormat } ` , {
239
+ // Validate and lint the final bundle
240
+ spinner . text = `validating '${ spec } ' bundled spec` ;
241
+ await run ( `yarn openapi lint specs/bundled/${ spec } .${ outputFormat } ` , {
180
242
verbose,
181
243
} ) ;
182
244
183
- spinner . text = `linting '${ client } ' bundled spec` ;
184
- await run ( `yarn specs:fix bundled/${ client } .${ outputFormat } ` , { verbose } ) ;
185
-
186
- if ( shouldBundleLiteSpec ) {
187
- spinner . text = `Building and linting '${ spec } ' spec` ;
188
- await buildLiteSpec ( spec , bundledPath , outputFormat , verbose ) ;
189
- }
245
+ spinner . text = `linting '${ spec } ' bundled spec` ;
246
+ await run ( `yarn specs:fix bundled/${ spec } .${ outputFormat } ` , { verbose } ) ;
190
247
191
248
if ( hash ) {
192
- spinner . text = `storing ${ client } spec cache` ;
249
+ spinner . text = `storing ' ${ spec } ' spec cache` ;
193
250
await fsp . writeFile ( cacheFile , hash ) ;
194
251
}
195
252
196
- spinner . succeed ( `building complete for '${ client } ' spec` ) ;
253
+ spinner . succeed ( `building complete for '${ spec } ' spec` ) ;
197
254
}
198
255
199
256
export async function buildSpecs (
0 commit comments