@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.
Files changed (99) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/bin/pikku-all.ts +203 -0
  3. package/bin/pikku-channels-map.ts +55 -0
  4. package/bin/pikku-channels.ts +63 -0
  5. package/bin/pikku-fetch.ts +55 -0
  6. package/bin/pikku-function-types.ts +84 -0
  7. package/bin/pikku-functions.ts +35 -0
  8. package/bin/pikku-http-map.ts +56 -0
  9. package/bin/pikku-http-routes.ts +63 -0
  10. package/bin/pikku-nextjs.test.ts +279 -0
  11. package/bin/pikku-nextjs.ts +152 -0
  12. package/bin/pikku-openapi.ts +76 -0
  13. package/bin/pikku-rpc.ts +22 -0
  14. package/bin/pikku-scheduler.ts +64 -0
  15. package/bin/pikku-schemas.ts +57 -0
  16. package/bin/pikku-websocket.ts +58 -0
  17. package/bin/pikku.ts +26 -0
  18. package/dist/bin/pikku-all.js +3 -0
  19. package/dist/bin/pikku-functions.d.ts +0 -2
  20. package/dist/bin/pikku-functions.js +2 -17
  21. package/dist/bin/pikku-http-map.js +1 -1
  22. package/dist/bin/pikku-openapi.js +2 -2
  23. package/dist/bin/pikku-rpc.d.ts +3 -0
  24. package/dist/bin/pikku-rpc.js +8 -0
  25. package/dist/bin/pikku-schemas.js +2 -2
  26. package/dist/bin/pikku.js +0 -0
  27. package/dist/src/openapi-spec-generator.d.ts +2 -2
  28. package/dist/src/openapi-spec-generator.js +14 -5
  29. package/dist/src/pikku-cli-config.d.ts +1 -0
  30. package/dist/src/pikku-cli-config.js +3 -0
  31. package/dist/src/schema-generator.d.ts +3 -3
  32. package/dist/src/schema-generator.js +15 -34
  33. package/dist/src/serialize-typed-function-map.d.ts +2 -1
  34. package/dist/src/serialize-typed-function-map.js +6 -4
  35. package/dist/src/serialize-typed-http-map.d.ts +2 -1
  36. package/dist/src/serialize-typed-http-map.js +10 -4
  37. package/dist/tsconfig.tsbuildinfo +1 -0
  38. package/package.json +3 -3
  39. package/src/inspector-glob.ts +28 -0
  40. package/src/openapi-spec-generator.ts +246 -0
  41. package/src/pikku-cli-config.ts +240 -0
  42. package/src/schema-generator.ts +118 -0
  43. package/{dist/src/events/http/serialize-fetch-wrapper.js → src/serialize-fetch-wrapper.ts} +4 -4
  44. package/src/serialize-import-map.ts +34 -0
  45. package/{dist/src/nextjs/serialize-nextjs-backend-wrapper.js → src/serialize-nextjs-backend-wrapper.ts} +11 -4
  46. package/{dist/src/nextjs/serialize-nextjs-http-wrapper.js → src/serialize-nextjs-http-wrapper.ts} +7 -4
  47. package/{dist/src/core/serialize-pikku-types.js → src/serialize-pikku-types.ts} +46 -35
  48. package/src/serialize-scheduler-meta.ts +18 -0
  49. package/src/serialize-typed-channel-map.ts +138 -0
  50. package/src/serialize-typed-function-map.ts +161 -0
  51. package/src/serialize-typed-http-map.ts +167 -0
  52. package/{dist/src/channels/serialize-websocket-wrapper.js → src/serialize-websocket-wrapper.ts} +4 -4
  53. package/src/utils.ts +284 -0
  54. package/tsconfig.json +21 -0
  55. package/dist/bin/pikku-http.d.ts +0 -5
  56. package/dist/bin/pikku-http.js +0 -27
  57. package/dist/bin/pikku-routes-map.d.ts +0 -5
  58. package/dist/bin/pikku-routes-map.js +0 -23
  59. package/dist/src/channels/serialize-channels.d.ts +0 -3
  60. package/dist/src/channels/serialize-channels.js +0 -19
  61. package/dist/src/channels/serialize-typed-channel-map.d.ts +0 -3
  62. package/dist/src/channels/serialize-typed-channel-map.js +0 -93
  63. package/dist/src/channels/serialize-websocket-wrapper.d.ts +0 -1
  64. package/dist/src/core/serialize-import-map.d.ts +0 -2
  65. package/dist/src/core/serialize-import-map.js +0 -24
  66. package/dist/src/core/serialize-pikku-types.d.ts +0 -4
  67. package/dist/src/events/channels/serialize-channels.d.ts +0 -3
  68. package/dist/src/events/channels/serialize-channels.js +0 -19
  69. package/dist/src/events/channels/serialize-typed-channel-map.d.ts +0 -3
  70. package/dist/src/events/channels/serialize-typed-channel-map.js +0 -90
  71. package/dist/src/events/channels/serialize-websocket-wrapper.d.ts +0 -1
  72. package/dist/src/events/channels/serialize-websocket-wrapper.js +0 -61
  73. package/dist/src/events/http/serialize-fetch-wrapper.d.ts +0 -1
  74. package/dist/src/events/http/serialize-route-imports.d.ts +0 -1
  75. package/dist/src/events/http/serialize-route-imports.js +0 -13
  76. package/dist/src/events/http/serialize-route-meta.d.ts +0 -2
  77. package/dist/src/events/http/serialize-route-meta.js +0 -6
  78. package/dist/src/events/http/serialize-typed-route-map.d.ts +0 -4
  79. package/dist/src/events/http/serialize-typed-route-map.js +0 -107
  80. package/dist/src/events/scheduler/serialize-schedulers.d.ts +0 -3
  81. package/dist/src/events/scheduler/serialize-schedulers.js +0 -23
  82. package/dist/src/http/serialize-fetch-wrapper.d.ts +0 -1
  83. package/dist/src/http/serialize-fetch-wrapper.js +0 -67
  84. package/dist/src/http/serialize-route-imports.d.ts +0 -1
  85. package/dist/src/http/serialize-route-imports.js +0 -13
  86. package/dist/src/http/serialize-route-meta.d.ts +0 -2
  87. package/dist/src/http/serialize-route-meta.js +0 -6
  88. package/dist/src/http/serialize-typed-route-map.d.ts +0 -4
  89. package/dist/src/http/serialize-typed-route-map.js +0 -107
  90. package/dist/src/nextjs/serialize-nextjs-backend-wrapper.d.ts +0 -1
  91. package/dist/src/nextjs/serialize-nextjs-http-wrapper.d.ts +0 -1
  92. package/dist/src/openapi/openapi-spec-generator.d.ts +0 -79
  93. package/dist/src/openapi/openapi-spec-generator.js +0 -136
  94. package/dist/src/scheduler/serialize-schedulers.d.ts +0 -3
  95. package/dist/src/scheduler/serialize-schedulers.js +0 -23
  96. package/dist/src/schema/schema-generator.d.ts +0 -5
  97. package/dist/src/schema/schema-generator.js +0 -89
  98. package/dist/src/serialize-typed-route-map.d.ts +0 -4
  99. 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
- return `
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 = (routesPath, routesMapPath, schemasPath, configImport, singleServicesFactoryImport, sessionServicesImport) => {
2
- return `'server-only'
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
+ }
@@ -1,5 +1,8 @@
1
- export const serializeNextJsHTTPWrapper = (routesMapPath, pikkuFetchImport) => {
2
- return `'server-only'
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
+ }