@pikku/cli 0.7.0 → 0.7.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.
- package/CHANGELOG.md +18 -0
- package/bin/pikku-all.ts +203 -0
- package/bin/pikku-channels-map.ts +55 -0
- package/bin/pikku-channels.ts +63 -0
- package/bin/pikku-fetch.ts +55 -0
- package/bin/pikku-function-types.ts +84 -0
- package/bin/pikku-functions.ts +35 -0
- package/bin/pikku-http-map.ts +56 -0
- package/bin/pikku-http-routes.ts +63 -0
- package/bin/pikku-nextjs.test.ts +279 -0
- package/bin/pikku-nextjs.ts +152 -0
- package/bin/pikku-openapi.ts +76 -0
- package/bin/pikku-rpc.ts +22 -0
- package/bin/pikku-scheduler.ts +64 -0
- package/bin/pikku-schemas.ts +57 -0
- package/bin/pikku-websocket.ts +58 -0
- package/bin/pikku.ts +26 -0
- package/dist/bin/pikku-all.js +3 -0
- package/dist/bin/pikku-functions.d.ts +0 -2
- package/dist/bin/pikku-functions.js +2 -17
- package/dist/bin/pikku-http-map.js +1 -1
- package/dist/bin/pikku-openapi.js +2 -2
- package/dist/bin/pikku-rpc.d.ts +3 -0
- package/dist/bin/pikku-rpc.js +8 -0
- package/dist/bin/pikku-schemas.js +2 -2
- package/dist/bin/pikku.js +0 -0
- package/dist/src/openapi-spec-generator.d.ts +2 -2
- package/dist/src/openapi-spec-generator.js +14 -5
- package/dist/src/pikku-cli-config.d.ts +1 -0
- package/dist/src/pikku-cli-config.js +3 -0
- package/dist/src/schema-generator.d.ts +3 -3
- package/dist/src/schema-generator.js +15 -34
- package/dist/src/serialize-typed-function-map.d.ts +2 -1
- package/dist/src/serialize-typed-function-map.js +6 -4
- package/dist/src/serialize-typed-http-map.d.ts +2 -1
- package/dist/src/serialize-typed-http-map.js +10 -4
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +3 -3
- package/src/inspector-glob.ts +28 -0
- package/src/openapi-spec-generator.ts +246 -0
- package/src/pikku-cli-config.ts +240 -0
- package/src/schema-generator.ts +118 -0
- package/{dist/src/events/http/serialize-fetch-wrapper.js → src/serialize-fetch-wrapper.ts} +4 -4
- package/src/serialize-import-map.ts +34 -0
- package/{dist/src/nextjs/serialize-nextjs-backend-wrapper.js → src/serialize-nextjs-backend-wrapper.ts} +11 -4
- package/{dist/src/nextjs/serialize-nextjs-http-wrapper.js → src/serialize-nextjs-http-wrapper.ts} +7 -4
- package/{dist/src/core/serialize-pikku-types.js → src/serialize-pikku-types.ts} +46 -35
- package/src/serialize-scheduler-meta.ts +18 -0
- package/src/serialize-typed-channel-map.ts +138 -0
- package/src/serialize-typed-function-map.ts +161 -0
- package/src/serialize-typed-http-map.ts +167 -0
- package/{dist/src/channels/serialize-websocket-wrapper.js → src/serialize-websocket-wrapper.ts} +4 -4
- package/src/utils.ts +284 -0
- package/tsconfig.json +21 -0
- package/dist/bin/pikku-http.d.ts +0 -5
- package/dist/bin/pikku-http.js +0 -27
- package/dist/bin/pikku-routes-map.d.ts +0 -5
- package/dist/bin/pikku-routes-map.js +0 -23
- package/dist/src/channels/serialize-channels.d.ts +0 -3
- package/dist/src/channels/serialize-channels.js +0 -19
- package/dist/src/channels/serialize-typed-channel-map.d.ts +0 -3
- package/dist/src/channels/serialize-typed-channel-map.js +0 -93
- package/dist/src/channels/serialize-websocket-wrapper.d.ts +0 -1
- package/dist/src/core/serialize-import-map.d.ts +0 -2
- package/dist/src/core/serialize-import-map.js +0 -24
- package/dist/src/core/serialize-pikku-types.d.ts +0 -4
- package/dist/src/events/channels/serialize-channels.d.ts +0 -3
- package/dist/src/events/channels/serialize-channels.js +0 -19
- package/dist/src/events/channels/serialize-typed-channel-map.d.ts +0 -3
- package/dist/src/events/channels/serialize-typed-channel-map.js +0 -90
- package/dist/src/events/channels/serialize-websocket-wrapper.d.ts +0 -1
- package/dist/src/events/channels/serialize-websocket-wrapper.js +0 -61
- package/dist/src/events/http/serialize-fetch-wrapper.d.ts +0 -1
- package/dist/src/events/http/serialize-route-imports.d.ts +0 -1
- package/dist/src/events/http/serialize-route-imports.js +0 -13
- package/dist/src/events/http/serialize-route-meta.d.ts +0 -2
- package/dist/src/events/http/serialize-route-meta.js +0 -6
- package/dist/src/events/http/serialize-typed-route-map.d.ts +0 -4
- package/dist/src/events/http/serialize-typed-route-map.js +0 -107
- package/dist/src/events/scheduler/serialize-schedulers.d.ts +0 -3
- package/dist/src/events/scheduler/serialize-schedulers.js +0 -23
- package/dist/src/http/serialize-fetch-wrapper.d.ts +0 -1
- package/dist/src/http/serialize-fetch-wrapper.js +0 -67
- package/dist/src/http/serialize-route-imports.d.ts +0 -1
- package/dist/src/http/serialize-route-imports.js +0 -13
- package/dist/src/http/serialize-route-meta.d.ts +0 -2
- package/dist/src/http/serialize-route-meta.js +0 -6
- package/dist/src/http/serialize-typed-route-map.d.ts +0 -4
- package/dist/src/http/serialize-typed-route-map.js +0 -107
- package/dist/src/nextjs/serialize-nextjs-backend-wrapper.d.ts +0 -1
- package/dist/src/nextjs/serialize-nextjs-http-wrapper.d.ts +0 -1
- package/dist/src/openapi/openapi-spec-generator.d.ts +0 -79
- package/dist/src/openapi/openapi-spec-generator.js +0 -136
- package/dist/src/scheduler/serialize-schedulers.d.ts +0 -3
- package/dist/src/scheduler/serialize-schedulers.js +0 -23
- package/dist/src/schema/schema-generator.d.ts +0 -5
- package/dist/src/schema/schema-generator.js +0 -89
- package/dist/src/serialize-typed-route-map.d.ts +0 -4
- package/dist/src/serialize-typed-route-map.js +0 -107
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { FunctionsMeta, HTTPRoutesMeta, pikkuState } from '@pikku/core'
|
|
2
|
+
import _convertSchema from '@openapi-contrib/json-schema-to-openapi-schema'
|
|
3
|
+
const convertSchema =
|
|
4
|
+
'default' in _convertSchema ? (_convertSchema.default as any) : _convertSchema
|
|
5
|
+
|
|
6
|
+
interface OpenAPISpec {
|
|
7
|
+
openapi: string
|
|
8
|
+
info: {
|
|
9
|
+
title: string
|
|
10
|
+
version: string
|
|
11
|
+
description?: string
|
|
12
|
+
termsOfService?: string
|
|
13
|
+
contact?: {
|
|
14
|
+
name?: string
|
|
15
|
+
url?: string
|
|
16
|
+
email?: string
|
|
17
|
+
}
|
|
18
|
+
license?: {
|
|
19
|
+
name: string
|
|
20
|
+
url?: string
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
servers: { url: string; description?: string }[]
|
|
24
|
+
paths: Record<string, any>
|
|
25
|
+
components: {
|
|
26
|
+
schemas: Record<string, any>
|
|
27
|
+
responses?: Record<string, any>
|
|
28
|
+
parameters?: Record<string, any>
|
|
29
|
+
examples?: Record<string, any>
|
|
30
|
+
requestBodies?: Record<string, any>
|
|
31
|
+
headers?: Record<string, any>
|
|
32
|
+
securitySchemes?: Record<string, any>
|
|
33
|
+
}
|
|
34
|
+
security?: { [key: string]: any[] }[]
|
|
35
|
+
tags?: { name: string; description?: string }[]
|
|
36
|
+
externalDocs?: {
|
|
37
|
+
description?: string
|
|
38
|
+
url: string
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface OpenAPISpecInfo {
|
|
43
|
+
info: {
|
|
44
|
+
title: string
|
|
45
|
+
version: string
|
|
46
|
+
description: string
|
|
47
|
+
termsOfService?: string
|
|
48
|
+
contact?: {
|
|
49
|
+
name?: string
|
|
50
|
+
url?: string
|
|
51
|
+
email?: string
|
|
52
|
+
}
|
|
53
|
+
license?: {
|
|
54
|
+
name: string
|
|
55
|
+
url?: string
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
servers: { url: string; description?: string }[]
|
|
59
|
+
tags?: { name: string; description?: string }[]
|
|
60
|
+
externalDocs?: {
|
|
61
|
+
description?: string
|
|
62
|
+
url: string
|
|
63
|
+
}
|
|
64
|
+
securitySchemes?: Record<string, any>
|
|
65
|
+
security?: { [key: string]: any[] }[]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const getErrorResponseForConstructorName = (constructorName: string) => {
|
|
69
|
+
const errors = Array.from(pikkuState('misc', 'errors').entries())
|
|
70
|
+
const foundError = errors.find(([e]) => e.name === constructorName)
|
|
71
|
+
if (foundError) {
|
|
72
|
+
return foundError[1]
|
|
73
|
+
}
|
|
74
|
+
return undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const convertSchemasToBodyPayloads = async (
|
|
78
|
+
functionsMeta: FunctionsMeta,
|
|
79
|
+
routesMeta: HTTPRoutesMeta,
|
|
80
|
+
schemas: Record<string, any>
|
|
81
|
+
) => {
|
|
82
|
+
const requiredSchemas = new Set(
|
|
83
|
+
routesMeta
|
|
84
|
+
.map(({ inputTypes, pikkuFuncName }) => {
|
|
85
|
+
const output = functionsMeta[pikkuFuncName]?.outputs?.[0]
|
|
86
|
+
return [inputTypes?.body, output]
|
|
87
|
+
})
|
|
88
|
+
.flat()
|
|
89
|
+
.filter((schema) => !!schema)
|
|
90
|
+
)
|
|
91
|
+
const convertedEntries = await Promise.all(
|
|
92
|
+
Object.entries(schemas).map(async ([key, schema]) => {
|
|
93
|
+
if (requiredSchemas.has(key)) {
|
|
94
|
+
const convertedSchema = await convertSchema(schema, {
|
|
95
|
+
convertUnreferencedDefinitions: false,
|
|
96
|
+
dereference: { circular: 'ignore' },
|
|
97
|
+
})
|
|
98
|
+
return [key, convertedSchema]
|
|
99
|
+
}
|
|
100
|
+
return
|
|
101
|
+
})
|
|
102
|
+
)
|
|
103
|
+
return Object.fromEntries(convertedEntries.filter((s) => !!s))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function generateOpenAPISpec(
|
|
107
|
+
functionsMeta: FunctionsMeta,
|
|
108
|
+
routeMeta: HTTPRoutesMeta,
|
|
109
|
+
schemas: Record<string, any>,
|
|
110
|
+
additionalInfo: OpenAPISpecInfo
|
|
111
|
+
): Promise<OpenAPISpec> {
|
|
112
|
+
const paths: Record<string, any> = {}
|
|
113
|
+
|
|
114
|
+
routeMeta.forEach((meta) => {
|
|
115
|
+
const { route, method, inputTypes, pikkuFuncName, params, query, docs } =
|
|
116
|
+
meta
|
|
117
|
+
const functionMeta = functionsMeta[pikkuFuncName]
|
|
118
|
+
if (!functionMeta) {
|
|
119
|
+
console.error(
|
|
120
|
+
`• No function metadata found for '${pikkuFuncName}' in route '${route}'.`
|
|
121
|
+
)
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
const output = functionMeta.outputs ? functionMeta.outputs[0] : undefined
|
|
125
|
+
|
|
126
|
+
const path = route.replace(/:(\w+)/g, '{$1}') // Convert ":param" to "{param}"
|
|
127
|
+
|
|
128
|
+
if (!paths[path]) {
|
|
129
|
+
paths[path] = {}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const responses = {}
|
|
133
|
+
docs?.errors?.forEach((error) => {
|
|
134
|
+
const errorResponse = getErrorResponseForConstructorName(error)
|
|
135
|
+
if (errorResponse) {
|
|
136
|
+
responses[errorResponse.status] = {
|
|
137
|
+
description: errorResponse.message,
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const operation: any = {
|
|
143
|
+
description:
|
|
144
|
+
docs?.description ||
|
|
145
|
+
`This endpoint handles the ${method.toUpperCase()} request for the route ${route}.`,
|
|
146
|
+
tags: docs?.tags || [route.split('/')[1] || 'default'],
|
|
147
|
+
parameters: [],
|
|
148
|
+
responses: {
|
|
149
|
+
...responses,
|
|
150
|
+
'200': {
|
|
151
|
+
description: 'Successful response',
|
|
152
|
+
content: output
|
|
153
|
+
? {
|
|
154
|
+
'application/json': {
|
|
155
|
+
schema:
|
|
156
|
+
typeof output === 'string' &&
|
|
157
|
+
['boolean', 'string', 'number'].includes(output)
|
|
158
|
+
? { type: output }
|
|
159
|
+
: { $ref: `#/components/schemas/${output}` },
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
: undefined,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const bodyType = inputTypes?.body
|
|
168
|
+
if (bodyType) {
|
|
169
|
+
operation.requestBody = {
|
|
170
|
+
required: true,
|
|
171
|
+
content: {
|
|
172
|
+
'application/json': {
|
|
173
|
+
schema:
|
|
174
|
+
typeof bodyType === 'string' &&
|
|
175
|
+
['boolean', 'string', 'number'].includes(bodyType)
|
|
176
|
+
? { type: bodyType }
|
|
177
|
+
: { $ref: `#/components/schemas/${bodyType}` },
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (params) {
|
|
184
|
+
operation.parameters = params.map((param) => ({
|
|
185
|
+
name: param,
|
|
186
|
+
in: 'path',
|
|
187
|
+
required: true,
|
|
188
|
+
schema: { type: 'string' },
|
|
189
|
+
}))
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (query) {
|
|
193
|
+
operation.parameters.push(
|
|
194
|
+
...query.map((query) => ({
|
|
195
|
+
name: query,
|
|
196
|
+
in: 'query',
|
|
197
|
+
required: false,
|
|
198
|
+
schema: { type: 'string' },
|
|
199
|
+
}))
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
paths[path][method] = operation
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
openapi: '3.1.0',
|
|
208
|
+
info: additionalInfo.info,
|
|
209
|
+
servers: additionalInfo.servers,
|
|
210
|
+
paths,
|
|
211
|
+
components: {
|
|
212
|
+
schemas: await convertSchemasToBodyPayloads(
|
|
213
|
+
functionsMeta,
|
|
214
|
+
routeMeta,
|
|
215
|
+
schemas
|
|
216
|
+
),
|
|
217
|
+
responses: {},
|
|
218
|
+
parameters: {},
|
|
219
|
+
examples: {},
|
|
220
|
+
requestBodies: {},
|
|
221
|
+
headers: {},
|
|
222
|
+
securitySchemes: additionalInfo.securitySchemes || {
|
|
223
|
+
ApiKeyAuth: {
|
|
224
|
+
type: 'apiKey',
|
|
225
|
+
in: 'header',
|
|
226
|
+
name: 'x-api-key',
|
|
227
|
+
},
|
|
228
|
+
BearerAuth: {
|
|
229
|
+
type: 'http',
|
|
230
|
+
scheme: 'bearer',
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
security: additionalInfo.security || [
|
|
235
|
+
{
|
|
236
|
+
ApiKeyAuth: [],
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
BearerAuth: [],
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
tags: additionalInfo.tags,
|
|
243
|
+
externalDocs: additionalInfo.externalDocs,
|
|
244
|
+
// definitions
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { join, dirname, resolve, isAbsolute } from 'path'
|
|
2
|
+
import { readdir, readFile } from 'fs/promises'
|
|
3
|
+
import { OpenAPISpecInfo } from './openapi-spec-generator.js'
|
|
4
|
+
import { InspectorFilters } from '@pikku/inspector'
|
|
5
|
+
|
|
6
|
+
export interface PikkuCLICoreOutputFiles {
|
|
7
|
+
outDir?: string
|
|
8
|
+
functionsFile: string
|
|
9
|
+
functionsMetaFile: string
|
|
10
|
+
httpRoutesFile: string
|
|
11
|
+
httpRoutesMetaFile: string
|
|
12
|
+
channelsFile: string
|
|
13
|
+
channelsMetaFile: string
|
|
14
|
+
schedulersFile: string
|
|
15
|
+
schedulersMetaFile: string
|
|
16
|
+
rpcFile: string
|
|
17
|
+
schemaDirectory: string
|
|
18
|
+
typesDeclarationFile: string
|
|
19
|
+
httpRoutesMapDeclarationFile: string
|
|
20
|
+
channelsMapDeclarationFile: string
|
|
21
|
+
bootstrapFile: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type PikkuCLIConfig = {
|
|
25
|
+
$schema?: string
|
|
26
|
+
|
|
27
|
+
extends?: string
|
|
28
|
+
|
|
29
|
+
rootDir: string
|
|
30
|
+
srcDirectories: string[]
|
|
31
|
+
packageMappings: Record<string, string>
|
|
32
|
+
supportsImportAttributes: boolean
|
|
33
|
+
|
|
34
|
+
configDir: string
|
|
35
|
+
tsconfig: string
|
|
36
|
+
|
|
37
|
+
nextBackendFile?: string
|
|
38
|
+
nextHTTPFile?: string
|
|
39
|
+
fetchFile?: string
|
|
40
|
+
websocketFile?: string
|
|
41
|
+
|
|
42
|
+
openAPI?: {
|
|
43
|
+
outputFile: string
|
|
44
|
+
additionalInfo: OpenAPISpecInfo
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
filters: InspectorFilters
|
|
48
|
+
} & PikkuCLICoreOutputFiles
|
|
49
|
+
|
|
50
|
+
const CONFIG_DIR_FILES = [
|
|
51
|
+
'nextBackendFile',
|
|
52
|
+
'nextHTTPFile',
|
|
53
|
+
'fetchFile',
|
|
54
|
+
'websocketFile',
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
export const getPikkuCLIConfig = async (
|
|
58
|
+
configFile: string | undefined = undefined,
|
|
59
|
+
requiredFields: Array<keyof PikkuCLIConfig>,
|
|
60
|
+
tags: string[] = [],
|
|
61
|
+
exitProcess: boolean = false
|
|
62
|
+
): Promise<PikkuCLIConfig> => {
|
|
63
|
+
const config = await _getPikkuCLIConfig(
|
|
64
|
+
configFile,
|
|
65
|
+
requiredFields,
|
|
66
|
+
tags,
|
|
67
|
+
exitProcess
|
|
68
|
+
)
|
|
69
|
+
return config
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const _getPikkuCLIConfig = async (
|
|
73
|
+
configFile: string | undefined = undefined,
|
|
74
|
+
requiredFields: Array<keyof PikkuCLIConfig>,
|
|
75
|
+
tags: string[] = [],
|
|
76
|
+
exitProcess: boolean = false
|
|
77
|
+
): Promise<PikkuCLIConfig> => {
|
|
78
|
+
if (!configFile) {
|
|
79
|
+
let execDirectory = process.cwd()
|
|
80
|
+
const files = await readdir(execDirectory)
|
|
81
|
+
const file = files.find((file) => /pikku\.config\.(ts|js|json)$/.test(file))
|
|
82
|
+
if (!file) {
|
|
83
|
+
const errorMessage =
|
|
84
|
+
'\nConfig file pikku.config.json not found\nExiting...'
|
|
85
|
+
if (exitProcess) {
|
|
86
|
+
console.error(errorMessage)
|
|
87
|
+
process.exit(1)
|
|
88
|
+
}
|
|
89
|
+
throw new Error(errorMessage)
|
|
90
|
+
}
|
|
91
|
+
configFile = join(execDirectory, file)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
let result: PikkuCLIConfig
|
|
96
|
+
const file = await readFile(configFile, 'utf-8')
|
|
97
|
+
const configDir = dirname(configFile)
|
|
98
|
+
const config: PikkuCLIConfig = JSON.parse(file)
|
|
99
|
+
if (config.extends) {
|
|
100
|
+
const extendedConfig = await getPikkuCLIConfig(
|
|
101
|
+
resolve(configDir, config.extends),
|
|
102
|
+
[],
|
|
103
|
+
tags,
|
|
104
|
+
exitProcess
|
|
105
|
+
)
|
|
106
|
+
result = {
|
|
107
|
+
...extendedConfig,
|
|
108
|
+
...config,
|
|
109
|
+
configDir,
|
|
110
|
+
packageMappings: {
|
|
111
|
+
...extendedConfig.packageMappings,
|
|
112
|
+
...config.packageMappings,
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
result = {
|
|
117
|
+
...config,
|
|
118
|
+
configDir,
|
|
119
|
+
packageMappings: config.packageMappings || {},
|
|
120
|
+
rootDir: config.rootDir
|
|
121
|
+
? resolve(configDir, config.rootDir)
|
|
122
|
+
: configDir,
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (result.outDir) {
|
|
127
|
+
if (!result.schemaDirectory) {
|
|
128
|
+
result.schemaDirectory = join(result.outDir, 'pikku-schemas')
|
|
129
|
+
}
|
|
130
|
+
if (!result.functionsFile) {
|
|
131
|
+
result.functionsFile = join(result.outDir, 'pikku-functions.gen.ts')
|
|
132
|
+
}
|
|
133
|
+
if (!result.functionsMetaFile) {
|
|
134
|
+
result.functionsMetaFile = join(
|
|
135
|
+
result.outDir,
|
|
136
|
+
'pikku-functions-meta.gen.ts'
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
if (!result.rpcFile) {
|
|
140
|
+
result.rpcFile = join(result.outDir, 'pikku-rpc.gen.ts')
|
|
141
|
+
}
|
|
142
|
+
if (!result.httpRoutesFile) {
|
|
143
|
+
result.httpRoutesFile = join(result.outDir, 'pikku-http-routes.gen.ts')
|
|
144
|
+
}
|
|
145
|
+
if (!result.httpRoutesMetaFile) {
|
|
146
|
+
result.httpRoutesMetaFile = join(
|
|
147
|
+
result.outDir,
|
|
148
|
+
'pikku-http-routes-meta.gen.ts'
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
if (!result.schedulersFile) {
|
|
152
|
+
result.schedulersFile = join(result.outDir, 'pikku-schedules.gen.ts')
|
|
153
|
+
}
|
|
154
|
+
if (!result.schedulersMetaFile) {
|
|
155
|
+
result.schedulersMetaFile = join(
|
|
156
|
+
result.outDir,
|
|
157
|
+
'pikku-schedules-meta.gen.ts'
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
if (!result.channelsFile) {
|
|
161
|
+
result.channelsFile = join(result.outDir, 'pikku-channels.gen.ts')
|
|
162
|
+
}
|
|
163
|
+
if (!result.channelsMetaFile) {
|
|
164
|
+
result.channelsMetaFile = join(
|
|
165
|
+
result.outDir,
|
|
166
|
+
'pikku-channels-meta.gen.ts'
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
if (!result.typesDeclarationFile) {
|
|
170
|
+
result.typesDeclarationFile = join(result.outDir, 'pikku-types.gen.ts')
|
|
171
|
+
}
|
|
172
|
+
if (!result.httpRoutesMapDeclarationFile) {
|
|
173
|
+
result.httpRoutesMapDeclarationFile = join(
|
|
174
|
+
result.outDir,
|
|
175
|
+
'pikku-routes-map.gen.d.ts'
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
if (!result.channelsMapDeclarationFile) {
|
|
179
|
+
result.channelsMapDeclarationFile = join(
|
|
180
|
+
result.outDir,
|
|
181
|
+
'pikku-channels-map.gen.d.ts'
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
if (!result.bootstrapFile) {
|
|
185
|
+
result.bootstrapFile = join(result.outDir, 'pikku-bootstrap.gen.ts')
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (requiredFields.length > 0) {
|
|
190
|
+
validateCLIConfig(result, requiredFields)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
for (const objectKey of Object.keys(result)) {
|
|
194
|
+
if (objectKey.endsWith('File') || objectKey.endsWith('Directory')) {
|
|
195
|
+
const relativeTo = CONFIG_DIR_FILES.includes(objectKey)
|
|
196
|
+
? result.configDir
|
|
197
|
+
: result.rootDir
|
|
198
|
+
if (result[objectKey]) {
|
|
199
|
+
if (!isAbsolute(result[objectKey])) {
|
|
200
|
+
result[objectKey] = join(relativeTo, result[objectKey])
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
result.filters = result.filters || {}
|
|
207
|
+
if (tags.length > 0) {
|
|
208
|
+
result.filters.tags = tags
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!isAbsolute(result.tsconfig)) {
|
|
212
|
+
result.tsconfig = join(result.rootDir, result.tsconfig)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return result
|
|
216
|
+
} catch (e: any) {
|
|
217
|
+
console.error(e)
|
|
218
|
+
console.error(`Config file not found: ${configFile}`)
|
|
219
|
+
process.exit(1)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export const validateCLIConfig = (
|
|
224
|
+
cliConfig: PikkuCLIConfig,
|
|
225
|
+
required: Array<keyof PikkuCLIConfig>
|
|
226
|
+
) => {
|
|
227
|
+
let errors: string[] = []
|
|
228
|
+
for (const key of required) {
|
|
229
|
+
if (!cliConfig[key]) {
|
|
230
|
+
errors.push(key)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (errors.length > 0) {
|
|
235
|
+
console.error(
|
|
236
|
+
`${errors.join(', ')} ${errors.length === 1 ? 'is' : 'are'} required in pikku.config.json`
|
|
237
|
+
)
|
|
238
|
+
process.exit(1)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { createGenerator, RootlessError } from 'ts-json-schema-generator'
|
|
2
|
+
import { logInfo, writeFileInDir } from './utils.js'
|
|
3
|
+
import { mkdir, writeFile } from 'fs/promises'
|
|
4
|
+
import { FunctionsMeta, JSONValue } from '@pikku/core'
|
|
5
|
+
import { HTTPRoutesMeta } from '@pikku/core/http'
|
|
6
|
+
import { TypesMap } from '@pikku/inspector'
|
|
7
|
+
|
|
8
|
+
export async function generateSchemas(
|
|
9
|
+
tsconfig: string,
|
|
10
|
+
typesMap: TypesMap,
|
|
11
|
+
functionMeta: FunctionsMeta,
|
|
12
|
+
httpRoutesMeta: HTTPRoutesMeta
|
|
13
|
+
): Promise<Record<string, JSONValue>> {
|
|
14
|
+
const schemasSet = new Set(typesMap.customTypes.keys())
|
|
15
|
+
for (const { inputs, outputs } of Object.values(functionMeta)) {
|
|
16
|
+
const types = [...(inputs || []), ...(outputs || [])]
|
|
17
|
+
for (const type of types) {
|
|
18
|
+
const uniqueName = typesMap.getUniqueName(type)
|
|
19
|
+
if (uniqueName) {
|
|
20
|
+
schemasSet.add(uniqueName)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
for (const { inputTypes } of httpRoutesMeta) {
|
|
25
|
+
if (inputTypes?.body) {
|
|
26
|
+
schemasSet.add(inputTypes.body)
|
|
27
|
+
}
|
|
28
|
+
if (inputTypes?.query) {
|
|
29
|
+
schemasSet.add(inputTypes.query)
|
|
30
|
+
}
|
|
31
|
+
if (inputTypes?.params) {
|
|
32
|
+
schemasSet.add(inputTypes.params)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const generator = createGenerator({
|
|
37
|
+
tsconfig,
|
|
38
|
+
skipTypeCheck: true,
|
|
39
|
+
topRef: false,
|
|
40
|
+
discriminatorType: 'open-api',
|
|
41
|
+
})
|
|
42
|
+
const schemas: Record<string, JSONValue> = {}
|
|
43
|
+
schemasSet.forEach((schema) => {
|
|
44
|
+
try {
|
|
45
|
+
schemas[schema] = generator.createSchema(schema) as JSONValue
|
|
46
|
+
} catch (e) {
|
|
47
|
+
// Ignore rootless errors
|
|
48
|
+
if (e instanceof RootlessError) {
|
|
49
|
+
console.error('Error generating schema since it has no root:', schema)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
throw e
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return schemas
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function saveSchemas(
|
|
60
|
+
schemaParentDir: string,
|
|
61
|
+
schemas: Record<string, JSONValue>,
|
|
62
|
+
typesMap: TypesMap,
|
|
63
|
+
functionsMeta: FunctionsMeta,
|
|
64
|
+
supportsImportAttributes: boolean
|
|
65
|
+
) {
|
|
66
|
+
await writeFileInDir(
|
|
67
|
+
`${schemaParentDir}/register.gen.ts`,
|
|
68
|
+
'export const empty = null;'
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
const desiredSchemas = new Set([
|
|
72
|
+
...Object.values(functionsMeta)
|
|
73
|
+
.map(({ inputs, outputs }) => [
|
|
74
|
+
inputs?.[0] ? typesMap.getUniqueName(inputs[0]) : undefined,
|
|
75
|
+
outputs?.[0] ? typesMap.getUniqueName(outputs[0]) : undefined,
|
|
76
|
+
])
|
|
77
|
+
.flat()
|
|
78
|
+
.filter(
|
|
79
|
+
(s) =>
|
|
80
|
+
!!s &&
|
|
81
|
+
!['boolean', 'string', 'number', 'null', 'undefined'].includes(s)
|
|
82
|
+
),
|
|
83
|
+
...typesMap.customTypes.keys(),
|
|
84
|
+
])
|
|
85
|
+
|
|
86
|
+
if (desiredSchemas.size === 0) {
|
|
87
|
+
logInfo(`• Skipping schemas since none found.\x1b[0m`)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
await mkdir(`${schemaParentDir}/schemas`, { recursive: true })
|
|
92
|
+
await Promise.all(
|
|
93
|
+
Object.entries(schemas).map(async ([schemaName, schema]) => {
|
|
94
|
+
if (desiredSchemas.has(schemaName)) {
|
|
95
|
+
await writeFile(
|
|
96
|
+
`${schemaParentDir}/schemas/${schemaName}.schema.json`,
|
|
97
|
+
JSON.stringify(schema),
|
|
98
|
+
'utf-8'
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
const schemaImports = Array.from(desiredSchemas)
|
|
105
|
+
.map(
|
|
106
|
+
(schema) => `
|
|
107
|
+
import * as ${schema} from './schemas/${schema}.schema.json' ${supportsImportAttributes ? `with { type: 'json' }` : ''}
|
|
108
|
+
addSchema('${schema}', ${schema})
|
|
109
|
+
`
|
|
110
|
+
)
|
|
111
|
+
.join('\n')
|
|
112
|
+
|
|
113
|
+
await writeFileInDir(
|
|
114
|
+
`${schemaParentDir}/register.gen.ts`,
|
|
115
|
+
`import { addSchema } from '@pikku/core/schema'
|
|
116
|
+
${schemaImports}`
|
|
117
|
+
)
|
|
118
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export const serializeFetchWrapper = (routesMapPath) => {
|
|
2
|
-
|
|
1
|
+
export const serializeFetchWrapper = (routesMapPath: string) => {
|
|
2
|
+
return `
|
|
3
3
|
import { CorePikkuFetch, HTTPMethod } from '@pikku/fetch'
|
|
4
4
|
import type { RoutesMap, RouteHandlerOf, RoutesWithMethod } from '${routesMapPath}'
|
|
5
5
|
|
|
@@ -63,5 +63,5 @@ export class PikkuFetch extends CorePikkuFetch {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
export const pikkuFetch = new PikkuFetch();
|
|
66
|
-
|
|
67
|
-
}
|
|
66
|
+
`
|
|
67
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { TypesMap } from '@pikku/inspector'
|
|
2
|
+
import { getFileImportRelativePath } from './utils.js'
|
|
3
|
+
|
|
4
|
+
export const serializeImportMap = (
|
|
5
|
+
relativeToPath: string,
|
|
6
|
+
packageMappings: Record<string, string>,
|
|
7
|
+
typesMap: TypesMap,
|
|
8
|
+
requiredTypes: Set<string>
|
|
9
|
+
) => {
|
|
10
|
+
const paths = new Map<string, string[]>()
|
|
11
|
+
Array.from(requiredTypes).forEach((requiredType) => {
|
|
12
|
+
const { originalName, uniqueName, path } =
|
|
13
|
+
typesMap.getTypeMeta(requiredType)
|
|
14
|
+
if (!path) {
|
|
15
|
+
// This is a custom type that exists in file, so we don't need to import it
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
const variables = paths.get(path) || []
|
|
19
|
+
if (originalName === uniqueName) {
|
|
20
|
+
variables.push(originalName)
|
|
21
|
+
} else {
|
|
22
|
+
variables.push(`${originalName} as ${uniqueName}`)
|
|
23
|
+
}
|
|
24
|
+
paths.set(path, variables)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const imports: string[] = []
|
|
28
|
+
for (const [path, variables] of paths.entries()) {
|
|
29
|
+
imports.push(
|
|
30
|
+
`import type { ${variables.join(', ')} } from '${getFileImportRelativePath(relativeToPath, path, packageMappings)}'`
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
return imports.join('\n')
|
|
34
|
+
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
export const serializeNextJsBackendWrapper = (
|
|
2
|
-
|
|
1
|
+
export const serializeNextJsBackendWrapper = (
|
|
2
|
+
routesPath: string,
|
|
3
|
+
routesMapPath: string,
|
|
4
|
+
schemasPath: string,
|
|
5
|
+
configImport: string,
|
|
6
|
+
singleServicesFactoryImport: string,
|
|
7
|
+
sessionServicesImport: string
|
|
8
|
+
) => {
|
|
9
|
+
return `'server-only'
|
|
3
10
|
|
|
4
11
|
/**
|
|
5
12
|
* This file provides a wrapper around the PikkuNextJS class to allow for methods to be type checked against your routes.
|
|
@@ -176,5 +183,5 @@ export const pikku = (_options?: any) => {
|
|
|
176
183
|
staticPost
|
|
177
184
|
}
|
|
178
185
|
}
|
|
179
|
-
|
|
180
|
-
}
|
|
186
|
+
`
|
|
187
|
+
}
|
package/{dist/src/nextjs/serialize-nextjs-http-wrapper.js → src/serialize-nextjs-http-wrapper.ts}
RENAMED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export const serializeNextJsHTTPWrapper = (
|
|
2
|
-
|
|
1
|
+
export const serializeNextJsHTTPWrapper = (
|
|
2
|
+
routesMapPath: string,
|
|
3
|
+
pikkuFetchImport: string
|
|
4
|
+
) => {
|
|
5
|
+
return `'server-only'
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* This file provides a wrapper around the PikkuNextJS class to allow for methods to be type checked against your routes.
|
|
@@ -156,5 +159,5 @@ export const pikku = (options?: CorePikkuFetchOptions) => {
|
|
|
156
159
|
staticPost
|
|
157
160
|
}
|
|
158
161
|
}
|
|
159
|
-
|
|
160
|
-
}
|
|
162
|
+
`
|
|
163
|
+
}
|