@platformatic/service 1.51.8 → 2.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,863 +0,0 @@
1
- 'use strict'
2
-
3
- const { join, relative } = require('node:path')
4
- const { readFile, writeFile, mkdir, readdir, rm } = require('node:fs/promises')
5
- const { inspect } = require('node:util')
6
- const pino = require('pino')
7
- const pretty = require('pino-pretty')
8
- const cliProgress = require('cli-progress')
9
- const { request } = require('undici')
10
- const { green } = require('colorette')
11
- const compareOpenApiSchemas = require('openapi-schema-diff')
12
- const { default: CodeBlockWriter } = require('code-block-writer')
13
- const { loadConfig, getParser, getStringifier } = require('@platformatic/config')
14
- const { getOpenapiSchema } = require('./get-openapi-schema.js')
15
- const { platformaticService } = require('../index.js')
16
- const {
17
- isFileAccessible,
18
- changeOpenapiSchemaPrefix,
19
- convertOpenApiToFastifyPath,
20
- convertOpenApiToFastifyRouteSchema
21
- } = require('./utils.js')
22
-
23
- const OPENAI_SERVICE_HOST = 'https://openai.platformatic.cloud'
24
- const OPENAI_WARNING = '// !!! This function was generated by OpenAI. Check before use !!!\n'
25
-
26
- const HTTP_METHODS_WITH_BODY = ['POST', 'PUT', 'PATCH', 'DELETE']
27
-
28
- async function generateRequestMapper (openaiProxyHost, userApiKey, content) {
29
- openaiProxyHost = openaiProxyHost ?? OPENAI_SERVICE_HOST
30
-
31
- const url = openaiProxyHost + '/openapi/mappers/request'
32
- const { statusCode, body } = await request(url, {
33
- method: 'POST',
34
- headers: {
35
- 'Content-Type': 'application/json',
36
- 'x-platformatic-user-api-key': userApiKey
37
- },
38
- body: JSON.stringify(content)
39
- })
40
-
41
- if (statusCode !== 200) {
42
- const error = await body.text()
43
- throw new Error('Failed to generate request mapper: ' + error)
44
- }
45
-
46
- const data = await body.json()
47
- return OPENAI_WARNING + data.code
48
- }
49
-
50
- async function generateResponseMapper (openaiProxyHost, userApiKey, content) {
51
- openaiProxyHost = openaiProxyHost ?? OPENAI_SERVICE_HOST
52
-
53
- const url = openaiProxyHost + '/openapi/mappers/response'
54
- const { statusCode, body } = await request(url, {
55
- method: 'POST',
56
- headers: {
57
- 'Content-Type': 'application/json',
58
- 'x-platformatic-user-api-key': userApiKey
59
- },
60
- body: JSON.stringify(content)
61
- })
62
-
63
- if (statusCode !== 200) {
64
- const error = await body.text()
65
- throw new Error('Failed to generate response mapper: ' + error)
66
- }
67
-
68
- const data = await body.json()
69
- return OPENAI_WARNING + data.code
70
- }
71
-
72
- async function generateMapperForDeletedRoute (
73
- logger,
74
- method,
75
- openapiPath,
76
- prevVersionConfig,
77
- nextVersionConfig,
78
- routeDiff
79
- ) {
80
- const writer = new CodeBlockWriter({
81
- newLine: '\n',
82
- indentNumberOfSpaces: 2,
83
- useTabs: false,
84
- useSingleQuote: true
85
- })
86
-
87
- const nextVersion = nextVersionConfig.version
88
-
89
- const fastifySchema = convertOpenApiToFastifyRouteSchema(routeDiff.sourceSchema)
90
- const serializedFastifySchema = inspect(
91
- fastifySchema ?? {},
92
- {
93
- depth: null,
94
- compact: false
95
- }
96
- )
97
-
98
- writer.writeLine(
99
- `// Route ${method.toUpperCase()} "${openapiPath}" was deleted in the "${nextVersion}" API`
100
- )
101
-
102
- const fastifyPath = convertOpenApiToFastifyPath(openapiPath)
103
-
104
- const errorMessage =
105
- `Route ${method.toUpperCase()} "${openapiPath}" was deleted in the "${nextVersion}" API`
106
-
107
- writer.write('fastify.route({').indent(() => {
108
- writer.writeLine(`method: '${method.toUpperCase()}',`)
109
- writer.writeLine(`url: '${fastifyPath}',`)
110
- writer.writeLine(`schema: ${serializedFastifySchema},`)
111
- writer.write('handler: async (req, reply) => ').block(() => {
112
- writer.writeLine('reply').indent(() => {
113
- writer.writeLine('.code(404)')
114
- writer.writeLine('.send({').indent(() => {
115
- writer.writeLine('code: \'PLT_ERR_DELETED_ROUTE\',')
116
- writer.writeLine(`message: '${errorMessage}'`)
117
- }).write('})')
118
- })
119
- })
120
- }).write('})')
121
-
122
- return writer.toString()
123
- }
124
-
125
- function generateDefaultRequestMapper () {
126
- const writer = new CodeBlockWriter({
127
- newLine: '\n',
128
- indentNumberOfSpaces: 2,
129
- useTabs: false,
130
- useSingleQuote: true
131
- })
132
-
133
- writer.writeLine('function mapInputData (inputData) {').indent(() => {
134
- writer.writeLine('// Map your input params here')
135
- writer.blankLine()
136
- writer.writeLine('delete inputData.headers.connection')
137
- writer.writeLine('delete inputData.headers[\'content-length\']')
138
- writer.writeLine('delete inputData.headers[\'transfer-encoding\']')
139
- writer.blankLine()
140
- writer.write('return {').indent(() => {
141
- writer.writeLine('pathParams: inputData.pathParams,')
142
- writer.writeLine('queryParams: inputData.queryParams,')
143
- writer.writeLine('headers: inputData.headers,')
144
- writer.writeLine('requestBody: inputData.requestBody')
145
- }).write('}')
146
- }).write('}\n')
147
-
148
- return writer.toString()
149
- }
150
-
151
- function generateDefaultResponseMapper () {
152
- const writer = new CodeBlockWriter({
153
- newLine: '\n',
154
- indentNumberOfSpaces: 2,
155
- useTabs: false,
156
- useSingleQuote: true
157
- })
158
-
159
- writer.writeLine('function mapOutputData (outputData) {').indent(() => {
160
- writer.writeLine('// Map your output params here')
161
- writer.write('return {').indent(() => {
162
- writer.writeLine('headers: outputData.headers,')
163
- writer.writeLine('responseBody: outputData.responseBody')
164
- }).write('}')
165
- }).write('}\n')
166
-
167
- return writer.toString()
168
- }
169
-
170
- async function generateHandlerForChangedRoute (
171
- logger,
172
- method,
173
- openapiPath,
174
- prevVersionPrefix,
175
- nextVersionPrefix,
176
- routeDiff,
177
- userApiKey,
178
- openai,
179
- openaiProxyHost
180
- ) {
181
- const writer = new CodeBlockWriter({
182
- newLine: '\n',
183
- indentNumberOfSpaces: 2,
184
- useTabs: false,
185
- useSingleQuote: true
186
- })
187
-
188
- const requestChanges = routeDiff.changes.filter(
189
- c => c.type === 'parameter' || c.type === 'requestBody'
190
- )
191
- const responseChanges = routeDiff.changes.filter(
192
- c => c.type === 'responseHeader' || c.type === 'responseBody'
193
- )
194
-
195
- const includeRequestBody = HTTP_METHODS_WITH_BODY.includes(method.toUpperCase())
196
-
197
- let genRequestMapperReq = null
198
- if (requestChanges.length > 0) {
199
- if (openai) {
200
- genRequestMapperReq = generateRequestMapper(
201
- openaiProxyHost,
202
- userApiKey,
203
- {
204
- includeBody: includeRequestBody,
205
- sourceSchema: {
206
- parameters: routeDiff.sourceSchema.parameters ?? [],
207
- requestBody: routeDiff.sourceSchema.requestBody ?? {}
208
- },
209
- targetSchema: {
210
- parameters: routeDiff.targetSchema.parameters ?? [],
211
- requestBody: routeDiff.targetSchema.requestBody ?? {}
212
- }
213
- }
214
- ).catch(async (err) => {
215
- logger.warn({ err }, 'Failed to generate request params mapper. Using default mapper.')
216
- return generateDefaultRequestMapper()
217
- })
218
- } else {
219
- genRequestMapperReq = generateDefaultRequestMapper()
220
- }
221
- }
222
-
223
- const mappedStatusCodes = []
224
- const getResponseMappersReqs = []
225
-
226
- const sourceSchemaResponses = routeDiff.sourceSchema.responses ?? {}
227
- const targetSchemaResponses = routeDiff.targetSchema.responses ?? {}
228
-
229
- for (const statusCode in targetSchemaResponses) {
230
- const targetSchemaResponse = targetSchemaResponses[statusCode]
231
- const sourceSchemaResponse = sourceSchemaResponses[statusCode]
232
-
233
- const genMapperReqBody = { sourceSchema: {}, targetSchema: {} }
234
-
235
- const headerChanges = responseChanges.filter(change =>
236
- change.type === 'responseHeader' &&
237
- change.statusCode === statusCode
238
- )
239
-
240
- const bodyChange = responseChanges.find(change =>
241
- change.action === 'changed' &&
242
- change.type === 'responseBody' &&
243
- change.statusCode === statusCode &&
244
- change.mediaType === 'application/json'
245
- )
246
-
247
- if (bodyChange || headerChanges.length > 0) {
248
- if (headerChanges.length > 0) {
249
- genMapperReqBody.targetSchema.headers = sourceSchemaResponse.headers
250
- genMapperReqBody.sourceSchema.headers = targetSchemaResponse.headers
251
- }
252
- if (bodyChange) {
253
- genMapperReqBody.targetSchema.responseBody = bodyChange.sourceSchema.schema
254
- genMapperReqBody.sourceSchema.responseBody = bodyChange.targetSchema.schema
255
- }
256
-
257
- let genResponseMapperReq = null
258
- if (openai) {
259
- genResponseMapperReq = generateResponseMapper(
260
- openaiProxyHost,
261
- userApiKey,
262
- genMapperReqBody
263
- ).catch(async (err) => {
264
- logger.warn({ err }, 'Failed to generate response params mapper. Using default mapper.')
265
- return generateDefaultResponseMapper()
266
- })
267
- } else {
268
- genResponseMapperReq = generateDefaultResponseMapper()
269
- }
270
-
271
- mappedStatusCodes.push(statusCode)
272
- getResponseMappersReqs.push(genResponseMapperReq)
273
- }
274
- }
275
-
276
- const [requestMapper, ...responseMappers] = await Promise.all([
277
- genRequestMapperReq,
278
- ...getResponseMappersReqs
279
- ])
280
-
281
- writer.write('async (req, reply) => ').block(() => {
282
- if (requestMapper) {
283
- writer.writeLine(requestMapper)
284
- }
285
-
286
- for (let i = 0; i < mappedStatusCodes.length; i++) {
287
- const statusCode = mappedStatusCodes[i]
288
- const responseMapper = responseMappers[i]
289
-
290
- const renamedResponseMapper = responseMapper.replace(
291
- 'mapOutputData',
292
- `mapOutputData${statusCode}`
293
- )
294
-
295
- writer.writeLine(renamedResponseMapper)
296
- }
297
-
298
- if (requestMapper) {
299
- writer.write('const inputParams = mapInputData({').indent(() => {
300
- writer.writeLine('pathParams: req.params,')
301
- writer.writeLine('queryParams: req.query,')
302
- writer.writeLine('headers: req.headers,')
303
- if (includeRequestBody) {
304
- writer.writeLine('requestBody: req.body')
305
- }
306
- }).write('})')
307
- } else {
308
- writer.write('const inputParams = {').indent(() => {
309
- writer.writeLine('pathParams: req.params,')
310
- writer.writeLine('queryParams: req.query,')
311
- writer.writeLine('headers: req.headers,')
312
- if (includeRequestBody) {
313
- writer.writeLine('requestBody: req.body')
314
- }
315
- }
316
- ).write('}')
317
- }
318
- writer.blankLine()
319
-
320
- let nextRouteUrl = null
321
- const pathParams = openapiPath.match(/{(\w+)}/g) ?? []
322
- if (pathParams.length > 0) {
323
- let mappedPath = openapiPath
324
- for (const pathParam of pathParams) {
325
- let paramName = pathParam.slice(1, -1)
326
- paramName = paramName === 'wildcard' ? '*' : paramName
327
- mappedPath = mappedPath.replace(pathParam, '${params[\'' + paramName + '\']}')
328
- }
329
-
330
- writer.writeLine('const params = inputParams.pathParams')
331
- nextRouteUrl = `\`${nextVersionPrefix + mappedPath}\``
332
- } else {
333
- nextRouteUrl = `'${nextVersionPrefix + openapiPath}'`
334
- }
335
-
336
- writer.write('const res = await fastify.inject({').indent(() => {
337
- writer.writeLine('method: \'' + method.toUpperCase() + '\',')
338
- writer.writeLine(`url: ${nextRouteUrl},`)
339
- writer.writeLine('query: inputParams.queryParams,')
340
- writer.writeLine('headers: inputParams.headers,')
341
- if (includeRequestBody) {
342
- writer.writeLine('payload: inputParams.requestBody')
343
- }
344
- }).write('})')
345
- writer.blankLine()
346
-
347
- writer.write('let outputParams = {').indent(() => {
348
- writer.writeLine('headers: res.headers,')
349
- writer.writeLine('responseBody: res.body')
350
- }).write('}')
351
- writer.blankLine()
352
-
353
- for (let i = 0; i < mappedStatusCodes.length; i++) {
354
- const statusCode = mappedStatusCodes[i]
355
- const mapperFunctionName = `mapOutputData${statusCode}`
356
-
357
- writer.write(`if (res.statusCode === ${statusCode}) `).block(() => {
358
- writer.writeLine('const responseBody = outputParams.responseBody')
359
- writer.write('if (typeof responseBody === \'string\')').block(() => {
360
- writer.writeLine('outputParams.responseBody = JSON.parse(responseBody)')
361
- })
362
- writer.write(`outputParams = ${mapperFunctionName}(outputParams)`)
363
- })
364
- writer.blankLine()
365
- }
366
-
367
- writer.write('reply').indent(() => {
368
- writer.writeLine('.code(res.statusCode)')
369
- writer.writeLine('.headers(outputParams.headers)')
370
- })
371
- writer.blankLine()
372
- writer.writeLine('return outputParams.responseBody')
373
- })
374
-
375
- return writer.toString()
376
- }
377
-
378
- async function generateMapperForChangedRoute (
379
- logger,
380
- method,
381
- openapiPath,
382
- prevVersionConfig,
383
- nextVersionConfig,
384
- routeDiff,
385
- userApiKey,
386
- openai,
387
- openaiProxyHost
388
- ) {
389
- const writer = new CodeBlockWriter({
390
- newLine: '\n',
391
- indentNumberOfSpaces: 2,
392
- useTabs: false,
393
- useSingleQuote: true
394
- })
395
-
396
- const nextVersion = nextVersionConfig.version
397
-
398
- const prevVersionPrefix = prevVersionConfig.openapi.prefix ?? ''
399
- const nextVersionPrefix = nextVersionConfig.openapi.prefix ?? ''
400
-
401
- writer.writeLine(
402
- `/* Route ${method.toUpperCase()} "${openapiPath}" was changed in the "${nextVersion}" API`
403
- )
404
- writer.indent(() => {
405
- for (const componentChange of routeDiff.changes) {
406
- writer.writeLine(`- ${componentChange.comment}`)
407
- if (componentChange.action !== 'changed') continue
408
-
409
- writer.indent(() => {
410
- for (const keywordChange of componentChange.changes ?? []) {
411
- writer.writeLine(`- ${keywordChange.comment}`)
412
- if (keywordChange.keyword !== 'schema') continue
413
-
414
- writer.indent(() => {
415
- for (const schemaChange of keywordChange.changes ?? []) {
416
- const prevSchemaComments = JSON.stringify(schemaChange.source || '', null, 2)
417
- const nextSchemaComments = JSON.stringify(schemaChange.target || '', null, 2)
418
-
419
- writer.writeLine(`- schema diff at "${schemaChange.jsonPath}":`)
420
- writer.indent(() => {
421
- prevSchemaComments.split('\n').forEach(l => writer.writeLine('- ' + l))
422
- nextSchemaComments.split('\n').forEach(l => writer.writeLine('+ ' + l))
423
- writer.blankLine()
424
- })
425
- }
426
- })
427
- }
428
- })
429
- }
430
- })
431
-
432
- writer.writeLine('*/')
433
-
434
- const fastifySchema = convertOpenApiToFastifyRouteSchema(routeDiff.sourceSchema)
435
- const serializedFastifySchema = inspect(
436
- fastifySchema ?? {},
437
- {
438
- depth: null,
439
- compact: false
440
- }
441
- )
442
-
443
- const routeHandler = await generateHandlerForChangedRoute(
444
- logger,
445
- method,
446
- openapiPath,
447
- prevVersionPrefix,
448
- nextVersionPrefix,
449
- routeDiff,
450
- userApiKey,
451
- openai,
452
- openaiProxyHost
453
- )
454
-
455
- const fastifyPath = convertOpenApiToFastifyPath(openapiPath)
456
-
457
- writer.write('fastify.route({').indent(() => {
458
- writer.writeLine(`method: '${method.toUpperCase()}',`)
459
- writer.writeLine(`url: '${fastifyPath}',`)
460
- writer.writeLine(`schema: ${serializedFastifySchema},`)
461
- writer.write(`handler: ${routeHandler}`)
462
- }).write('})')
463
-
464
- return writer.toString()
465
- }
466
-
467
- async function generateMapperPluginForDeletedRoute (
468
- logger,
469
- deletedRouteDiff,
470
- prevVersionConfig,
471
- nextVersionConfig
472
- ) {
473
- const writer = new CodeBlockWriter({
474
- newLine: '\n',
475
- indentNumberOfSpaces: 2,
476
- useTabs: false,
477
- useSingleQuote: true
478
- })
479
-
480
- writer.writeLine('\'use strict\'')
481
- writer.blankLine()
482
-
483
- const method = deletedRouteDiff.method
484
- const openapiPath = deletedRouteDiff.path
485
-
486
- const routeMapper = await generateMapperForDeletedRoute(
487
- logger,
488
- method,
489
- openapiPath,
490
- prevVersionConfig,
491
- nextVersionConfig,
492
- deletedRouteDiff
493
- )
494
-
495
- writer.write('module.exports = async function (fastify, opts) {').indent(() => {
496
- writer.write(routeMapper)
497
- }).write('}\n')
498
- return writer.toString()
499
- }
500
-
501
- async function generateMapperPluginForChangesRoute (
502
- logger,
503
- changedRouteDiff,
504
- prevVersionConfig,
505
- nextVersionConfig,
506
- userApiKey,
507
- openai,
508
- openaiProxyHost
509
- ) {
510
- const writer = new CodeBlockWriter({
511
- newLine: '\n',
512
- indentNumberOfSpaces: 2,
513
- useTabs: false,
514
- useSingleQuote: true
515
- })
516
-
517
- writer.writeLine('\'use strict\'')
518
- writer.blankLine()
519
-
520
- const method = changedRouteDiff.method
521
- const openapiPath = changedRouteDiff.path
522
-
523
- const routeMapper = await generateMapperForChangedRoute(
524
- logger,
525
- method,
526
- openapiPath,
527
- prevVersionConfig,
528
- nextVersionConfig,
529
- changedRouteDiff,
530
- userApiKey,
531
- openai,
532
- openaiProxyHost
533
- )
534
-
535
- writer.write('module.exports = async function (fastify, opts) {').indent(() => {
536
- writer.write(routeMapper)
537
- }).write('}\n')
538
- return writer.toString()
539
- }
540
-
541
- function createProgressBar (count) {
542
- const multibar = new cliProgress.MultiBar({
543
- format: 'Routes generation: |' + green('{bar}') + '| {value}/{total} Generated routes | Time: {duration}s',
544
- clearOnComplete: true,
545
- stopOnComplete: true,
546
- gracefulExit: true
547
- }, cliProgress.Presets.shades_classic)
548
-
549
- const progressBar = multibar.create(count, 0)
550
- return {
551
- increment: () => progressBar.increment(),
552
- stop: () => {
553
- progressBar.stop()
554
- multibar.stop()
555
- }
556
- }
557
- }
558
-
559
- function generatePluginName (method, openapiPath) {
560
- const methodPrefix = method.toLowerCase()
561
- const pathPrefix = openapiPath
562
- .split('-')
563
- .map(p => p.replace(/\//g, '-'))
564
- .join('--')
565
-
566
- return methodPrefix + pathPrefix + '.js'
567
- }
568
-
569
- function generateOpenapiPath (pluginName) {
570
- const index = pluginName.indexOf('-')
571
- const methodPrefix = pluginName.slice(0, index)
572
- const pathPrefix = pluginName.slice(index + 1, -3)
573
-
574
- const method = methodPrefix.toLowerCase()
575
-
576
- // Should map "-" to "/" only if there is no another "-" next to it or before it
577
- const path = '/' + pathPrefix
578
- .split('--')
579
- .map(p => p.replace(/-/g, '/'))
580
- .join('-')
581
-
582
- return { method, path }
583
- }
584
-
585
- async function createMappersPlugins ({
586
- logger,
587
- configManager,
588
- prevVersion,
589
- nextVersion,
590
- prevOpenapiSchema,
591
- nextOpenapiSchema,
592
- userApiKey = null,
593
- openai = false,
594
- openaiProxyHost = OPENAI_SERVICE_HOST
595
- }) {
596
- const config = configManager.current
597
-
598
- const versions = config.versions ?? {}
599
- const versionsConfigs = versions.configs ?? []
600
-
601
- const prevVersionConfig = versionsConfigs.find(c => c.version === prevVersion)
602
- const nextVersionConfig = versionsConfigs.find(c => c.version === nextVersion)
603
-
604
- const prevNormalizedOpenapiSchema = changeOpenapiSchemaPrefix(
605
- prevOpenapiSchema,
606
- prevVersionConfig.openapi.prefix,
607
- ''
608
- )
609
-
610
- const nextNormalizedOpenapiSchema = changeOpenapiSchemaPrefix(
611
- nextOpenapiSchema,
612
- nextVersionConfig.openapi.prefix,
613
- ''
614
- )
615
-
616
- const schemasDiff = compareOpenApiSchemas(
617
- prevNormalizedOpenapiSchema,
618
- nextNormalizedOpenapiSchema
619
- )
620
-
621
- const modifiedRoutesCount =
622
- schemasDiff.changedRoutes.length +
623
- schemasDiff.deletedRoutes.length
624
-
625
- if (modifiedRoutesCount === 0) {
626
- logger.info(
627
- `No changes found between "${prevVersion}" and "${nextVersion}" openapi schemas.` +
628
- ' Skipping mappers generation'
629
- )
630
- return null
631
- }
632
-
633
- logger.info(`Generating openapi mappers for "${prevVersion}" -> "${nextVersion}"`)
634
-
635
- const progressBar = createProgressBar(modifiedRoutesCount)
636
- const onRouteGenerated = async () => {
637
- progressBar.increment()
638
- }
639
-
640
- const mappersDir = join(versions.dir, prevVersionConfig.version, 'mappers')
641
- const mappersDirExists = await isFileAccessible(mappersDir)
642
- if (!mappersDirExists) {
643
- logger.info(`Creating mappers directory for "${prevVersion}" -> "${nextVersion}"`)
644
- await mkdir(mappersDir, { recursive: true })
645
- }
646
-
647
- const genPluginsReqs = []
648
- for (const deletedRouteDiff of schemasDiff.deletedRoutes) {
649
- const method = deletedRouteDiff.method
650
- const openapiPath = deletedRouteDiff.path
651
-
652
- const mapperPluginFileName = generatePluginName(method, openapiPath)
653
- const mapperPluginFilePath = join(mappersDir, mapperPluginFileName)
654
-
655
- const generateMapperReq = generateMapperPluginForDeletedRoute(
656
- logger,
657
- deletedRouteDiff,
658
- prevVersionConfig,
659
- nextVersionConfig
660
- )
661
- .then(
662
- async (mapperPlugin) => {
663
- await writeFile(mapperPluginFilePath, mapperPlugin, 'utf8')
664
- }
665
- )
666
- .then(
667
- async () => {
668
- await onRouteGenerated(method, openapiPath)
669
- }
670
- )
671
-
672
- genPluginsReqs.push(generateMapperReq)
673
- }
674
-
675
- for (const changedRouteDiff of schemasDiff.changedRoutes) {
676
- const method = changedRouteDiff.method
677
- const openapiPath = changedRouteDiff.path
678
-
679
- const mapperPluginFileName = generatePluginName(method, openapiPath)
680
- const mapperPluginFilePath = join(mappersDir, mapperPluginFileName)
681
-
682
- const generateMapperReq = generateMapperPluginForChangesRoute(
683
- logger,
684
- changedRouteDiff,
685
- prevVersionConfig,
686
- nextVersionConfig,
687
- userApiKey,
688
- openai,
689
- openaiProxyHost
690
- )
691
- .then(
692
- async (mapperPlugin) => {
693
- await writeFile(mapperPluginFilePath, mapperPlugin, 'utf8')
694
- }
695
- )
696
- .then(
697
- async () => {
698
- await onRouteGenerated(method, openapiPath)
699
- }
700
- )
701
-
702
- genPluginsReqs.push(generateMapperReq)
703
- }
704
-
705
- await Promise.all(genPluginsReqs)
706
- progressBar.stop()
707
-
708
- const mappersPluginsNames = await readdir(mappersDir)
709
- for (const mappersPluginName of mappersPluginsNames) {
710
- const { method, path } = generateOpenapiPath(mappersPluginName)
711
-
712
- const isDeletedRoute = schemasDiff.deletedRoutes.some(
713
- routeDiff => {
714
- return routeDiff.method === method && routeDiff.path === path
715
- }
716
- )
717
- const isChangedRoute = schemasDiff.changedRoutes.some(
718
- routeDiff => {
719
- return routeDiff.method === method && routeDiff.path === path
720
- }
721
- )
722
- if (!isDeletedRoute && !isChangedRoute) {
723
- logger.info(`Removing obsolete mappers plugin "${mappersPluginName}"`)
724
- const mapperPluginPath = join(mappersDir, mappersPluginName)
725
- await rm(mapperPluginPath, { force: true })
726
- }
727
- }
728
-
729
- logger.info(`Updating "${prevVersion}" version config with mappers plugin path`)
730
- await addMappersToConfig(mappersDir, prevVersionConfig.version, configManager)
731
- }
732
-
733
- async function addMappersToConfig (mappersDir, version, configManager) {
734
- const config = configManager.current
735
- const prevVersionConfig = config.versions.configs.find(c => c.version === version)
736
-
737
- const pluginsPaths = prevVersionConfig.plugins?.paths ?? []
738
- for (let foundPluginPath of pluginsPaths) {
739
- foundPluginPath = typeof foundPluginPath === 'string'
740
- ? foundPluginPath
741
- : foundPluginPath.path
742
-
743
- if (mappersDir.startsWith(foundPluginPath)) return
744
- }
745
-
746
- // TODO(mcollina) this must auto-upgrade
747
- const parse = getParser(configManager.fullPath)
748
- const stringify = getStringifier(configManager.fullPath)
749
- const rawConfig = parse(await readFile(configManager.fullPath, 'utf8'))
750
- const rawPrevVersionConfig = rawConfig.versions.configs.find(c => c.version === version)
751
-
752
- const relativePath = relative(configManager.dirname, mappersDir)
753
- if (!prevVersionConfig.plugins) {
754
- prevVersionConfig.plugins = { paths: [] }
755
- rawPrevVersionConfig.plugins = { paths: [] }
756
- }
757
- prevVersionConfig.plugins.paths.push(relativePath)
758
- rawPrevVersionConfig.plugins.paths.push(relativePath)
759
- await Promise.all([configManager.update(), writeFile(configManager.fullPath, stringify(rawConfig), 'utf8')])
760
- }
761
-
762
- async function execute ({
763
- logger,
764
- configManager,
765
- userApiKey,
766
- openai,
767
- openaiProxyHost
768
- }) {
769
- const config = configManager.current
770
-
771
- const versions = config.versions ?? {}
772
- const versionsConfigs = versions.configs ?? []
773
-
774
- const nextVersionConfig = versionsConfigs.at(-1)
775
- if (!nextVersionConfig) {
776
- logger.info('No versions found. Skipping version update.')
777
- return
778
- }
779
-
780
- const nextVersion = nextVersionConfig.version
781
- const nextOpenapiSchemaPath = nextVersionConfig.openapi.path
782
-
783
- logger.info(`Loading openapi schema for "${nextVersion}"`)
784
- const nextOpenapiSchema = await getOpenapiSchema({
785
- logger,
786
- configManager,
787
- version: nextVersionConfig.version
788
- })
789
- logger.info(`Updating openapi schema for "${nextVersion}"`)
790
- await writeFile(nextOpenapiSchemaPath, JSON.stringify(nextOpenapiSchema, null, 2), 'utf8')
791
-
792
- const prevVersionConfig = versionsConfigs.at(-2)
793
- if (!prevVersionConfig) {
794
- logger.info('No previous versions found. Skipping mappers generation.')
795
- return
796
- }
797
-
798
- const prevVersion = prevVersionConfig.version
799
- const prevOpenapiSchemaPath = prevVersionConfig.openapi.path
800
-
801
- logger.info(`Reading openapi schema for "${prevVersion}"`)
802
- const prevOpenapiSchemaFile = await readFile(prevOpenapiSchemaPath, 'utf8')
803
- const prevOpenapiSchema = JSON.parse(prevOpenapiSchemaFile)
804
-
805
- await createMappersPlugins({
806
- logger,
807
- configManager,
808
- prevVersion,
809
- nextVersion,
810
- prevOpenapiSchema,
811
- nextOpenapiSchema,
812
- userApiKey,
813
- openai,
814
- openaiProxyHost
815
- })
816
- }
817
-
818
- async function updateVersion (_args) {
819
- const logger = pino(pretty({
820
- translateTime: 'SYS:HH:MM:ss',
821
- ignore: 'hostname,pid'
822
- }))
823
-
824
- try {
825
- const { configManager, args } = await loadConfig({
826
- string: ['openai-proxy-host', 'user-api-key'],
827
- boolean: ['openai']
828
- }, _args, platformaticService)
829
- await configManager.parseAndValidate()
830
-
831
- const openai = args.openai ?? false
832
- const openaiProxyHost = args['openai-proxy-host'] ?? OPENAI_SERVICE_HOST
833
-
834
- let userApiKey = args['user-api-key'] ?? null
835
- /* c8 ignore next 10 */
836
- if (!userApiKey && openai) {
837
- logger.info('Reading platformatic user api key')
838
- const { getUserApiKey } = await import('@platformatic/authenticate')
839
- try {
840
- userApiKey = await getUserApiKey()
841
- } catch (err) {
842
- logger.error('Failed to read user api key. Please run "plt login" command.')
843
- return
844
- }
845
- }
846
-
847
- await execute({
848
- logger,
849
- configManager,
850
- userApiKey,
851
- openai,
852
- openaiProxyHost
853
- })
854
-
855
- // TODO: find out why process stucks sometimes
856
- process.exit(0)
857
- } catch (err) {
858
- logger.error({ err })
859
- process.exit(1)
860
- }
861
- }
862
-
863
- module.exports = { execute, updateVersion, createMappersPlugins }