Skip to content

Commit 622488f

Browse files
committed
feat: add test coverage command and integrate @vitest/coverage-v8 for enhanced testing capabilities, update package dependencies and configurations
1 parent 48b845b commit 622488f

11 files changed

Lines changed: 196 additions & 206 deletions

File tree

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"lint": "eslint --cache .",
1111
"typecheck": "pnpm -r run typecheck",
1212
"test": "vitest",
13+
"test:coverage": "vitest run --coverage",
1314
"automd": "pnpm --filter '@genapi/*' exec automd",
1415
"release": "bumpp -r && pnpm -r publish --access public",
1516
"prepare": "simple-git-hooks && skills-manifest install"
@@ -19,6 +20,7 @@
1920
"@antfu/ni": "catalog:cli",
2021
"@antfu/utils": "catalog:inlined",
2122
"@types/node": "catalog:types",
23+
"@vitest/coverage-v8": "catalog:testing",
2224
"automd": "catalog:cli",
2325
"bumpp": "catalog:cli",
2426
"eslint": "catalog:cli",

packages/parser/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export * from './create-parser'
1+
export * from './parser'
22
export * from './parses'
33
export * from './transform'
44
export * from './traverse'
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,7 @@ export function createParser(pathHandler: PathHandler) {
4545
if (source.definitions)
4646
transformDefinitions(source.definitions)
4747

48-
traversePaths(source.paths ?? {}, (config) => {
49-
pathHandler(config, inject() as ParserContext)
50-
})
48+
traversePaths(source.paths ?? {}, config => pathHandler(config, inject() as ParserContext))
5149

5250
configRead.graphs.comments = comments
5351
configRead.graphs.functions = functions
Lines changed: 23 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ApiPipeline, StatementInterface } from '@genapi/shared'
1+
import type { ApiPipeline, StatementField, StatementInterface } from '@genapi/shared'
22
import type { Definitions, Schema } from 'openapi-specification-types'
33
import { inject } from '@genapi/shared'
44
import { parseSchemaType } from '../parses'
@@ -10,43 +10,23 @@ export interface DefinitionTransformOptions {
1010

1111
export function transformDefinitions(definitions: Definitions) {
1212
const { interfaces, configRead } = inject()
13-
const config = (configRead?.config || {}) as ApiPipeline.Config
13+
const config = configRead?.config || {} as ApiPipeline.Config
1414
const transformDef = config.transform?.definition
1515
const patchDefinitions = config.patch?.definitions || {}
1616

17-
// Map from original interface name to renamed interface name
18-
// Used by parseSchemaType to resolve $ref to renamed interfaces
19-
const nameMapping: Record<string, string> = {}
17+
for (let [name, definition] of Object.entries(definitions)) {
18+
const defProperties = definition.properties || {}
19+
let properties = Object.keys(defProperties)
20+
.map(name => defToFields(name, defProperties[name])) as StatementField[]
21+
name = varName(name)
2022

21-
for (const [name, definition] of Object.entries(definitions)) {
22-
const { properties = {} } = definition
23-
24-
const interfaceName = varName(name)
25-
26-
// Build a structural type string for this definition so `transform.definition`
27-
// and `patch.definitions` can operate on it.
28-
const baseType = buildDefinitionType(interfaceName, properties)
29-
const patchResult = applyDefinitionTransformsAndPatches({
30-
baseName: interfaceName,
31-
baseType,
32-
configRead,
33-
transformDef,
34-
patchDefinitions,
35-
})
36-
37-
// If patch only renames (no type override), use the new name for the interface
38-
// Otherwise, keep original name and create type alias
39-
const finalInterfaceName = patchResult.shouldRename ? patchResult.aliasName : interfaceName
40-
41-
// Store mapping for parseSchemaType to use
42-
if (finalInterfaceName !== interfaceName) {
43-
nameMapping[interfaceName] = finalInterfaceName
44-
}
23+
;({ name, properties } = applyPatch(transformDef?.(name, properties)))
24+
;({ name, properties } = applyPatch(patchDefinitions[name]))
4525

4626
interfaces.push({
27+
name,
4728
export: true,
48-
name: finalInterfaceName,
49-
properties: Object.keys(properties).map(name => defToFields(name, properties[name])),
29+
properties,
5030
})
5131

5232
function defToFields(name: string, propertie: Schema) {
@@ -59,109 +39,20 @@ export function transformDefinitions(definitions: Definitions) {
5939
required: required ?? (typeof propertie.required === 'boolean' ? propertie.required : undefined),
6040
}
6141
}
62-
}
63-
64-
// Store name mapping in config for parseSchemaType to use
65-
if (configRead?.config) {
66-
(configRead.config as any).__definitionNameMapping = nameMapping
67-
}
68-
}
69-
70-
function buildDefinitionType(interfaceName: string, properties: Definitions[string]['properties'] = {}) {
71-
const entries = Object.entries(properties)
72-
if (entries.length === 0)
73-
return 'any'
74-
75-
const fields = entries.map(([propName, schema]) => {
76-
const type = parseSchemaType(schema as Schema)
77-
return `${varFiled(propName)}: ${type}`
78-
})
79-
80-
return `{ ${fields.join(', ')} }`
81-
}
82-
83-
interface DefinitionPatchContext {
84-
baseName: string
85-
baseType: string
86-
configRead: ApiPipeline.ConfigRead
87-
transformDef?: (name: string, type: string) => ApiPipeline.Definition
88-
patchDefinitions: Record<string, ApiPipeline.Definition>
89-
}
9042

91-
/**
92-
* Applies `config.transform.definition` (global) and `config.patch.definitions` (static)
93-
* to a single Swagger/OpenAPI definition.
94-
*
95-
* Semantics:
96-
* - Rename only (string or name only): `'Order': 'OrderDTO'` → interface renamed to `OrderDTO`
97-
* - Rename + override type:
98-
* `'SessionDto': { name: 'Session', type: '{ name: string }' }`
99-
* → interface stays `SessionDto`, creates `export type Session = { name: string }`
100-
*
101-
* When only renaming, the interface itself is renamed so all references automatically
102-
* use the new name. When type is overridden, the original interface is preserved
103-
* and a type alias is created.
104-
*/
105-
function applyDefinitionTransformsAndPatches(ctx: DefinitionPatchContext) {
106-
const {
107-
baseName,
108-
baseType,
109-
configRead,
110-
transformDef,
111-
patchDefinitions,
112-
} = ctx
113-
114-
let aliasName = baseName
115-
let aliasType = baseType
116-
let hasTypeOverride = false
117-
118-
function applyPatch(patch?: ApiPipeline.Definition) {
119-
if (!patch)
120-
return
121-
122-
if (typeof patch === 'string') {
123-
aliasName = patch
124-
return
125-
}
43+
function applyPatch(patch?: ApiPipeline.Definition) {
44+
if (!patch)
45+
return { name, properties }
46+
if (typeof patch === 'string') {
47+
name = patch
48+
return { name, properties }
49+
}
50+
if (patch.name)
51+
name = patch.name
52+
if (patch.type)
53+
properties = patch.type
12654

127-
if (patch.name)
128-
aliasName = patch.name
129-
if (patch.type) {
130-
aliasType = patch.type
131-
hasTypeOverride = true
55+
return { name, properties }
13256
}
13357
}
134-
135-
// Global transform first.
136-
if (transformDef) {
137-
const patch = transformDef(baseName, baseType)
138-
applyPatch(patch)
139-
}
140-
141-
// Then static patch; allow matching by original or transformed name.
142-
const staticPatch = patchDefinitions[baseName] ?? patchDefinitions[aliasName]
143-
applyPatch(staticPatch)
144-
145-
const hasNameChange = aliasName !== baseName
146-
const hasTypeChange = aliasType !== baseType
147-
148-
if (!hasNameChange && !hasTypeChange)
149-
return { aliasName: baseName, shouldRename: false }
150-
151-
// If only renaming (no type override), rename the interface itself
152-
// Otherwise, create a type alias
153-
const shouldRename = hasNameChange && !hasTypeOverride
154-
155-
if (!shouldRename) {
156-
// Create type alias when type is overridden
157-
const aliasValue = hasTypeChange ? aliasType : baseName
158-
configRead.graphs.typings = configRead.graphs.typings || []
159-
configRead.graphs.typings.push({
160-
export: true,
161-
name: aliasName,
162-
value: aliasValue,
163-
})
164-
}
165-
166-
return { aliasName, shouldRename }
16758
}

packages/parser/test/transform/definitions.test.ts

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -126,18 +126,16 @@ describe('transformDefinitions', () => {
126126
// Interface renamed to User
127127
expect(interfaces.find((i: any) => i.name === 'User')).toBeDefined()
128128
expect(interfaces.find((i: any) => i.name === 'UserDto')).toBeUndefined()
129-
// No type alias created when only renaming
130-
expect(configRead.graphs.typings).toHaveLength(0)
131129
})
132130

133-
it('creates type alias with custom type when patch provides type override', () => {
131+
it('applies name and type when patch provides both', () => {
134132
const interfaces: any[] = []
135133
provide({ interfaces, configRead })
136134
configRead.config.patch = {
137135
definitions: {
138136
SessionDto: {
139137
name: 'Session',
140-
type: '{ name: string, age: number }',
138+
type: [{ name: 'name', type: 'string' }, { name: 'age', type: 'number' }],
141139
},
142140
},
143141
}
@@ -153,11 +151,12 @@ describe('transformDefinitions', () => {
153151
}
154152
transformDefinitions(definitions as any)
155153

156-
// Original interface preserved when type is overridden
157-
expect(interfaces.find((i: any) => i.name === 'SessionDto')).toBeDefined()
158-
expect(configRead.graphs.typings).toHaveLength(1)
159-
expect(configRead.graphs.typings![0].name).toBe('Session')
160-
expect(configRead.graphs.typings![0].value).toBe('{ name: string, age: number }')
154+
// Interface renamed to Session with patch type as properties
155+
const session = interfaces.find((i: any) => i.name === 'Session')
156+
expect(session).toBeDefined()
157+
expect(session!.properties).toHaveLength(2)
158+
expect(session!.properties.map((p: any) => p.name)).toEqual(['name', 'age'])
159+
expect(interfaces.find((i: any) => i.name === 'SessionDto')).toBeUndefined()
161160
})
162161

163162
it('handles multiple definition patches', () => {
@@ -182,10 +181,9 @@ describe('transformDefinitions', () => {
182181
}
183182
transformDefinitions(definitions as any)
184183

185-
// Interfaces renamed, no type aliases created
184+
// Interfaces renamed
186185
expect(interfaces.find((i: any) => i.name === 'User')).toBeDefined()
187186
expect(interfaces.find((i: any) => i.name === 'Order')).toBeDefined()
188-
expect(configRead.graphs.typings).toHaveLength(0)
189187
})
190188

191189
it('renames interface when patch matches transformed name', () => {
@@ -215,7 +213,6 @@ describe('transformDefinitions', () => {
215213

216214
// Interface renamed to UserAlias (transform: UserDto -> User, patch: User -> UserAlias)
217215
expect(interfaces.find((i: any) => i.name === 'UserAlias')).toBeDefined()
218-
expect(configRead.graphs.typings).toHaveLength(0)
219216
})
220217

221218
it('renames interface when patch only renames', () => {
@@ -275,11 +272,9 @@ describe('transformDefinitions', () => {
275272

276273
// Transform: UserDto -> User
277274
// Patch: User -> UserEntity
278-
// Interface should be renamed to UserEntity (no typings created)
279275
expect(interfaces.find((i: any) => i.name === 'UserEntity')).toBeDefined()
280276
expect(interfaces.find((i: any) => i.name === 'User')).toBeUndefined()
281277
expect(interfaces.find((i: any) => i.name === 'UserDto')).toBeUndefined()
282-
expect(configRead.graphs.typings).toHaveLength(0)
283278
})
284279

285280
it('renames interface when transform returns string', () => {
@@ -301,18 +296,17 @@ describe('transformDefinitions', () => {
301296
// Interface renamed to RenamedType
302297
expect(interfaces.find((i: any) => i.name === 'RenamedType')).toBeDefined()
303298
expect(interfaces.find((i: any) => i.name === 'OriginalType')).toBeUndefined()
304-
expect(configRead.graphs.typings).toHaveLength(0)
305299
})
306300

307-
it('creates type alias with custom type when transform returns object with type', () => {
301+
it('applies name and type when transform returns object with type', () => {
308302
const interfaces: any[] = []
309303
provide({ interfaces, configRead })
310304
configRead.config.transform = {
311305
operation: () => '',
312-
definition: (name, type) => {
306+
definition: (name) => {
313307
return {
314308
name: `${name}Alias`,
315-
type: `Custom${type}`,
309+
type: [{ name: 'id', type: 'integer' }],
316310
}
317311
},
318312
}
@@ -325,19 +319,19 @@ describe('transformDefinitions', () => {
325319
}
326320
transformDefinitions(definitions as any)
327321

328-
// Original interface preserved when type is overridden
329-
expect(interfaces.find((i: any) => i.name === 'Test')).toBeDefined()
330-
expect(configRead.graphs.typings).toHaveLength(1)
331-
expect(configRead.graphs.typings![0].name).toBe('TestAlias')
332-
expect(configRead.graphs.typings![0].value).toContain('Custom')
322+
// Interface renamed to TestAlias with transform type as properties
323+
const testAlias = interfaces.find((i: any) => i.name === 'TestAlias')
324+
expect(testAlias).toBeDefined()
325+
expect(testAlias!.properties).toHaveLength(1)
326+
expect(testAlias!.properties[0]).toEqual({ name: 'id', type: 'integer', required: undefined, description: undefined })
333327
})
334328

335-
it('does not create alias when transform returns same name and type', () => {
329+
it('keeps interface unchanged when transform returns same name and type', () => {
336330
const interfaces: any[] = []
337331
provide({ interfaces, configRead })
338332
configRead.config.transform = {
339333
operation: () => '',
340-
definition: (name, type) => ({ name, type }), // No-op transform
334+
definition: (name, properties) => ({ name, type: properties }), // No-op transform
341335
}
342336

343337
const definitions = {
@@ -348,8 +342,8 @@ describe('transformDefinitions', () => {
348342
}
349343
transformDefinitions(definitions as any)
350344

351-
// No typings should be created since nothing changed
352-
expect(configRead.graphs.typings).toHaveLength(0)
345+
expect(interfaces.find((i: any) => i.name === 'Unchanged')).toBeDefined()
346+
expect(interfaces.find((i: any) => i.name === 'Unchanged')!.properties).toHaveLength(1)
353347
})
354348
})
355349
})

packages/presets/src/ofetch/js/config/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export function config(userConfig: ApiPipeline.Config): ApiPipeline.ConfigRead {
1111
const configRead = _config(userConfig)
1212

1313
configRead.graphs.imports.push({
14-
name: 'ofetch',
1514
value: userConfig.import.http,
15+
names: ['ofetch'],
1616
})
1717

1818
return configRead

packages/presets/src/ofetch/ts/config/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ export function config(userConfig: ApiPipeline.Config): ApiPipeline.ConfigRead {
88
const configRead = _config(userConfig)
99

1010
configRead.graphs.imports.push({
11-
name: 'ofetch',
12-
names: userConfig.import.http === 'ofetch' ? ['FetchOptions'] : undefined,
11+
names: userConfig.import.http === 'ofetch' ? ['FetchOptions', 'ofetch'] : ['ofetch'],
1312
value: userConfig.import.http,
1413
})
1514

packages/shared/src/types/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export namespace ApiPipeline {
104104
}
105105
export type Definition = string | {
106106
name?: string
107-
type?: string
107+
type?: StatementField[]
108108
}
109109

110110
export interface Patch {
@@ -149,7 +149,7 @@ export namespace ApiPipeline {
149149
* @description
150150
* Transform the definition type
151151
*/
152-
definition?: (name: string, type: string) => Definition
152+
definition?: (name: string, type: StatementField[]) => Definition
153153
}
154154

155155
export interface Config extends PreInputs, PreOutput, Meta {

playground/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010
"@genapi/pipeline": "workspace:*",
1111
"@genapi/presets": "workspace:*",
1212
"@genapi/transform": "workspace:*",
13-
"ofetch": "catalog:",
1413
"wpapi-to-swagger": "catalog:playground"
1514
},
1615
"devDependencies": {
1716
"@uni-helper/uni-network": "catalog:playground",
18-
"fetchdts": "catalog:"
17+
"fetchdts": "catalog:inlined",
18+
"ofetch": "catalog:inlined"
1919
}
2020
}

0 commit comments

Comments
 (0)