massimo-cli 0.3.0 → 0.5.0
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.
- package/help/help.txt +2 -1
- package/index.js +273 -42
- package/lib/frontend-openapi-generator.js +10 -7
- package/lib/graphql-generator.js +22 -8
- package/lib/openapi-generator.js +24 -9
- package/package.json +2 -2
package/help/help.txt
CHANGED
|
@@ -55,7 +55,7 @@ You can generate only the types with the --types-only flag.
|
|
|
55
55
|
$ massimo http://exmaple.com/to/schema/file --name myclient --types-only
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
Will create the single myclient.d.ts file.
|
|
58
|
+
Will create the single myclient.d.ts file (or .d.mts/.d.cts depending on module format and --type-extension option).
|
|
59
59
|
|
|
60
60
|
Options:
|
|
61
61
|
|
|
@@ -77,3 +77,4 @@ Options:
|
|
|
77
77
|
* `--props-optional` - If `true`, properties will be defined as optional unless they're part of the `required` array. By default this option is `true`.
|
|
78
78
|
* `--skip-config-update` - If `true`, it will not update the `platformatic|watt` config found in your repo. By default this option is `true`.
|
|
79
79
|
* `--retry-timeout-ms` - If passed, the HTTP request to get an Open API schema will be retried (reference: https://undici.nodejs.org/#/docs/api/RetryHandler.md)
|
|
80
|
+
* `--type-extension` - Force the use of module-specific type extensions (.d.mts for ESM, .d.cts for CJS) instead of the default logic.
|
package/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { access, mkdir, readFile, rm, writeFile } from 'fs/promises'
|
|
|
4
4
|
import graphql from 'graphql'
|
|
5
5
|
import helpMe from 'help-me'
|
|
6
6
|
import parseArgs from 'minimist'
|
|
7
|
-
import { join } from 'path'
|
|
7
|
+
import { join, dirname } from 'path'
|
|
8
8
|
import pino from 'pino'
|
|
9
9
|
import pinoPretty from 'pino-pretty'
|
|
10
10
|
import { getGlobalDispatcher, interceptors, request } from 'undici'
|
|
@@ -43,6 +43,71 @@ export async function createDirectory (path, empty = false) {
|
|
|
43
43
|
return mkdir(path, { recursive: true, maxRetries: 10, retryDelay: 1000 })
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
export async function detectModuleFormat (folder, explicitFormat) {
|
|
47
|
+
if (explicitFormat) {
|
|
48
|
+
if (explicitFormat === 'esm' || explicitFormat === 'cjs') {
|
|
49
|
+
return explicitFormat
|
|
50
|
+
}
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Invalid module format: ${explicitFormat}. Valid values are 'esm' or 'cjs'`
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
let currentDir = folder
|
|
56
|
+
while (currentDir !== dirname(currentDir)) {
|
|
57
|
+
const packageJsonPath = join(currentDir, 'package.json')
|
|
58
|
+
if (await isFileAccessible(packageJsonPath)) {
|
|
59
|
+
try {
|
|
60
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'))
|
|
61
|
+
if (packageJson.type === 'module') return 'esm'
|
|
62
|
+
return 'cjs'
|
|
63
|
+
} catch (err) {
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
currentDir = dirname(currentDir)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return 'esm'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function determineTypeExtension (folder, moduleFormat, typeExtension, explicitModuleFormat, generateImplementation = true) {
|
|
73
|
+
if (typeExtension) {
|
|
74
|
+
return moduleFormat === 'esm' ? 'd.mts' : 'd.cts'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!explicitModuleFormat && !generateImplementation) {
|
|
78
|
+
return 'd.ts'
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!explicitModuleFormat && generateImplementation) {
|
|
82
|
+
return 'd.ts'
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let currentDir = folder
|
|
86
|
+
while (currentDir !== dirname(currentDir)) {
|
|
87
|
+
const packageJsonPath = join(currentDir, 'package.json')
|
|
88
|
+
if (await isFileAccessible(packageJsonPath)) {
|
|
89
|
+
try {
|
|
90
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'))
|
|
91
|
+
const parentType = packageJson.type === 'module' ? 'esm' : 'cjs'
|
|
92
|
+
|
|
93
|
+
if (moduleFormat === parentType) {
|
|
94
|
+
return 'd.ts'
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return moduleFormat === 'esm' ? 'd.mts' : 'd.cts'
|
|
98
|
+
} catch (err) {
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
currentDir = dirname(currentDir)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (explicitModuleFormat) {
|
|
105
|
+
return moduleFormat === 'esm' ? 'd.mts' : 'd.cts'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return 'd.ts'
|
|
109
|
+
}
|
|
110
|
+
|
|
46
111
|
async function writeOpenAPIClient (
|
|
47
112
|
folder,
|
|
48
113
|
name,
|
|
@@ -58,20 +123,29 @@ async function writeOpenAPIClient (
|
|
|
58
123
|
typesComment,
|
|
59
124
|
logger,
|
|
60
125
|
withCredentials,
|
|
61
|
-
propsOptional
|
|
126
|
+
propsOptional,
|
|
127
|
+
moduleFormat,
|
|
128
|
+
typeExtension,
|
|
129
|
+
explicitModuleFormat
|
|
62
130
|
) {
|
|
63
131
|
await createDirectory(folder)
|
|
64
132
|
|
|
65
133
|
// TODO deal with yaml
|
|
66
134
|
const schema = parseFile(text)
|
|
67
135
|
if (!schema) {
|
|
68
|
-
throw new Error(
|
|
136
|
+
throw new Error(
|
|
137
|
+
'Cannot parse OpenAPI file. Please make sure is a JSON or a YAML file.'
|
|
138
|
+
)
|
|
69
139
|
}
|
|
70
140
|
if (!typesOnly) {
|
|
71
|
-
await writeFile(
|
|
141
|
+
await writeFile(
|
|
142
|
+
join(folder, `${name}.openapi.json`),
|
|
143
|
+
JSON.stringify(schema, null, 2)
|
|
144
|
+
)
|
|
72
145
|
}
|
|
73
146
|
|
|
74
147
|
if (isFrontend) {
|
|
148
|
+
const typeExt = await determineTypeExtension(folder, moduleFormat, typeExtension, explicitModuleFormat, generateImplementation)
|
|
75
149
|
const { types, implementation } = processFrontendOpenAPI({
|
|
76
150
|
schema,
|
|
77
151
|
name,
|
|
@@ -80,9 +154,10 @@ async function writeOpenAPIClient (
|
|
|
80
154
|
language,
|
|
81
155
|
logger,
|
|
82
156
|
withCredentials,
|
|
83
|
-
propsOptional
|
|
157
|
+
propsOptional,
|
|
158
|
+
typeExt
|
|
84
159
|
})
|
|
85
|
-
await writeFile(join(folder, `${name}-types
|
|
160
|
+
await writeFile(join(folder, `${name}-types.${typeExt}`), types)
|
|
86
161
|
if (generateImplementation) {
|
|
87
162
|
const extension = language === 'js' ? 'mjs' : 'mts'
|
|
88
163
|
await writeFile(join(folder, `${name}.${extension}`), implementation)
|
|
@@ -96,30 +171,56 @@ async function writeOpenAPIClient (
|
|
|
96
171
|
optionalHeaders,
|
|
97
172
|
validateResponse,
|
|
98
173
|
typesComment,
|
|
99
|
-
propsOptional
|
|
174
|
+
propsOptional,
|
|
175
|
+
moduleFormat,
|
|
100
176
|
})
|
|
101
|
-
await
|
|
177
|
+
const typeExt = await determineTypeExtension(folder, moduleFormat, typeExtension, explicitModuleFormat, generateImplementation)
|
|
178
|
+
const implExt = moduleFormat === 'esm' ? 'mjs' : 'cjs'
|
|
179
|
+
await writeFile(join(folder, `${name}.${typeExt}`), types)
|
|
102
180
|
if (generateImplementation) {
|
|
103
|
-
await writeFile(join(folder, `${name}
|
|
181
|
+
await writeFile(join(folder, `${name}.${implExt}`), implementation)
|
|
104
182
|
}
|
|
105
183
|
|
|
106
184
|
if (!typesOnly) {
|
|
107
|
-
await writeFile(
|
|
185
|
+
await writeFile(
|
|
186
|
+
join(folder, 'package.json'),
|
|
187
|
+
await getPackageJSON({ name, generateImplementation, moduleFormat, folder, typeExtension, explicitModuleFormat })
|
|
188
|
+
)
|
|
108
189
|
}
|
|
109
190
|
}
|
|
110
191
|
}
|
|
111
192
|
|
|
112
|
-
async function writeGraphQLClient (
|
|
193
|
+
async function writeGraphQLClient (
|
|
194
|
+
folder,
|
|
195
|
+
name,
|
|
196
|
+
schema,
|
|
197
|
+
url,
|
|
198
|
+
generateImplementation,
|
|
199
|
+
moduleFormat,
|
|
200
|
+
typeExtension,
|
|
201
|
+
explicitModuleFormat
|
|
202
|
+
) {
|
|
113
203
|
await createDirectory(folder, { recursive: true })
|
|
114
|
-
const { types, implementation } = processGraphQL({
|
|
204
|
+
const { types, implementation } = processGraphQL({
|
|
205
|
+
schema,
|
|
206
|
+
name,
|
|
207
|
+
folder,
|
|
208
|
+
url,
|
|
209
|
+
moduleFormat,
|
|
210
|
+
})
|
|
115
211
|
const clientSchema = graphql.buildClientSchema(schema)
|
|
116
212
|
const sdl = graphql.printSchema(clientSchema)
|
|
213
|
+
const typeExt = await determineTypeExtension(folder, moduleFormat, typeExtension, explicitModuleFormat, generateImplementation)
|
|
214
|
+
const implExt = moduleFormat === 'esm' ? 'mjs' : 'cjs'
|
|
117
215
|
await writeFile(join(folder, `${name}.schema.graphql`), sdl)
|
|
118
|
-
await writeFile(join(folder, `${name}
|
|
216
|
+
await writeFile(join(folder, `${name}.${typeExt}`), types)
|
|
119
217
|
if (generateImplementation) {
|
|
120
|
-
await writeFile(join(folder, `${name}
|
|
218
|
+
await writeFile(join(folder, `${name}.${implExt}`), implementation)
|
|
121
219
|
}
|
|
122
|
-
await writeFile(
|
|
220
|
+
await writeFile(
|
|
221
|
+
join(folder, 'package.json'),
|
|
222
|
+
await getPackageJSON({ name, generateImplementation, moduleFormat, folder, typeExtension, explicitModuleFormat })
|
|
223
|
+
)
|
|
123
224
|
}
|
|
124
225
|
|
|
125
226
|
async function downloadAndWriteOpenAPI (
|
|
@@ -139,7 +240,10 @@ async function downloadAndWriteOpenAPI (
|
|
|
139
240
|
typesComment,
|
|
140
241
|
withCredentials,
|
|
141
242
|
propsOptional,
|
|
142
|
-
retryTimeoutMs
|
|
243
|
+
retryTimeoutMs,
|
|
244
|
+
moduleFormat,
|
|
245
|
+
typeExtension,
|
|
246
|
+
explicitModuleFormat
|
|
143
247
|
) {
|
|
144
248
|
logger.debug(`Trying to download OpenAPI schema from ${url}`)
|
|
145
249
|
let requestOptions
|
|
@@ -152,7 +256,9 @@ async function downloadAndWriteOpenAPI (
|
|
|
152
256
|
}
|
|
153
257
|
|
|
154
258
|
const dispatcher = retryTimeoutMs
|
|
155
|
-
? getGlobalDispatcher().compose([
|
|
259
|
+
? getGlobalDispatcher().compose([
|
|
260
|
+
interceptors.retry({ minTimeout: retryTimeoutMs })
|
|
261
|
+
])
|
|
156
262
|
: undefined
|
|
157
263
|
const res = await request(url, { ...requestOptions, dispatcher })
|
|
158
264
|
if (res.statusCode === 200) {
|
|
@@ -174,7 +280,10 @@ async function downloadAndWriteOpenAPI (
|
|
|
174
280
|
typesComment,
|
|
175
281
|
logger,
|
|
176
282
|
withCredentials,
|
|
177
|
-
propsOptional
|
|
283
|
+
propsOptional,
|
|
284
|
+
moduleFormat,
|
|
285
|
+
typeExtension,
|
|
286
|
+
explicitModuleFormat
|
|
178
287
|
)
|
|
179
288
|
/* c8 ignore next 3 */
|
|
180
289
|
} catch (err) {
|
|
@@ -188,7 +297,16 @@ async function downloadAndWriteOpenAPI (
|
|
|
188
297
|
return false
|
|
189
298
|
}
|
|
190
299
|
|
|
191
|
-
async function downloadAndWriteGraphQL (
|
|
300
|
+
async function downloadAndWriteGraphQL (
|
|
301
|
+
logger,
|
|
302
|
+
url,
|
|
303
|
+
folder,
|
|
304
|
+
name,
|
|
305
|
+
generateImplementation,
|
|
306
|
+
moduleFormat,
|
|
307
|
+
typeExtension,
|
|
308
|
+
explicitModuleFormat
|
|
309
|
+
) {
|
|
192
310
|
logger.debug(`Trying to download GraphQL schema from ${url}`)
|
|
193
311
|
const query = graphql.getIntrospectionQuery()
|
|
194
312
|
const res = await request(url, {
|
|
@@ -198,7 +316,7 @@ async function downloadAndWriteGraphQL (logger, url, folder, name, generateImple
|
|
|
198
316
|
},
|
|
199
317
|
body: JSON.stringify({
|
|
200
318
|
query
|
|
201
|
-
})
|
|
319
|
+
}),
|
|
202
320
|
})
|
|
203
321
|
|
|
204
322
|
const text = await res.body.text()
|
|
@@ -208,7 +326,16 @@ async function downloadAndWriteGraphQL (logger, url, folder, name, generateImple
|
|
|
208
326
|
}
|
|
209
327
|
|
|
210
328
|
const { data: schema } = JSON.parse(text)
|
|
211
|
-
await writeGraphQLClient(
|
|
329
|
+
await writeGraphQLClient(
|
|
330
|
+
folder,
|
|
331
|
+
name,
|
|
332
|
+
schema,
|
|
333
|
+
url,
|
|
334
|
+
generateImplementation,
|
|
335
|
+
moduleFormat,
|
|
336
|
+
typeExtension,
|
|
337
|
+
explicitModuleFormat
|
|
338
|
+
)
|
|
212
339
|
return 'graphql'
|
|
213
340
|
}
|
|
214
341
|
|
|
@@ -227,7 +354,10 @@ async function readFromFileAndWrite (
|
|
|
227
354
|
language,
|
|
228
355
|
typesComment,
|
|
229
356
|
withCredentials,
|
|
230
|
-
propsOptional
|
|
357
|
+
propsOptional,
|
|
358
|
+
moduleFormat,
|
|
359
|
+
typeExtension,
|
|
360
|
+
explicitModuleFormat
|
|
231
361
|
) {
|
|
232
362
|
logger.info(`Trying to read schema from file ${file}`)
|
|
233
363
|
const text = await readFile(file, 'utf8')
|
|
@@ -248,17 +378,32 @@ async function readFromFileAndWrite (
|
|
|
248
378
|
typesComment,
|
|
249
379
|
logger,
|
|
250
380
|
withCredentials,
|
|
251
|
-
propsOptional
|
|
381
|
+
propsOptional,
|
|
382
|
+
moduleFormat,
|
|
383
|
+
typeExtension,
|
|
384
|
+
explicitModuleFormat
|
|
252
385
|
)
|
|
253
386
|
return 'openapi'
|
|
254
387
|
} catch (err) {
|
|
255
|
-
logger.error(
|
|
388
|
+
logger.error(
|
|
389
|
+
err,
|
|
390
|
+
`Error parsing OpenAPI definition: "${err.message}". Trying with GraphQL`
|
|
391
|
+
)
|
|
256
392
|
// try GraphQL
|
|
257
393
|
const schema = graphql.buildSchema(text)
|
|
258
394
|
const introspectionResult = graphql.introspectionFromSchema(schema)
|
|
259
395
|
|
|
260
396
|
// dummy URL
|
|
261
|
-
await writeGraphQLClient(
|
|
397
|
+
await writeGraphQLClient(
|
|
398
|
+
folder,
|
|
399
|
+
name,
|
|
400
|
+
introspectionResult,
|
|
401
|
+
'http://localhost:3042/graphql',
|
|
402
|
+
generateImplementation,
|
|
403
|
+
moduleFormat,
|
|
404
|
+
typeExtension,
|
|
405
|
+
explicitModuleFormat
|
|
406
|
+
)
|
|
262
407
|
return 'graphql'
|
|
263
408
|
}
|
|
264
409
|
}
|
|
@@ -280,7 +425,10 @@ async function downloadAndProcess (options) {
|
|
|
280
425
|
typesComment,
|
|
281
426
|
withCredentials,
|
|
282
427
|
propsOptional,
|
|
283
|
-
retryTimeoutMs
|
|
428
|
+
retryTimeoutMs,
|
|
429
|
+
moduleFormat,
|
|
430
|
+
typeExtension,
|
|
431
|
+
explicitModuleFormat
|
|
284
432
|
} = options
|
|
285
433
|
|
|
286
434
|
const generateImplementation = options.generateImplementation
|
|
@@ -308,7 +456,10 @@ async function downloadAndProcess (options) {
|
|
|
308
456
|
typesComment,
|
|
309
457
|
withCredentials,
|
|
310
458
|
propsOptional,
|
|
311
|
-
retryTimeoutMs
|
|
459
|
+
retryTimeoutMs,
|
|
460
|
+
moduleFormat,
|
|
461
|
+
typeExtension,
|
|
462
|
+
explicitModuleFormat
|
|
312
463
|
)
|
|
313
464
|
)
|
|
314
465
|
toTry.push(
|
|
@@ -330,14 +481,39 @@ async function downloadAndProcess (options) {
|
|
|
330
481
|
typesComment,
|
|
331
482
|
withCredentials,
|
|
332
483
|
propsOptional,
|
|
333
|
-
retryTimeoutMs
|
|
484
|
+
retryTimeoutMs,
|
|
485
|
+
moduleFormat,
|
|
486
|
+
typeExtension,
|
|
487
|
+
explicitModuleFormat
|
|
334
488
|
)
|
|
335
489
|
)
|
|
336
490
|
} else if (options.type === 'graphql') {
|
|
337
491
|
toTry.push(
|
|
338
|
-
downloadAndWriteGraphQL.bind(
|
|
492
|
+
downloadAndWriteGraphQL.bind(
|
|
493
|
+
null,
|
|
494
|
+
logger,
|
|
495
|
+
url + '/graphql',
|
|
496
|
+
folder,
|
|
497
|
+
name,
|
|
498
|
+
generateImplementation,
|
|
499
|
+
moduleFormat,
|
|
500
|
+
typeExtension,
|
|
501
|
+
explicitModuleFormat
|
|
502
|
+
)
|
|
503
|
+
)
|
|
504
|
+
toTry.push(
|
|
505
|
+
downloadAndWriteGraphQL.bind(
|
|
506
|
+
null,
|
|
507
|
+
logger,
|
|
508
|
+
url,
|
|
509
|
+
folder,
|
|
510
|
+
name,
|
|
511
|
+
generateImplementation,
|
|
512
|
+
moduleFormat,
|
|
513
|
+
typeExtension,
|
|
514
|
+
explicitModuleFormat
|
|
515
|
+
)
|
|
339
516
|
)
|
|
340
|
-
toTry.push(downloadAndWriteGraphQL.bind(null, logger, url, folder, name, generateImplementation, typesOnly))
|
|
341
517
|
} else {
|
|
342
518
|
// add download functions only if it's an URL
|
|
343
519
|
toTry.push(
|
|
@@ -359,11 +535,24 @@ async function downloadAndProcess (options) {
|
|
|
359
535
|
typesComment,
|
|
360
536
|
withCredentials,
|
|
361
537
|
propsOptional,
|
|
362
|
-
retryTimeoutMs
|
|
538
|
+
retryTimeoutMs,
|
|
539
|
+
moduleFormat,
|
|
540
|
+
typeExtension,
|
|
541
|
+
explicitModuleFormat
|
|
363
542
|
)
|
|
364
543
|
)
|
|
365
544
|
toTry.push(
|
|
366
|
-
downloadAndWriteGraphQL.bind(
|
|
545
|
+
downloadAndWriteGraphQL.bind(
|
|
546
|
+
null,
|
|
547
|
+
logger,
|
|
548
|
+
url + '/graphql',
|
|
549
|
+
folder,
|
|
550
|
+
name,
|
|
551
|
+
generateImplementation,
|
|
552
|
+
moduleFormat,
|
|
553
|
+
typeExtension,
|
|
554
|
+
explicitModuleFormat
|
|
555
|
+
)
|
|
367
556
|
)
|
|
368
557
|
toTry.push(
|
|
369
558
|
downloadAndWriteOpenAPI.bind(
|
|
@@ -384,10 +573,25 @@ async function downloadAndProcess (options) {
|
|
|
384
573
|
typesComment,
|
|
385
574
|
withCredentials,
|
|
386
575
|
propsOptional,
|
|
387
|
-
retryTimeoutMs
|
|
576
|
+
retryTimeoutMs,
|
|
577
|
+
moduleFormat,
|
|
578
|
+
typeExtension,
|
|
579
|
+
explicitModuleFormat
|
|
580
|
+
)
|
|
581
|
+
)
|
|
582
|
+
toTry.push(
|
|
583
|
+
downloadAndWriteGraphQL.bind(
|
|
584
|
+
null,
|
|
585
|
+
logger,
|
|
586
|
+
url,
|
|
587
|
+
folder,
|
|
588
|
+
name,
|
|
589
|
+
generateImplementation,
|
|
590
|
+
moduleFormat,
|
|
591
|
+
typeExtension,
|
|
592
|
+
explicitModuleFormat
|
|
388
593
|
)
|
|
389
594
|
)
|
|
390
|
-
toTry.push(downloadAndWriteGraphQL.bind(null, logger, url, folder, name, generateImplementation, typesOnly))
|
|
391
595
|
}
|
|
392
596
|
} else {
|
|
393
597
|
// add readFromFileAndWrite to the functions only if it's not an URL
|
|
@@ -408,7 +612,10 @@ async function downloadAndProcess (options) {
|
|
|
408
612
|
language,
|
|
409
613
|
typesComment,
|
|
410
614
|
withCredentials,
|
|
411
|
-
propsOptional
|
|
615
|
+
propsOptional,
|
|
616
|
+
moduleFormat,
|
|
617
|
+
typeExtension,
|
|
618
|
+
explicitModuleFormat
|
|
412
619
|
)
|
|
413
620
|
)
|
|
414
621
|
}
|
|
@@ -420,18 +627,26 @@ async function downloadAndProcess (options) {
|
|
|
420
627
|
}
|
|
421
628
|
/* c8 ignore next 3 */
|
|
422
629
|
if (!found) {
|
|
423
|
-
throw new Error(
|
|
630
|
+
throw new Error(
|
|
631
|
+
`Could not find a valid OpenAPI or GraphQL schema at ${url}`
|
|
632
|
+
)
|
|
424
633
|
}
|
|
425
634
|
}
|
|
426
635
|
|
|
427
|
-
function getPackageJSON ({ name, generateImplementation }) {
|
|
636
|
+
async function getPackageJSON ({ name, generateImplementation, moduleFormat, folder, typeExtension, explicitModuleFormat }) {
|
|
637
|
+
const isESM = moduleFormat === 'esm'
|
|
638
|
+
const typeExt = await determineTypeExtension(folder, moduleFormat, typeExtension, explicitModuleFormat, generateImplementation)
|
|
428
639
|
const obj = {
|
|
429
640
|
name,
|
|
430
|
-
types: `./${name}
|
|
641
|
+
types: `./${name}.${typeExt}`
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (isESM) {
|
|
645
|
+
obj.type = 'module'
|
|
431
646
|
}
|
|
432
647
|
|
|
433
648
|
if (generateImplementation) {
|
|
434
|
-
obj.main = `./${name}
|
|
649
|
+
obj.main = `./${name}.${isESM ? 'mjs' : 'cjs'}`
|
|
435
650
|
}
|
|
436
651
|
|
|
437
652
|
return JSON.stringify(obj, null, 2)
|
|
@@ -447,7 +662,16 @@ export async function command (argv) {
|
|
|
447
662
|
_: [url],
|
|
448
663
|
...options
|
|
449
664
|
} = parseArgs(argv, {
|
|
450
|
-
string: [
|
|
665
|
+
string: [
|
|
666
|
+
'name',
|
|
667
|
+
'folder',
|
|
668
|
+
'optional-headers',
|
|
669
|
+
'language',
|
|
670
|
+
'type',
|
|
671
|
+
'url-auth-headers',
|
|
672
|
+
'types-comment',
|
|
673
|
+
'module'
|
|
674
|
+
],
|
|
451
675
|
boolean: [
|
|
452
676
|
'typescript',
|
|
453
677
|
'full-response',
|
|
@@ -457,7 +681,8 @@ export async function command (argv) {
|
|
|
457
681
|
'frontend',
|
|
458
682
|
'validate-response',
|
|
459
683
|
'props-optional',
|
|
460
|
-
'skip-config-update'
|
|
684
|
+
'skip-config-update',
|
|
685
|
+
'type-extension'
|
|
461
686
|
],
|
|
462
687
|
default: {
|
|
463
688
|
typescript: false,
|
|
@@ -507,7 +732,7 @@ export async function command (argv) {
|
|
|
507
732
|
options.propsOptional = options['props-optional'] ?? true
|
|
508
733
|
|
|
509
734
|
options.optionalHeaders = options['optional-headers']
|
|
510
|
-
? options['optional-headers'].split(',').map(h => h.trim())
|
|
735
|
+
? options['optional-headers'].split(',').map((h) => h.trim())
|
|
511
736
|
: []
|
|
512
737
|
|
|
513
738
|
options.validateResponse = options['validate-response']
|
|
@@ -516,11 +741,17 @@ export async function command (argv) {
|
|
|
516
741
|
options.name = options.isFrontend ? 'api' : 'client'
|
|
517
742
|
}
|
|
518
743
|
options.folder = options.folder || join(process.cwd(), options.name)
|
|
744
|
+
options.moduleFormat = await detectModuleFormat(options.folder, options.module)
|
|
745
|
+
if (!options.module) {
|
|
746
|
+
logger.info(`Module format detected: ${options.moduleFormat}`)
|
|
747
|
+
}
|
|
519
748
|
options.urlAuthHeaders = options['url-auth-headers']
|
|
520
749
|
options.typesComment = options['types-comment']
|
|
521
750
|
options.withCredentials = options['with-credentials']
|
|
522
751
|
options.skipConfigUpdate = options['skip-config-update'] ?? true
|
|
523
752
|
options.retryTimeoutMs = options['retry-timeout-ms']
|
|
753
|
+
options.typeExtension = options['type-extension']
|
|
754
|
+
options.explicitModuleFormat = !!options.module
|
|
524
755
|
await downloadAndProcess({ url, ...options, logger })
|
|
525
756
|
logger.info(`Client generated successfully into ${options.folder}`)
|
|
526
757
|
logger.info('Check out the docs to know more: https://docs.platformatic.dev/docs/service/overview')
|
|
@@ -18,7 +18,8 @@ export function processFrontendOpenAPI ({
|
|
|
18
18
|
fullRequest,
|
|
19
19
|
logger,
|
|
20
20
|
withCredentials,
|
|
21
|
-
propsOptional
|
|
21
|
+
propsOptional,
|
|
22
|
+
typeExt = 'd.mts'
|
|
22
23
|
}) {
|
|
23
24
|
return {
|
|
24
25
|
types: generateTypesFromOpenAPI({ schema, name, fullResponse, fullRequest, propsOptional }),
|
|
@@ -29,7 +30,8 @@ export function processFrontendOpenAPI ({
|
|
|
29
30
|
fullResponse,
|
|
30
31
|
fullRequest,
|
|
31
32
|
logger,
|
|
32
|
-
withCredentials
|
|
33
|
+
withCredentials,
|
|
34
|
+
typeExt
|
|
33
35
|
})
|
|
34
36
|
}
|
|
35
37
|
}
|
|
@@ -41,7 +43,8 @@ function generateFrontendImplementationFromOpenAPI ({
|
|
|
41
43
|
fullResponse,
|
|
42
44
|
fullRequest,
|
|
43
45
|
logger,
|
|
44
|
-
withCredentials
|
|
46
|
+
withCredentials,
|
|
47
|
+
typeExt = 'd.mts'
|
|
45
48
|
}) {
|
|
46
49
|
const camelCaseName = capitalize(camelcase(name))
|
|
47
50
|
const { paths } = schema
|
|
@@ -111,13 +114,13 @@ function generateFrontendImplementationFromOpenAPI ({
|
|
|
111
114
|
writer.write('function sanitizeUrl(url)').block(() => {
|
|
112
115
|
writer.writeLine("if (url.endsWith('/')) { return url.slice(0, -1) } else { return url }")
|
|
113
116
|
})
|
|
114
|
-
writer.writeLine(`/** @type {import('./${name}-types
|
|
117
|
+
writer.writeLine(`/** @type {import('./${name}-types.${typeExt}').${camelCaseName}['setBaseUrl']} */`)
|
|
115
118
|
writer.writeLine('export const setBaseUrl = (newUrl) => { baseUrl = sanitizeUrl(newUrl) }')
|
|
116
119
|
writer.newLine()
|
|
117
|
-
writer.writeLine(`/** @type {import('./${name}-types
|
|
120
|
+
writer.writeLine(`/** @type {import('./${name}-types.${typeExt}').${camelCaseName}['setDefaultHeaders']} */`)
|
|
118
121
|
writer.writeLine('export const setDefaultHeaders = (headers) => { defaultHeaders = headers }')
|
|
119
122
|
writer.newLine()
|
|
120
|
-
writer.writeLine(`/** @type {import('./${name}-types
|
|
123
|
+
writer.writeLine(`/** @type {import('./${name}-types.${typeExt}').${camelCaseName}['setDefaultFetchParams']} */`)
|
|
121
124
|
writer.writeLine('export const setDefaultFetchParams = (fetchParams) => { defaultFetchParams = fetchParams }')
|
|
122
125
|
writer.newLine()
|
|
123
126
|
writer.write('function headersToJSON(headers) ').block(() => {
|
|
@@ -354,7 +357,7 @@ function generateFrontendImplementationFromOpenAPI ({
|
|
|
354
357
|
// ```
|
|
355
358
|
//
|
|
356
359
|
writer
|
|
357
|
-
.writeLine(`/** @type {import('./${name}-types
|
|
360
|
+
.writeLine(`/** @type {import('./${name}-types.${typeExt}').${camelCaseName}['${operationId}']} */`)
|
|
358
361
|
.write(`export const ${operationId} = async (request) =>`)
|
|
359
362
|
.block(() => {
|
|
360
363
|
writer.write(`return await ${underscoredOperationId}(baseUrl, request)`)
|
package/lib/graphql-generator.js
CHANGED
|
@@ -2,11 +2,11 @@ import CodeBlockWriter from 'code-block-writer'
|
|
|
2
2
|
import { UnknownTypeError } from './errors.js'
|
|
3
3
|
import { capitalize, toJavaScriptName } from './utils.js'
|
|
4
4
|
|
|
5
|
-
export function processGraphQL ({ schema, name, folder, url }) {
|
|
5
|
+
export function processGraphQL ({ schema, name, folder, url, moduleFormat }) {
|
|
6
6
|
schema = schema.__schema
|
|
7
7
|
return {
|
|
8
8
|
types: generateTypesFromGraphQL({ schema, name }),
|
|
9
|
-
implementation: generateImplementationFromGraqhQL({ schema, name, url })
|
|
9
|
+
implementation: generateImplementationFromGraqhQL({ schema, name, url, moduleFormat })
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -60,8 +60,9 @@ function generateTypesFromGraphQL ({ schema, name }) {
|
|
|
60
60
|
return writer.toString()
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
function generateImplementationFromGraqhQL ({ name, url }) {
|
|
63
|
+
function generateImplementationFromGraqhQL ({ name, url, moduleFormat }) {
|
|
64
64
|
const camelcasedName = toJavaScriptName(name)
|
|
65
|
+
const isESM = moduleFormat === 'esm'
|
|
65
66
|
|
|
66
67
|
const writer = new CodeBlockWriter({
|
|
67
68
|
indentNumberOfSpaces: 2,
|
|
@@ -69,27 +70,40 @@ function generateImplementationFromGraqhQL ({ name, url }) {
|
|
|
69
70
|
useSingleQuote: true
|
|
70
71
|
})
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
if (isESM) {
|
|
74
|
+
writer.writeLine("import { buildGraphQLClient } from 'massimo'")
|
|
75
|
+
writer.writeLine("import { join } from 'node:path'")
|
|
76
|
+
} else {
|
|
77
|
+
writer.writeLine("const { buildGraphQLClient } = require('massimo')")
|
|
78
|
+
writer.writeLine("const { join } = require('node:path')")
|
|
79
|
+
}
|
|
74
80
|
writer.blankLine()
|
|
75
81
|
|
|
76
82
|
url = new URL(url)
|
|
77
83
|
|
|
78
84
|
const functionName = `generate${capitalize(camelcasedName)}Client`
|
|
79
|
-
|
|
85
|
+
const funcDecl = isESM ? `export async function ${functionName} (opts)` : `async function ${functionName} (opts)`
|
|
86
|
+
writer.write(funcDecl).block(() => {
|
|
80
87
|
writer.writeLine('const url = new URL(opts.url)')
|
|
81
88
|
writer.writeLine(`url.pathname = '${url.pathname}'`)
|
|
82
89
|
writer.write('return buildGraphQLClient(').inlineBlock(() => {
|
|
83
90
|
writer.writeLine("type: 'graphql',")
|
|
84
91
|
writer.writeLine(`name: '${camelcasedName}',`)
|
|
85
|
-
|
|
92
|
+
const pathExpr = isESM ? `join(import.meta.dirname, '${name}.schema.graphql')` : `join(__dirname, '${name}.schema.graphql')`
|
|
93
|
+
writer.writeLine(`path: ${pathExpr},`)
|
|
86
94
|
writer.writeLine('serviceId: opts.serviceId,')
|
|
87
95
|
writer.writeLine('url: url.toString()')
|
|
88
96
|
})
|
|
89
97
|
writer.write(')')
|
|
90
98
|
})
|
|
91
99
|
writer.blankLine()
|
|
92
|
-
|
|
100
|
+
if (isESM) {
|
|
101
|
+
writer.writeLine(`export default ${functionName}`)
|
|
102
|
+
} else {
|
|
103
|
+
writer.writeLine(`module.exports = ${functionName}`)
|
|
104
|
+
writer.writeLine(`module.exports.default = ${functionName}`)
|
|
105
|
+
writer.writeLine(`module.exports.${functionName} = ${functionName}`)
|
|
106
|
+
}
|
|
93
107
|
return writer.toString()
|
|
94
108
|
}
|
|
95
109
|
|
package/lib/openapi-generator.js
CHANGED
|
@@ -11,7 +11,8 @@ export function processOpenAPI ({
|
|
|
11
11
|
optionalHeaders,
|
|
12
12
|
validateResponse,
|
|
13
13
|
typesComment,
|
|
14
|
-
propsOptional
|
|
14
|
+
propsOptional,
|
|
15
|
+
moduleFormat
|
|
15
16
|
}) {
|
|
16
17
|
return {
|
|
17
18
|
types: generateTypesFromOpenAPI({
|
|
@@ -23,12 +24,13 @@ export function processOpenAPI ({
|
|
|
23
24
|
typesComment,
|
|
24
25
|
propsOptional
|
|
25
26
|
}),
|
|
26
|
-
implementation: generateImplementationFromOpenAPI({ name, fullResponse, fullRequest, validateResponse })
|
|
27
|
+
implementation: generateImplementationFromOpenAPI({ name, fullResponse, fullRequest, validateResponse, moduleFormat })
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
function generateImplementationFromOpenAPI ({ name, fullResponse, fullRequest, validateResponse }) {
|
|
31
|
+
function generateImplementationFromOpenAPI ({ name, fullResponse, fullRequest, validateResponse, moduleFormat }) {
|
|
31
32
|
const camelcasedName = toJavaScriptName(name)
|
|
33
|
+
const isESM = moduleFormat === 'esm'
|
|
32
34
|
|
|
33
35
|
const writer = new CodeBlockWriter({
|
|
34
36
|
indentNumberOfSpaces: 2,
|
|
@@ -36,16 +38,23 @@ function generateImplementationFromOpenAPI ({ name, fullResponse, fullRequest, v
|
|
|
36
38
|
useSingleQuote: true
|
|
37
39
|
})
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
if (isESM) {
|
|
42
|
+
writer.writeLine("import { buildOpenAPIClient } from 'massimo'")
|
|
43
|
+
writer.writeLine("import { join } from 'node:path'")
|
|
44
|
+
} else {
|
|
45
|
+
writer.writeLine("const { buildOpenAPIClient } = require('massimo')")
|
|
46
|
+
writer.writeLine("const { join } = require('node:path')")
|
|
47
|
+
}
|
|
41
48
|
writer.blankLine()
|
|
42
49
|
|
|
43
50
|
const functionName = `generate${capitalize(camelcasedName)}Client`
|
|
44
|
-
|
|
51
|
+
const funcDecl = isESM ? `export async function ${functionName} (opts)` : `async function ${functionName} (opts)`
|
|
52
|
+
writer.write(funcDecl).block(() => {
|
|
45
53
|
writer.write('return buildOpenAPIClient(').inlineBlock(() => {
|
|
46
54
|
writer.writeLine("type: 'openapi',")
|
|
47
55
|
writer.writeLine(`name: '${camelcasedName}',`)
|
|
48
|
-
|
|
56
|
+
const pathExpr = isESM ? `join(import.meta.dirname, '${name}.openapi.json')` : `join(__dirname, '${name}.openapi.json')`
|
|
57
|
+
writer.writeLine(`path: ${pathExpr},`)
|
|
49
58
|
writer.writeLine('url: opts.url,')
|
|
50
59
|
writer.writeLine('serviceId: opts.serviceId,')
|
|
51
60
|
writer.writeLine('throwOnError: opts.throwOnError,')
|
|
@@ -57,7 +66,13 @@ function generateImplementationFromOpenAPI ({ name, fullResponse, fullRequest, v
|
|
|
57
66
|
writer.write(')')
|
|
58
67
|
})
|
|
59
68
|
writer.blankLine()
|
|
60
|
-
|
|
69
|
+
if (isESM) {
|
|
70
|
+
writer.writeLine(`export default ${functionName}`)
|
|
71
|
+
} else {
|
|
72
|
+
writer.writeLine(`module.exports = ${functionName}`)
|
|
73
|
+
writer.writeLine(`module.exports.default = ${functionName}`)
|
|
74
|
+
writer.writeLine(`module.exports.${functionName} = ${functionName}`)
|
|
75
|
+
}
|
|
61
76
|
return writer.toString()
|
|
62
77
|
}
|
|
63
78
|
|
|
@@ -68,7 +83,7 @@ function generateTypesFromOpenAPI ({
|
|
|
68
83
|
fullRequest,
|
|
69
84
|
optionalHeaders,
|
|
70
85
|
typesComment,
|
|
71
|
-
propsOptional
|
|
86
|
+
propsOptional,
|
|
72
87
|
}) {
|
|
73
88
|
const camelcasedName = toJavaScriptName(name)
|
|
74
89
|
const capitalizedName = capitalize(camelcasedName)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "massimo-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "A client for HTTP services.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"pino-pretty": "^13.0.0",
|
|
31
31
|
"undici": "^7.0.0",
|
|
32
32
|
"yaml": "^2.4.1",
|
|
33
|
-
"massimo": "0.
|
|
33
|
+
"massimo": "0.5.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@platformatic/composer": "3.0.0-alpha.6",
|