Skip to content

Commit 52079b7

Browse files
authored
Merge pull request #16 from sparingsoftware/feat/fetchkeys-array-handling
fetchKeys array handling
2 parents 08e460b + c4252bf commit 52079b7

11 files changed

Lines changed: 12730 additions & 283 deletions

File tree

package-lock.json

Lines changed: 2319 additions & 83 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"scripts": {
88
"prepublish": "npm run build",
99
"build": "shx rm -rf dist && tsc",
10-
"prepare": "is-ci || husky install"
10+
"prepare": "is-ci || husky install",
11+
"test": "vitest"
1112
},
1213
"keywords": [
1314
"open-api",
@@ -36,18 +37,20 @@
3637
"ts-morph": "^19.0.0"
3738
},
3839
"devDependencies": {
39-
"@sparing-software/prettier-config": "^1.0.0",
4040
"@commitlint/cli": "^17.6.5",
4141
"@semantic-release/git": "^10.0.1",
4242
"@sparing-software/commitlint-config": "^1.0.0",
43+
"@sparing-software/prettier-config": "^1.0.0",
4344
"@types/node": "^18.16.0",
44-
"shx": "^0.3.4",
45-
"typescript": "^5.0.4",
45+
"axios": "^1.7.4",
4646
"husky": "^8.0.0",
4747
"is-ci": "^3.0.1",
4848
"lint-staged": "^13.2.0",
4949
"prettier": "^2.8.4",
50-
"semantic-release": "^21.0.5"
50+
"semantic-release": "^21.0.5",
51+
"shx": "^0.3.4",
52+
"typescript": "^5.0.4",
53+
"vitest": "^2.0.5"
5154
},
5255
"files": [
5356
"dist",

src/generateFromConfig.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import path from 'path'
2+
import { generateApi, ParsedRoute } from 'swagger-typescript-api'
3+
import prettierConfig from '@sparing-software/prettier-config'
4+
import fs from 'fs'
5+
import { default as optimizeTypesUtil } from './optimizeTypes'
6+
import onCreateRoute from './onCreateRoute'
7+
8+
export type Config = {
9+
/**
10+
* Http address of JSON OpenAPI schema to your API
11+
* @required
12+
*/
13+
url?: string
14+
/**
15+
* File path of JSON OpenAPI schema to your API
16+
*/
17+
filePath?: string
18+
/**
19+
* Output directory for generated http service
20+
*
21+
* In order to help webpack automatically map aliases for generated file in Vue/React projects please use the following config: './src/service'
22+
* @default './service'
23+
*/
24+
outDir?: string
25+
/**
26+
* Output filename (filename must be with .ts extension)
27+
* @default '__generated-api.ts'
28+
*/
29+
outFilename?: string
30+
/**
31+
* List of paths to be excluded from generated api
32+
* @example ['/users'] // all paths starting with /users
33+
* @example [''] // all paths
34+
*/
35+
exclude?: string[]
36+
/**
37+
* List of paths to be included in the generated api, takes priority over excluded paths
38+
*
39+
* Helpful when you need to exclude all but a few paths
40+
* @example ['/users'] // all paths starting with /users
41+
*/
42+
include?: string[]
43+
/**
44+
* Removes all type exports that have no references in endpoints (can slow down api generation)
45+
* @default true
46+
*/
47+
optimizeTypes?: boolean
48+
/**
49+
* List of types unaffected by optimizeTypes
50+
*
51+
* Helpful when you need an exported type that isn't referenced in any endpoint
52+
* @example ['WidgetResourcetypeEnum']
53+
*/
54+
typeWhitelist?: string[]
55+
}
56+
57+
export const generateFromConfig = ({
58+
url,
59+
filePath,
60+
outDir = './service/',
61+
outFilename = '__generated-api.ts',
62+
exclude = [],
63+
include = [],
64+
optimizeTypes = true,
65+
typeWhitelist = []
66+
}: Config) => {
67+
const OUTPUT_PATH = path.resolve(process.cwd(), outDir)
68+
const TEMPLATES_PATH = path.resolve(__dirname, '../templates/')
69+
70+
return generateApi({
71+
...(filePath ? { input: filePath } : { url }),
72+
name: outFilename,
73+
httpClientType: 'axios',
74+
templates: TEMPLATES_PATH,
75+
prettier: {
76+
parser: 'typescript',
77+
...prettierConfig
78+
},
79+
generateUnionEnums: true,
80+
unwrapResponseData: true,
81+
// @ts-ignore
82+
exclude,
83+
// @ts-ignore
84+
include,
85+
hooks: {
86+
onCreateRoute: onCreateRoute as unknown as (
87+
routeData: ParsedRoute
88+
) => ParsedRoute
89+
}
90+
}).then(async ({ files }) => {
91+
if (!fs.existsSync(OUTPUT_PATH))
92+
fs.mkdirSync(OUTPUT_PATH, { recursive: true })
93+
94+
const [firstFile] = files
95+
96+
if (firstFile) {
97+
const { name, content } = firstFile
98+
const fullPath = `${OUTPUT_PATH}/${name}`
99+
100+
fs.writeFileSync(fullPath, content)
101+
102+
if (optimizeTypes) {
103+
console.log('🤏 optimizing types')
104+
await optimizeTypesUtil(fullPath, typeWhitelist)
105+
}
106+
}
107+
})
108+
}

src/index.ts

Lines changed: 5 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -2,55 +2,7 @@
22
import chalk from 'chalk'
33
import fs from 'fs'
44
import path from 'path'
5-
import { generateApi } from 'swagger-typescript-api'
6-
import prettierConfig from '@sparing-software/prettier-config'
7-
8-
import { default as optimizeTypesUtil } from './optimizeTypes'
9-
10-
export type Config = {
11-
/**
12-
* Http address of JSON OpenAPI schema to your API
13-
* @required
14-
*/
15-
url?: string
16-
/**
17-
* Output directory for generated http service
18-
*
19-
* In order to help webpack automatically map aliases for generated file in Vue/React projects please use the following config: './src/service'
20-
* @default './service'
21-
*/
22-
outDir?: string
23-
/**
24-
* Output filename (filename must be with .ts extension)
25-
* @default '__generated-api.ts'
26-
*/
27-
outFilename?: string
28-
/**
29-
* List of paths to be excluded from generated api
30-
* @example ['/users'] // all paths starting with /users
31-
* @example [''] // all paths
32-
*/
33-
exclude?: string[]
34-
/**
35-
* List of paths to be included in the generated api, takes priority over excluded paths
36-
*
37-
* Helpful when you need to exclude all but a few paths
38-
* @example ['/users'] // all paths starting with /users
39-
*/
40-
include?: string[]
41-
/**
42-
* Removes all type exports that have no references in endpoints (can slow down api generation)
43-
* @default true
44-
*/
45-
optimizeTypes?: boolean
46-
/**
47-
* List of types unaffected by optimizeTypes
48-
*
49-
* Helpful when you need an exported type that isn't referenced in any endpoint
50-
* @example ['WidgetResourcetypeEnum']
51-
*/
52-
typeWhitelist?: string[]
53-
}
5+
import { type Config, generateFromConfig } from './generateFromConfig'
546

557
async function main() {
568
const CONFIG_PATH = path.resolve(process.cwd(), 'sparing-open-api.config.js')
@@ -64,17 +16,9 @@ async function main() {
6416
return
6517
}
6618

67-
const {
68-
url,
69-
outDir = './service/',
70-
outFilename = '__generated-api.ts',
71-
exclude = [],
72-
include = [],
73-
optimizeTypes = true,
74-
typeWhitelist = []
75-
} = (await import(CONFIG_PATH)).default as Config
19+
const config = (await import(CONFIG_PATH)).default as Config
7620

77-
if (!url) {
21+
if (!config.url) {
7822
console.log(
7923
chalk.yellow(
8024
'"url" property in sparing-open-api.config.js is not defined. Service creation aborted!'
@@ -83,83 +27,8 @@ async function main() {
8327
return
8428
}
8529

86-
const OUTPUT_PATH = path.resolve(process.cwd(), outDir)
87-
const TEMPLATES_PATH = path.resolve(__dirname, '../templates/')
88-
89-
generateApi({
90-
name: outFilename,
91-
url,
92-
httpClientType: 'axios',
93-
templates: TEMPLATES_PATH,
94-
prettier: {
95-
parser: 'typescript',
96-
...prettierConfig
97-
},
98-
generateUnionEnums: true,
99-
unwrapResponseData: true,
100-
// @ts-ignore
101-
exclude,
102-
// @ts-ignore
103-
include,
104-
hooks: {
105-
onCreateRoute: data => {
106-
// TODO Simplify types
107-
const routeData = data as typeof data & {
108-
responseBodySchema: { type: string }
109-
routeParams: { query: object[] }
110-
request: { query: { type: string; name: string; optional: boolean } }
111-
}
112-
113-
if (routeData.request.method !== 'get') return routeData
114-
115-
const type = `FetchKeys<${routeData.responseBodySchema.type}>`
116-
117-
routeData.routeParams.query.push({
118-
name: 'fetchKeys',
119-
required: false,
120-
in: 'query',
121-
description: 'Keys to fetch from endpoint',
122-
schema: { type },
123-
type
124-
})
125-
126-
if (routeData.request.query) {
127-
const requestQuery = routeData.request.query
128-
routeData.request.query = {
129-
...requestQuery,
130-
type: requestQuery.type.slice(0, -1) + 'fetchKeys?: T }'
131-
}
132-
} else {
133-
routeData.request.query = {
134-
name: 'query',
135-
optional: true,
136-
type: `{ fetchKeys?: T }`
137-
}
138-
}
139-
140-
return routeData
141-
}
142-
}
143-
}).then(async ({ files }) => {
144-
if (!fs.existsSync(OUTPUT_PATH))
145-
fs.mkdirSync(OUTPUT_PATH, { recursive: true })
146-
147-
const [firstFile] = files
148-
149-
if (firstFile) {
150-
const { name, content } = firstFile
151-
const fullPath = `${OUTPUT_PATH}/${name}`
152-
153-
fs.writeFileSync(fullPath, content)
154-
155-
if (optimizeTypes) {
156-
console.log('🤏 optimizing types')
157-
await optimizeTypesUtil(fullPath, typeWhitelist)
158-
}
159-
}
160-
161-
process.exit(0)
162-
})
30+
await generateFromConfig(config)
31+
process.exit(0)
16332
}
16433

16534
main()

src/onCreateRoute.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// TODO Simplify types
2+
export default function onCreateRoute(routeData: {
3+
responseBodySchema: { type: string }
4+
routeParams: {
5+
query: {
6+
name: string
7+
required: boolean
8+
in: string
9+
description: string
10+
schema: { type: string }
11+
type: string
12+
}[]
13+
}
14+
request: {
15+
query?: { type: string; name: string; optional: boolean }
16+
method: string
17+
}
18+
}) {
19+
if (routeData.request.method !== 'get') return routeData
20+
21+
const type = `FetchKeys<${routeData.responseBodySchema.type}>`
22+
23+
routeData.routeParams.query.push({
24+
name: 'fetchKeys',
25+
required: false,
26+
in: 'query',
27+
description: 'Keys to fetch from endpoint',
28+
schema: { type },
29+
type
30+
})
31+
32+
if (routeData.request.query) {
33+
const requestQuery = routeData.request.query
34+
routeData.request.query = {
35+
...requestQuery,
36+
type: requestQuery.type.slice(0, -1) + 'fetchKeys?: T }'
37+
}
38+
} else {
39+
routeData.request.query = {
40+
name: 'query',
41+
optional: true,
42+
type: `{ fetchKeys?: T }`
43+
}
44+
}
45+
46+
return routeData
47+
}

0 commit comments

Comments
 (0)