@kubb/swagger-ts 2.0.0-alpha.2 → 2.0.0-alpha.4

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.
@@ -0,0 +1,211 @@
1
+ import { getRelativePath } from '@kubb/core/utils'
2
+ import { print } from '@kubb/parser'
3
+ import * as factory from '@kubb/parser/factory'
4
+ import { OperationGenerator as Generator, resolve } from '@kubb/swagger'
5
+
6
+ import { pascalCase } from 'change-case'
7
+
8
+ import { TypeBuilder } from '../builders/index.ts'
9
+
10
+ import type { KubbFile } from '@kubb/core'
11
+ import type { FileResolver, Operation, OperationSchemas, Resolver } from '@kubb/swagger'
12
+ import type ts from 'typescript'
13
+ import type { FileMeta, Options as PluginOptions } from '../types.ts'
14
+
15
+ type Options = {
16
+ usedEnumNames: Record<string, number>
17
+
18
+ mode: KubbFile.Mode
19
+ enumType: NonNullable<PluginOptions['enumType']>
20
+ dateType: NonNullable<PluginOptions['dateType']>
21
+ optionalType: NonNullable<PluginOptions['optionalType']>
22
+ }
23
+
24
+ export class OperationGenerator extends Generator<Options> {
25
+ resolve(operation: Operation): Resolver {
26
+ const { pluginManager, plugin } = this.context
27
+
28
+ return resolve({
29
+ operation,
30
+ resolveName: pluginManager.resolveName,
31
+ resolvePath: pluginManager.resolvePath,
32
+ pluginKey: plugin?.key,
33
+ })
34
+ }
35
+
36
+ async all(): Promise<KubbFile.File | null> {
37
+ return null
38
+ }
39
+
40
+ #printCombinedSchema(name: string, operation: Operation, schemas: OperationSchemas): string {
41
+ const properties: Record<string, ts.TypeNode> = {
42
+ 'response': factory.createTypeReferenceNode(
43
+ factory.createIdentifier(schemas.response.name),
44
+ undefined,
45
+ ),
46
+ }
47
+
48
+ if (schemas.request) {
49
+ properties['request'] = factory.createTypeReferenceNode(
50
+ factory.createIdentifier(schemas.request.name),
51
+ undefined,
52
+ )
53
+ }
54
+
55
+ if (schemas.pathParams) {
56
+ properties['pathParams'] = factory.createTypeReferenceNode(
57
+ factory.createIdentifier(schemas.pathParams.name),
58
+ undefined,
59
+ )
60
+ }
61
+
62
+ if (schemas.queryParams) {
63
+ properties['queryParams'] = factory.createTypeReferenceNode(
64
+ factory.createIdentifier(schemas.queryParams.name),
65
+ undefined,
66
+ )
67
+ }
68
+
69
+ if (schemas.headerParams) {
70
+ properties['headerParams'] = factory.createTypeReferenceNode(
71
+ factory.createIdentifier(schemas.headerParams.name),
72
+ undefined,
73
+ )
74
+ }
75
+
76
+ if (schemas.errors) {
77
+ properties['errors'] = factory.createUnionDeclaration({
78
+ nodes: schemas.errors.map(error => {
79
+ return factory.createTypeReferenceNode(
80
+ factory.createIdentifier(error.name),
81
+ undefined,
82
+ )
83
+ }),
84
+ })!
85
+ }
86
+
87
+ const namespaceNode = factory.createNamespaceDeclaration({
88
+ name: operation.method === 'get' ? `${name}Query` : `${name}Mutation`,
89
+ statements: Object.keys(properties).map(key => {
90
+ const type = properties[key]
91
+ if (!type) {
92
+ return undefined
93
+ }
94
+ return factory.createTypeAliasDeclaration({
95
+ modifiers: [factory.modifiers.export],
96
+ name: pascalCase(key),
97
+ type,
98
+ })
99
+ }).filter(Boolean),
100
+ })
101
+
102
+ return print(namespaceNode)
103
+ }
104
+
105
+ async get(operation: Operation, schemas: OperationSchemas, options: Options): Promise<KubbFile.File<FileMeta> | null> {
106
+ const { mode, enumType, dateType, optionalType, usedEnumNames } = options
107
+ const { pluginManager, plugin } = this.context
108
+
109
+ const type = this.resolve(operation)
110
+
111
+ const fileResolver: FileResolver = (name) => {
112
+ // Used when a react-query type(request, response, params) has an import of a global type
113
+ const root = pluginManager.resolvePath({ baseName: type.baseName, pluginKey: plugin?.key, options: { tag: operation.getTags()[0]?.name } })
114
+ // refs import, will always been created with the SwaggerTS plugin, our global type
115
+ const resolvedTypeId = pluginManager.resolvePath({
116
+ baseName: `${name}.ts`,
117
+ pluginKey: plugin?.key,
118
+ })
119
+
120
+ return getRelativePath(root, resolvedTypeId)
121
+ }
122
+
123
+ const source = new TypeBuilder({
124
+ usedEnumNames,
125
+ fileResolver: mode === 'file' ? undefined : fileResolver,
126
+ withJSDocs: true,
127
+ resolveName: (params) => pluginManager.resolveName({ ...params, pluginKey: plugin?.key }),
128
+ enumType,
129
+ optionalType,
130
+ dateType,
131
+ })
132
+ .add(schemas.pathParams)
133
+ .add(schemas.queryParams)
134
+ .add(schemas.headerParams)
135
+ .add(schemas.response)
136
+ .add(schemas.errors)
137
+ .configure()
138
+ .print()
139
+
140
+ const combinedSchemaSource = this.#printCombinedSchema(type.name, operation, schemas)
141
+
142
+ return {
143
+ path: type.path,
144
+ baseName: type.baseName,
145
+ source: [source, combinedSchemaSource].join('\n'),
146
+ meta: {
147
+ pluginKey: plugin.key,
148
+ tag: operation.getTags()[0]?.name,
149
+ },
150
+ }
151
+ }
152
+
153
+ async post(operation: Operation, schemas: OperationSchemas, options: Options): Promise<KubbFile.File<FileMeta> | null> {
154
+ const { mode, enumType, dateType, optionalType, usedEnumNames } = options
155
+ const { pluginManager, plugin } = this.context
156
+
157
+ const type = this.resolve(operation)
158
+
159
+ const fileResolver: FileResolver = (name) => {
160
+ // Used when a react-query type(request, response, params) has an import of a global type
161
+ const root = pluginManager.resolvePath({ baseName: type.baseName, pluginKey: plugin?.key, options: { tag: operation.getTags()[0]?.name } })
162
+ // refs import, will always been created with the SwaggerTS plugin, our global type
163
+ const resolvedTypeId = pluginManager.resolvePath({
164
+ baseName: `${name}.ts`,
165
+ pluginKey: plugin?.key,
166
+ })
167
+
168
+ return getRelativePath(root, resolvedTypeId)
169
+ }
170
+
171
+ const source = new TypeBuilder({
172
+ usedEnumNames,
173
+ fileResolver: mode === 'file' ? undefined : fileResolver,
174
+ withJSDocs: true,
175
+ resolveName: (params) => pluginManager.resolveName({ ...params, pluginKey: plugin?.key }),
176
+ enumType,
177
+ optionalType,
178
+ dateType,
179
+ })
180
+ .add(schemas.pathParams)
181
+ .add(schemas.queryParams)
182
+ .add(schemas.headerParams)
183
+ .add(schemas.request)
184
+ .add(schemas.response)
185
+ .add(schemas.errors)
186
+ .configure()
187
+ .print()
188
+
189
+ const combinedSchemaSource = this.#printCombinedSchema(type.name, operation, schemas)
190
+
191
+ return {
192
+ path: type.path,
193
+ baseName: type.baseName,
194
+ source: [source, combinedSchemaSource].join('\n'),
195
+ meta: {
196
+ pluginKey: plugin.key,
197
+ tag: operation.getTags()[0]?.name,
198
+ },
199
+ }
200
+ }
201
+
202
+ async put(operation: Operation, schemas: OperationSchemas, options: Options): Promise<KubbFile.File<FileMeta> | null> {
203
+ return this.post(operation, schemas, options)
204
+ }
205
+ async patch(operation: Operation, schemas: OperationSchemas, options: Options): Promise<KubbFile.File<FileMeta> | null> {
206
+ return this.post(operation, schemas, options)
207
+ }
208
+ async delete(operation: Operation, schemas: OperationSchemas, options: Options): Promise<KubbFile.File<FileMeta> | null> {
209
+ return this.post(operation, schemas, options)
210
+ }
211
+ }
@@ -0,0 +1,378 @@
1
+ import { SchemaGenerator } from '@kubb/core'
2
+ import { getUniqueName } from '@kubb/core/utils'
3
+ import * as factory from '@kubb/parser/factory'
4
+ import { isReference } from '@kubb/swagger/utils'
5
+
6
+ import { camelCase } from 'change-case'
7
+
8
+ import type { PluginContext } from '@kubb/core'
9
+ import type { ts } from '@kubb/parser'
10
+ import type { OpenAPIV3, Refs } from '@kubb/swagger'
11
+ import type { Options as CaseOptions } from 'change-case'
12
+
13
+ // based on https://github.com/cellular/oazapfts/blob/7ba226ebb15374e8483cc53e7532f1663179a22c/src/codegen/generate.ts#L398
14
+
15
+ type Options = {
16
+ usedEnumNames: Record<string, number>
17
+
18
+ withJSDocs?: boolean
19
+ resolveName: PluginContext['resolveName']
20
+ enumType: 'enum' | 'asConst' | 'asPascalConst'
21
+ dateType: 'string' | 'date'
22
+ optionalType: 'questionToken' | 'undefined' | 'questionTokenAndUndefined'
23
+ }
24
+ export class TypeGenerator extends SchemaGenerator<Options, OpenAPIV3.SchemaObject, ts.Node[]> {
25
+ refs: Refs = {}
26
+
27
+ extraNodes: ts.Node[] = []
28
+
29
+ aliases: ts.TypeAliasDeclaration[] = []
30
+
31
+ // Keep track of already used type aliases
32
+ #usedAliasNames: Record<string, number> = {}
33
+
34
+ #caseOptions: CaseOptions = {
35
+ delimiter: '',
36
+ stripRegexp: /[^A-Z0-9$]/gi,
37
+ }
38
+
39
+ constructor(
40
+ options: Options = {
41
+ usedEnumNames: {},
42
+ withJSDocs: true,
43
+ resolveName: ({ name }) => name,
44
+ enumType: 'asConst',
45
+ dateType: 'string',
46
+ optionalType: 'questionToken',
47
+ },
48
+ ) {
49
+ super(options)
50
+
51
+ return this
52
+ }
53
+
54
+ build({
55
+ schema,
56
+ baseName,
57
+ description,
58
+ keysToOmit,
59
+ }: {
60
+ schema: OpenAPIV3.SchemaObject
61
+ baseName: string
62
+ description?: string
63
+ keysToOmit?: string[]
64
+ }): ts.Node[] {
65
+ const nodes: ts.Node[] = []
66
+ const type = this.#getTypeFromSchema(schema, baseName)
67
+
68
+ if (!type) {
69
+ return this.extraNodes
70
+ }
71
+
72
+ const node = factory.createTypeAliasDeclaration({
73
+ modifiers: [factory.modifiers.export],
74
+ name: this.options.resolveName({ name: baseName }) || baseName,
75
+ type: keysToOmit?.length ? factory.createOmitDeclaration({ keys: keysToOmit, type, nonNullable: true }) : type,
76
+ })
77
+
78
+ if (description) {
79
+ nodes.push(
80
+ factory.appendJSDocToNode({
81
+ node,
82
+ comments: [`@description ${description}`],
83
+ }),
84
+ )
85
+ } else {
86
+ nodes.push(node)
87
+ }
88
+
89
+ // filter out if the export name is the same as one that we already defined in extraNodes(see enum)
90
+ const filterdNodes = nodes.filter(
91
+ (node: ts.Node) =>
92
+ !this.extraNodes.some(
93
+ (extraNode: ts.Node) => (extraNode as ts.TypeAliasDeclaration)?.name?.escapedText === (node as ts.TypeAliasDeclaration)?.name?.escapedText,
94
+ ),
95
+ )
96
+
97
+ return [...this.extraNodes, ...filterdNodes]
98
+ }
99
+
100
+ /**
101
+ * Creates a type node from a given schema.
102
+ * Delegates to getBaseTypeFromSchema internally and
103
+ * optionally adds a union with null.
104
+ */
105
+ #getTypeFromSchema(schema?: OpenAPIV3.SchemaObject, name?: string): ts.TypeNode | null {
106
+ const type = this.#getBaseTypeFromSchema(schema, name)
107
+
108
+ if (!type) {
109
+ return null
110
+ }
111
+
112
+ if (schema && !schema.nullable) {
113
+ return type
114
+ }
115
+
116
+ return factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.null] })
117
+ }
118
+
119
+ /**
120
+ * Recursively creates a type literal with the given props.
121
+ */
122
+ #getTypeFromProperties(baseSchema?: OpenAPIV3.SchemaObject, baseName?: string) {
123
+ const { optionalType } = this.options
124
+ const properties = baseSchema?.properties || {}
125
+ const required = baseSchema?.required
126
+ const additionalProperties = baseSchema?.additionalProperties
127
+
128
+ const members: Array<ts.TypeElement | null> = Object.keys(properties).map((name) => {
129
+ const schema = properties[name] as OpenAPIV3.SchemaObject
130
+
131
+ const isRequired = required && required.includes(name)
132
+ let type = this.#getTypeFromSchema(schema, this.options.resolveName({ name: `${baseName || ''} ${name}` }))
133
+
134
+ if (!type) {
135
+ return null
136
+ }
137
+
138
+ if (!isRequired && ['undefined', 'questionTokenAndUndefined'].includes(optionalType)) {
139
+ type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.undefined] })
140
+ }
141
+ const propertySignature = factory.createPropertySignature({
142
+ questionToken: ['questionToken', 'questionTokenAndUndefined'].includes(optionalType) && !isRequired,
143
+ name,
144
+ type: type as ts.TypeNode,
145
+ readOnly: schema.readOnly,
146
+ })
147
+ if (this.options.withJSDocs) {
148
+ return factory.appendJSDocToNode({
149
+ node: propertySignature,
150
+ comments: [
151
+ schema.description ? `@description ${schema.description}` : undefined,
152
+ schema.type ? `@type ${schema.type}${isRequired ? '' : ' | undefined'} ${schema.format || ''}` : undefined,
153
+ schema.example ? `@example ${schema.example as string}` : undefined,
154
+ schema.deprecated ? `@deprecated` : undefined,
155
+ schema.default !== undefined && typeof schema.default === 'string' ? `@default '${schema.default}'` : undefined,
156
+ schema.default !== undefined && typeof schema.default !== 'string' ? `@default ${schema.default as string}` : undefined,
157
+ ].filter(Boolean),
158
+ })
159
+ }
160
+
161
+ return propertySignature
162
+ })
163
+ if (additionalProperties) {
164
+ const type = additionalProperties === true ? factory.keywordTypeNodes.any : this.#getTypeFromSchema(additionalProperties as OpenAPIV3.SchemaObject)
165
+
166
+ if (type) {
167
+ members.push(factory.createIndexSignature(type))
168
+ }
169
+ }
170
+ return factory.createTypeLiteralNode(members.filter(Boolean))
171
+ }
172
+
173
+ /**
174
+ * Create a type alias for the schema referenced by the given ReferenceObject
175
+ */
176
+ #getRefAlias(obj: OpenAPIV3.ReferenceObject, _baseName?: string) {
177
+ const { $ref } = obj
178
+ let ref = this.refs[$ref]
179
+
180
+ if (ref) {
181
+ return factory.createTypeReferenceNode(ref.propertyName, undefined)
182
+ }
183
+
184
+ const originalName = getUniqueName($ref.replace(/.+\//, ''), this.#usedAliasNames)
185
+ const propertyName = this.options.resolveName({ name: originalName }) || originalName
186
+
187
+ ref = this.refs[$ref] = {
188
+ propertyName,
189
+ originalName,
190
+ }
191
+
192
+ return factory.createTypeReferenceNode(ref.propertyName, undefined)
193
+ }
194
+
195
+ /**
196
+ * This is the very core of the OpenAPI to TS conversion - it takes a
197
+ * schema and returns the appropriate type.
198
+ */
199
+ #getBaseTypeFromSchema(schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined, baseName?: string): ts.TypeNode | null {
200
+ if (!schema) {
201
+ return factory.keywordTypeNodes.any
202
+ }
203
+
204
+ if (isReference(schema)) {
205
+ return this.#getRefAlias(schema, baseName)
206
+ }
207
+
208
+ if (schema.oneOf) {
209
+ // union
210
+ const schemaWithoutOneOf = { ...schema, oneOf: undefined }
211
+
212
+ const union = factory.createUnionDeclaration({
213
+ withParentheses: true,
214
+ nodes: schema.oneOf
215
+ .map((item: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject) => {
216
+ return this.#getBaseTypeFromSchema(item)
217
+ })
218
+ .filter((item) => {
219
+ return item && item !== factory.keywordTypeNodes.any
220
+ }) as Array<ts.TypeNode>,
221
+ })
222
+
223
+ if (schemaWithoutOneOf.properties) {
224
+ return factory.createIntersectionDeclaration({
225
+ nodes: [this.#getBaseTypeFromSchema(schemaWithoutOneOf, baseName), union].filter(Boolean),
226
+ })
227
+ }
228
+
229
+ return union
230
+ }
231
+
232
+ if (schema.anyOf) {
233
+ const schemaWithoutAnyOf = { ...schema, anyOf: undefined }
234
+
235
+ const union = factory.createUnionDeclaration({
236
+ withParentheses: true,
237
+ nodes: schema.anyOf
238
+ .map((item: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject) => {
239
+ return this.#getBaseTypeFromSchema(item)
240
+ })
241
+ .filter((item) => {
242
+ return item && item !== factory.keywordTypeNodes.any
243
+ }) as Array<ts.TypeNode>,
244
+ })
245
+
246
+ if (schemaWithoutAnyOf.properties) {
247
+ return factory.createIntersectionDeclaration({
248
+ nodes: [this.#getBaseTypeFromSchema(schemaWithoutAnyOf, baseName), union].filter(Boolean),
249
+ })
250
+ }
251
+
252
+ return union
253
+ }
254
+ if (schema.allOf) {
255
+ // intersection/add
256
+ const schemaWithoutAllOf = { ...schema, allOf: undefined }
257
+
258
+ const and = factory.createIntersectionDeclaration({
259
+ withParentheses: true,
260
+ nodes: schema.allOf
261
+ .map((item: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject) => {
262
+ return this.#getBaseTypeFromSchema(item)
263
+ })
264
+ .filter((item) => {
265
+ return item && item !== factory.keywordTypeNodes.any
266
+ }) as Array<ts.TypeNode>,
267
+ })
268
+
269
+ if (schemaWithoutAllOf.properties) {
270
+ return factory.createIntersectionDeclaration({
271
+ nodes: [this.#getBaseTypeFromSchema(schemaWithoutAllOf, baseName), and].filter(Boolean),
272
+ })
273
+ }
274
+
275
+ return and
276
+ }
277
+
278
+ /**
279
+ * Enum will be defined outside the baseType(hints the baseName check)
280
+ */
281
+ if (schema.enum && baseName) {
282
+ const enumName = getUniqueName(baseName, this.options.usedEnumNames)
283
+
284
+ let enums: [key: string, value: string | number][] = [...new Set(schema.enum)].map((key) => [key, key])
285
+
286
+ if ('x-enumNames' in schema) {
287
+ enums = [...new Set(schema['x-enumNames'] as string[])].map((key: string, index) => {
288
+ return [key, schema.enum?.[index]]
289
+ })
290
+ }
291
+
292
+ this.extraNodes.push(
293
+ ...factory.createEnumDeclaration({
294
+ name: camelCase(enumName, this.#caseOptions),
295
+ typeName: this.options.resolveName({ name: enumName }),
296
+ enums,
297
+ type: this.options.enumType,
298
+ }),
299
+ )
300
+ return factory.createTypeReferenceNode(this.options.resolveName({ name: enumName }), undefined)
301
+ }
302
+
303
+ if (schema.enum) {
304
+ return factory.createUnionDeclaration({
305
+ nodes: schema.enum.map((name: string) => {
306
+ return factory.createLiteralTypeNode(typeof name === 'number' ? factory.createNumericLiteral(name) : factory.createStringLiteral(`${name}`))
307
+ }) as unknown as Array<ts.TypeNode>,
308
+ })
309
+ }
310
+
311
+ if ('items' in schema) {
312
+ // items -> array
313
+ const node = this.#getTypeFromSchema(schema.items as OpenAPIV3.SchemaObject, baseName)
314
+ if (node) {
315
+ return factory.createArrayTypeNode(node)
316
+ }
317
+ }
318
+ /**
319
+ * OpenAPI 3.1
320
+ * @link https://json-schema.org/understanding-json-schema/reference/array.html#tuple-validation
321
+ */
322
+ if ('prefixItems' in schema) {
323
+ const prefixItems = schema.prefixItems as OpenAPIV3.SchemaObject[]
324
+
325
+ return factory.createTupleDeclaration({
326
+ nodes: prefixItems.map((item) => {
327
+ // no baseType so we can fall back on an union when using enum
328
+ return this.#getBaseTypeFromSchema(item, undefined)
329
+ }) as Array<ts.TypeNode>,
330
+ })
331
+ }
332
+
333
+ if (schema.properties || schema.additionalProperties) {
334
+ // properties -> literal type
335
+ return this.#getTypeFromProperties(schema, baseName)
336
+ }
337
+
338
+ if (schema.type) {
339
+ if (Array.isArray(schema.type)) {
340
+ // OPENAPI v3.1.0: https://www.openapis.org/blog/2021/02/16/migrating-from-openapi-3-0-to-3-1-0
341
+ const [type, nullable] = schema.type as Array<OpenAPIV3.NonArraySchemaObjectType>
342
+
343
+ return factory.createUnionDeclaration({
344
+ nodes: [
345
+ this.#getBaseTypeFromSchema(
346
+ {
347
+ ...schema,
348
+ type,
349
+ },
350
+ baseName,
351
+ ),
352
+ nullable ? factory.createLiteralTypeNode(factory.createNull()) : undefined,
353
+ ].filter(Boolean),
354
+ })
355
+ }
356
+
357
+ if (this.options.dateType === 'date' && ['date', 'date-time'].some((item) => item === schema.format)) {
358
+ return factory.createTypeReferenceNode(factory.createIdentifier('Date'))
359
+ }
360
+
361
+ // string, boolean, null, number
362
+ if (schema.type in factory.keywordTypeNodes) {
363
+ return factory.keywordTypeNodes[schema.type as keyof typeof factory.keywordTypeNodes]
364
+ }
365
+ }
366
+
367
+ if (schema.format === 'binary') {
368
+ return factory.createTypeReferenceNode('Blob', [])
369
+ }
370
+
371
+ // detect assertion "const" and define the type property as a Literal
372
+ if ('const' in schema && schema['const'] !== undefined && typeof schema['const'] === 'string') {
373
+ return factory.createLiteralTypeNode(factory.createStringLiteral(schema['const']))
374
+ }
375
+
376
+ return factory.keywordTypeNodes.any
377
+ }
378
+ }
@@ -0,0 +1,2 @@
1
+ export * from './OperationGenerator.ts'
2
+ export * from './TypeGenerator.ts'
@@ -0,0 +1 @@
1
+ export * from './useResolve.ts'
@@ -0,0 +1,10 @@
1
+ import { useResolve as useResolveSwagger } from '@kubb/swagger/hooks'
2
+
3
+ import { pluginKey } from '../plugin.ts'
4
+
5
+ import type { Resolver } from '@kubb/swagger'
6
+ import type { UseResolveProps } from '@kubb/swagger/hooks'
7
+
8
+ export function useResolve(props: UseResolveProps = {}): Resolver {
9
+ return useResolveSwagger({ pluginKey, ...props })
10
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { definePlugin } from './plugin.ts'
2
+
3
+ export * from './plugin.ts'
4
+ export { resolve } from './resolve.ts'
5
+ export * from './types.ts'
6
+
7
+ export default definePlugin