@buildvision/nest-sdk-generator 1.1.3

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 (69) hide show
  1. package/.prettierrc +6 -0
  2. package/LICENSE.md +9 -0
  3. package/README.md +353 -0
  4. package/build/analyzer/builtin.d.ts +2 -0
  5. package/build/analyzer/builtin.js +11 -0
  6. package/build/analyzer/classdeps.d.ts +6 -0
  7. package/build/analyzer/classdeps.js +27 -0
  8. package/build/analyzer/controller.d.ts +26 -0
  9. package/build/analyzer/controller.js +103 -0
  10. package/build/analyzer/controllers.d.ts +7 -0
  11. package/build/analyzer/controllers.js +79 -0
  12. package/build/analyzer/decorator.d.ts +10 -0
  13. package/build/analyzer/decorator.js +29 -0
  14. package/build/analyzer/extractor.d.ts +107 -0
  15. package/build/analyzer/extractor.js +349 -0
  16. package/build/analyzer/index.d.ts +16 -0
  17. package/build/analyzer/index.js +101 -0
  18. package/build/analyzer/methods.d.ts +42 -0
  19. package/build/analyzer/methods.js +115 -0
  20. package/build/analyzer/module.d.ts +11 -0
  21. package/build/analyzer/module.js +41 -0
  22. package/build/analyzer/params.d.ts +38 -0
  23. package/build/analyzer/params.js +163 -0
  24. package/build/analyzer/route.d.ts +53 -0
  25. package/build/analyzer/route.js +131 -0
  26. package/build/analyzer/typedeps.d.ts +49 -0
  27. package/build/analyzer/typedeps.js +86 -0
  28. package/build/bin.d.ts +2 -0
  29. package/build/bin.js +39 -0
  30. package/build/config.d.ts +44 -0
  31. package/build/config.js +31 -0
  32. package/build/generator/genmodules.d.ts +24 -0
  33. package/build/generator/genmodules.js +134 -0
  34. package/build/generator/gentypes.d.ts +10 -0
  35. package/build/generator/gentypes.js +62 -0
  36. package/build/generator/index.d.ts +6 -0
  37. package/build/generator/index.js +83 -0
  38. package/build/generator/prettier.d.ts +16 -0
  39. package/build/generator/prettier.js +43 -0
  40. package/build/generator/sdk-interface.d.ts +1 -0
  41. package/build/generator/sdk-interface.js +36 -0
  42. package/build/logging.d.ts +6 -0
  43. package/build/logging.js +37 -0
  44. package/build/tsconfig.tsbuildinfo +1 -0
  45. package/build/utils.d.ts +9 -0
  46. package/build/utils.js +56 -0
  47. package/package.json +34 -0
  48. package/src/analyzer/builtin.ts +9 -0
  49. package/src/analyzer/classdeps.ts +37 -0
  50. package/src/analyzer/controller.ts +135 -0
  51. package/src/analyzer/controllers.ts +117 -0
  52. package/src/analyzer/decorator.ts +29 -0
  53. package/src/analyzer/extractor.ts +491 -0
  54. package/src/analyzer/index.ts +136 -0
  55. package/src/analyzer/methods.ts +166 -0
  56. package/src/analyzer/module.ts +56 -0
  57. package/src/analyzer/params.ts +241 -0
  58. package/src/analyzer/route.ts +156 -0
  59. package/src/analyzer/typedeps.ts +130 -0
  60. package/src/bin.ts +33 -0
  61. package/src/config.ts +89 -0
  62. package/src/generator/genmodules.ts +163 -0
  63. package/src/generator/gentypes.ts +73 -0
  64. package/src/generator/index.ts +92 -0
  65. package/src/generator/prettier.ts +46 -0
  66. package/src/generator/sdk-interface.ts +32 -0
  67. package/src/logging.ts +35 -0
  68. package/src/utils.ts +61 -0
  69. package/tsconfig.json +26 -0
@@ -0,0 +1,156 @@
1
+ /**
2
+ * @file Analyzer for the source API's routes in controller methods
3
+ */
4
+
5
+ /**
6
+ * A single URI part
7
+ */
8
+ export type RoutePart = { readonly segment: string } | { readonly param: string }
9
+
10
+ /**
11
+ * A parsed URI path
12
+ */
13
+ export interface Route {
14
+ /** Is this an absolute route? (routes starting with a '/') */
15
+ readonly isRoot: boolean
16
+
17
+ /** The route's parts */
18
+ readonly parts: RoutePart[]
19
+ }
20
+
21
+ /**
22
+ * Analyze an URI path
23
+ * @param uriPath The URI path to analyze
24
+ */
25
+ export function analyzeUri(uriPath: string): Route | Error {
26
+ // Split the URI path into "parts"
27
+ const rawParts = uriPath.split('/')
28
+
29
+ // The parsed parts we'll return
30
+ const parts: RoutePart[] = []
31
+
32
+ // The current string offset in the URI path
33
+ // This variable is used for error display
34
+ let offset = 0
35
+
36
+ /**
37
+ * Find if a specific part of the URI path contains an invalid character
38
+ * @param part The part to check
39
+ * @param pattern The invalid characters to look for, as a regular expression
40
+ * @returns An error message if an invalid character is found, nothing else
41
+ */
42
+ function _findInvalid(part: string, pattern: RegExp): string | null {
43
+ return part.match(pattern) ? uriPath + '\n' + ' '.repeat(offset) + '^' : null
44
+ }
45
+
46
+ // Treat all parts of the URI path
47
+ for (let i = 0; i < rawParts.length; i++) {
48
+ const part = rawParts[i]
49
+
50
+ // Ignore empty parts (e.g. "/a///b" will be treated as "/a/b")
51
+ if (part === '') continue
52
+
53
+ // Ensure there is no generic character in the path as we don't support them
54
+ const genericErr = _findInvalid(part, /[\*\+\?]/)
55
+
56
+ if (genericErr) {
57
+ return new Error(
58
+ 'Generic symbols (* + ?) are not supported as they prevent from determining the right route to use. Found in URI:\n' + genericErr
59
+ )
60
+ }
61
+
62
+ // Check if this part is an URI parameter
63
+ if (part.startsWith(':')) {
64
+ // URI parameters must follow a strict naming
65
+ const paramErr = _findInvalid(part, /[^a-zA-Z0-9_:]/)
66
+
67
+ if (paramErr) {
68
+ return new Error('Invalid character detected in named parameter in URI:\n' + paramErr)
69
+ }
70
+
71
+ // We got a parameter
72
+ parts.push({ param: part.substr(1) })
73
+ } else {
74
+ // We got a literal part
75
+ parts.push({ segment: part })
76
+ }
77
+
78
+ // Update the offset for error display
79
+ offset += part.length + 1
80
+ }
81
+
82
+ // Success!
83
+ return {
84
+ isRoot: uriPath.startsWith('/'),
85
+ parts,
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Get the named parameters of a parsed route
91
+ * @param route
92
+ */
93
+ export function paramsOfRoute(route: Route): string[] {
94
+ return route.parts.map((part) => ('param' in part ? part.param : null)).filter((e) => e !== null) as string[]
95
+ }
96
+
97
+ /**
98
+ * Convert a route back to its original string
99
+ * @param route
100
+ */
101
+ export function unparseRoute(route: Route): string {
102
+ return (route.isRoot ? '/' : '') + route.parts.map((part) => ('segment' in part ? part.segment : ':' + part.param)).join('/')
103
+ }
104
+
105
+ /**
106
+ * Pretty-print a route
107
+ */
108
+ export function debugUri(route: Route, color: (str: string) => string): string {
109
+ return (route.isRoot ? '/' : '') + route.parts.map((part) => ('segment' in part ? part.segment : color(':' + part.param))).join('/')
110
+ }
111
+
112
+ /**
113
+ * Resolve a route by providing its required parameters
114
+ * @param route
115
+ * @param params
116
+ */
117
+ export function resolveRoute(route: Route, params: { [name: string]: string }): string | Error {
118
+ let uri: string[] = []
119
+
120
+ for (const part of route.parts) {
121
+ if ('segment' in part) {
122
+ uri.push(part.segment)
123
+ } else if (!params.hasOwnProperty(part.param)) {
124
+ return new Error('Missing route parameter ' + part.param)
125
+ } else {
126
+ uri.push(params[part.param])
127
+ }
128
+ }
129
+
130
+ return (route.isRoot ? '/' : '') + uri.join('/')
131
+ }
132
+
133
+ /**
134
+ * Resolve a route by providing its required parameters through a callback
135
+ * @param route
136
+ * @param paramsProvider
137
+ */
138
+ export function resolveRouteWith(route: Route, paramsProvider: (param: string) => string | null): string | Error {
139
+ let uri: string[] = []
140
+
141
+ for (const part of route.parts) {
142
+ if ('segment' in part) {
143
+ uri.push(part.segment)
144
+ } else {
145
+ const param = paramsProvider(part.param)
146
+
147
+ if (param === null) {
148
+ return new Error('Missing route parameter ' + part.param)
149
+ } else {
150
+ uri.push(param)
151
+ }
152
+ }
153
+ }
154
+
155
+ return (route.isRoot ? '/' : '') + uri.join('/')
156
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @file Type dependencies builders from the source API
3
+ */
4
+
5
+ import * as path from 'path'
6
+ import { ts, Type } from 'ts-morph'
7
+ import { unreachable } from '../logging'
8
+
9
+ /**
10
+ * Regex to match or replace imported types
11
+ */
12
+ export const IMPORTED_TYPE_REGEX = /\bimport\(['"]([^'"]+)['"]\)\.([a-zA-Z0-9_]+)\b/g
13
+
14
+ /**
15
+ * Type with resolved level 1 dependencies (does not look for dependencies' own dependencies!)
16
+ */
17
+ export interface ResolvedTypeDeps {
18
+ /** The original raw type, with import("...") expressions */
19
+ readonly rawType: string
20
+
21
+ /** The resolved type, without imports */
22
+ readonly resolvedType: string
23
+
24
+ /** File from which the type originates */
25
+ readonly relativeFilePath: string
26
+
27
+ /**
28
+ * The type dependencies used by the resolved type
29
+ * A collection of scripts' relative paths mapped with the list of types imported from them
30
+ */
31
+ readonly dependencies: Map<string, string[]>
32
+
33
+ /**
34
+ * Non-native types that are not imported
35
+ * They may be either local types (declared in the same file than the one analyzed) or globally-defined types
36
+ */
37
+ readonly localTypes: string[]
38
+ }
39
+
40
+ /**
41
+ * Resolve the dependencies of a TS-Morph analyzed type
42
+ * @param type
43
+ * @param relativeFilePath
44
+ * @param absoluteSrcPath
45
+ * @returns
46
+ */
47
+ export function resolveTypeDependencies(type: Type<ts.Type>, relativeFilePath: string, absoluteSrcPath: string): ResolvedTypeDeps {
48
+ /** Raw type's text (e.g. `Array<import("somefile.ts").SomeType>`) */
49
+ const rawType = type.getText()
50
+
51
+ if (path.isAbsolute(relativeFilePath)) {
52
+ unreachable(
53
+ 'Internal error: got absolute file path in type dependencies resolver, when expecting a relative one (got {magentaBright})',
54
+ relativeFilePath
55
+ )
56
+ }
57
+
58
+ let dependencies: ResolvedTypeDeps['dependencies'] = new Map()
59
+ let localTypes: ResolvedTypeDeps['localTypes'] = []
60
+
61
+ /** Resolved type (without import statements) */
62
+ const resolvedType: ResolvedTypeDeps['resolvedType'] = rawType.replace(IMPORTED_TYPE_REGEX, (_, matchedFilePath, type) => {
63
+ const filePath = path.isAbsolute(matchedFilePath) ? path.relative(absoluteSrcPath, matchedFilePath) : matchedFilePath
64
+
65
+ const deps = dependencies.get(filePath)
66
+
67
+ if (deps) {
68
+ if (!deps.includes(type)) {
69
+ deps.push(type)
70
+ }
71
+ } else {
72
+ dependencies.set(filePath, [type])
73
+ }
74
+
75
+ return type
76
+ })
77
+
78
+ if (resolvedType.includes('import(')) {
79
+ unreachable('Internal error: resolved still contains an {magenta} statement: {green}', 'import(...)', resolvedType)
80
+ }
81
+
82
+ for (const depFile of dependencies.keys()) {
83
+ if (path.isAbsolute(depFile)) {
84
+ unreachable(
85
+ 'Internal error: resolved absolute file path in type dependencies, when should have resolved a relative one\nIn type: {yellow}\nGot: {magentaBright}',
86
+ type.getText(),
87
+ depFile
88
+ )
89
+ }
90
+ }
91
+
92
+ return {
93
+ rawType,
94
+ relativeFilePath,
95
+ resolvedType,
96
+ dependencies,
97
+ localTypes,
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Extract an import type's name from a TS-Morph type
103
+ * @example "import('dir/file').TypeName" => "TypeName"
104
+ * @param type
105
+ * @returns
106
+ */
107
+ export function getImportResolvedType(type: Type<ts.Type>): string {
108
+ return type.getText().replace(IMPORTED_TYPE_REGEX, (_, __, typename) => typename)
109
+ }
110
+
111
+ /**
112
+ * Convert paths for external files
113
+ * @param importedFilePath
114
+ */
115
+ export function normalizeExternalFilePath(importedFilePath: string): string {
116
+ importedFilePath = path.normalize(importedFilePath)
117
+
118
+ if (!importedFilePath.startsWith('../')) {
119
+ return importedFilePath
120
+ }
121
+
122
+ let level = 0
123
+
124
+ while (importedFilePath.startsWith('../')) {
125
+ level++
126
+ importedFilePath = importedFilePath.substr(3)
127
+ }
128
+
129
+ return `_external${level}/${importedFilePath}`
130
+ }
package/src/bin.ts ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+
3
+ import * as path from 'path'
4
+ import { analyzerCli } from './analyzer'
5
+ import { config, configPath } from './config'
6
+ import generatorCli from './generator'
7
+ import { println } from './logging'
8
+
9
+ async function main() {
10
+ const started = Date.now()
11
+
12
+ process.chdir(path.dirname(path.resolve(configPath)))
13
+
14
+ switch (process.argv[3]) {
15
+ case '--analyze':
16
+ await analyzerCli(config)
17
+ break
18
+
19
+ case '--generate':
20
+ case undefined:
21
+ const sdkContent = await analyzerCli(config)
22
+ await generatorCli(config, sdkContent)
23
+ break
24
+
25
+ default:
26
+ console.error('ERROR: Unknown action provided (must be either "--analyze" or "--generate")')
27
+ process.exit(1)
28
+ }
29
+
30
+ println('{green}', '@ Done in ' + ((Date.now() - started) / 1000).toFixed(2) + 's')
31
+ }
32
+
33
+ main()
package/src/config.ts ADDED
@@ -0,0 +1,89 @@
1
+ import chalk = require('chalk')
2
+ import * as fs from 'fs'
3
+ import * as path from 'path'
4
+
5
+ /**
6
+ * The configuration file's content
7
+ * For details on what these options do, see the project's README
8
+ */
9
+ export interface Config {
10
+ /** Enable verbose mode */
11
+ readonly verbose?: boolean
12
+
13
+ /** Disable colored output */
14
+ readonly noColor?: boolean
15
+
16
+ /** Path to the API's source directory */
17
+ readonly apiInputPath: string
18
+
19
+ /** Path to generate the SDK at */
20
+ readonly sdkOutput: string
21
+
22
+ /** Path to the SDK interface file */
23
+ readonly sdkInterfacePath: string
24
+
25
+ /** List of magic types */
26
+ readonly magicTypes?: MagicType[]
27
+
28
+ /** Show a JSON output */
29
+ readonly jsonOutput?: string
30
+
31
+ /** Prettify the JSON output */
32
+ readonly jsonPrettyOutput?: boolean
33
+
34
+ /** Prettify the generated files (enabled by default) */
35
+ readonly prettify?: boolean
36
+
37
+ /** Path to Prettier's configuration file */
38
+ readonly prettierConfig?: string
39
+
40
+ /** Path to custom tsconfig file */
41
+ readonly tsconfigFile?: string
42
+
43
+ /** If the output directory already exists, overwrite it (enabled by default) */
44
+ readonly overwriteOldOutputDir?: boolean
45
+
46
+ /** If the SDK interface file does not exist yet, create one automatically (enabled by default) */
47
+ readonly generateDefaultSdkInterface?: boolean
48
+
49
+ /** Write generation timestamp in each TypeScript file (enabled by default) */
50
+ readonly generateTimestamps?: boolean
51
+ }
52
+
53
+ /**
54
+ * Magic type used to replace a non-compatible type in the generated SDK
55
+ */
56
+ export interface MagicType {
57
+ readonly nodeModuleFilePath: string
58
+ readonly typeName: string
59
+ readonly placeholderContent: string
60
+ }
61
+
62
+ /**
63
+ * Load an existing configuration file and decode it
64
+ * @param configPath
65
+ */
66
+ function loadConfigFile(configPath: string): Config {
67
+ if (!fs.existsSync(configPath)) {
68
+ console.error(chalk.red('Config file was not found at path: ' + chalk.yellow(path.resolve(configPath))))
69
+ process.exit(4)
70
+ }
71
+
72
+ const text = fs.readFileSync(configPath, 'utf8')
73
+
74
+ try {
75
+ return JSON.parse(text)
76
+ } catch (e) {
77
+ console.error(chalk.red('Failed to parse configuration file: ' + e))
78
+ process.exit(3)
79
+ }
80
+ }
81
+
82
+ export const configPath = process.argv[2]
83
+
84
+ if (!configPath) {
85
+ console.error(chalk.red('Please provide a path to the configuration file'))
86
+ process.exit(2)
87
+ }
88
+
89
+ export const config = loadConfigFile(configPath)
@@ -0,0 +1,163 @@
1
+ /**
2
+ * @file Generate SDK modules
3
+ */
4
+
5
+ import * as path from 'path'
6
+ import { SdkModules } from '../analyzer/controllers'
7
+ import { SdkMethod } from '../analyzer/methods'
8
+ import { SdkMethodParams } from '../analyzer/params'
9
+ import { resolveRouteWith, unparseRoute } from '../analyzer/route'
10
+ import { normalizeExternalFilePath, ResolvedTypeDeps } from '../analyzer/typedeps'
11
+ import { panic } from '../logging'
12
+
13
+ /**
14
+ * Generate the SDK's module and controllers files
15
+ * @param modules
16
+ * @returns
17
+ */
18
+ export function generateSdkModules(modules: SdkModules): Map<string, string> {
19
+ /** Generated module files */
20
+ const genFiles = new Map<string, string>()
21
+
22
+ // Iterate over each module
23
+ for (const [moduleName, controllers] of modules) {
24
+ // Iterate over each of the module's controllers
25
+ for (const [controllerName, controller] of controllers) {
26
+ /** Generated controller's content */
27
+ const out: string[] = []
28
+
29
+ out.push('/// Parent module: ' + moduleName)
30
+ out.push(`/// Controller: "${controllerName}" registered as "${controller.registrationName}" (${controller.methods.length} routes)`)
31
+ out.push('')
32
+ out.push('import { request } from "../central";')
33
+
34
+ const imports = new Map<string, string[]>()
35
+
36
+ const depsToImport = new Array<ResolvedTypeDeps>()
37
+
38
+ // Iterate over each controller
39
+ for (const method of controller.methods.values()) {
40
+ const { parameters: args, query, body } = method.params
41
+
42
+ depsToImport.push(method.returnType)
43
+
44
+ if (args) {
45
+ depsToImport.push(...args.values())
46
+ }
47
+
48
+ if (query) {
49
+ depsToImport.push(...query.values())
50
+ }
51
+
52
+ if (body) {
53
+ if (body.full) {
54
+ depsToImport.push(body.type)
55
+ } else {
56
+ depsToImport.push(...body.fields.values())
57
+ }
58
+ }
59
+ }
60
+
61
+ // Build the imports list
62
+ for (const dep of depsToImport) {
63
+ for (const [file, types] of dep.dependencies) {
64
+ let imported = imports.get(file)
65
+
66
+ if (!imported) {
67
+ imported = []
68
+ imports.set(file, imported)
69
+ }
70
+
71
+ for (const typ of types) {
72
+ if (!imported.includes(typ)) {
73
+ imported.push(typ)
74
+ }
75
+ }
76
+ }
77
+ }
78
+
79
+ for (const [file, types] of imports) {
80
+ out.push(
81
+ `import type { ${types.join(', ')} } from "../_types/${normalizeExternalFilePath(file.replace(/\\/g, '/')).replace(/\\/g, '/')}";`
82
+ )
83
+ }
84
+
85
+ out.push('')
86
+ out.push(`export default {`)
87
+
88
+ for (const method of controller.methods) {
89
+ const ret = method.returnType.resolvedType
90
+ const promised = ret.startsWith('Promise<') ? ret : `Promise<${ret}>`
91
+
92
+ out.push('')
93
+ out.push(` // ${method.httpMethod} @ ${unparseRoute(method.route)}`)
94
+ out.push(` ${method.name}(${generateSdkMethodParams(method.params)}): ${promised} {`)
95
+ out.push(generateCentralRequest(method).replace(/^/gm, ' '))
96
+ out.push(' },')
97
+ }
98
+
99
+ out.push('')
100
+ out.push('};')
101
+
102
+ genFiles.set(path.join(moduleName, controller.camelClassName + '.ts'), out.join('\n'))
103
+ }
104
+
105
+ /** Generated module's content */
106
+ const moduleContent: string[] = []
107
+
108
+ moduleContent.push('/// Module name: ' + moduleName)
109
+ moduleContent.push('')
110
+
111
+ for (const controller of controllers.keys()) {
112
+ moduleContent.push(`export { default as ${controller} } from "./${controller}";`)
113
+ }
114
+
115
+ // Generate the SDK module file
116
+ genFiles.set(path.join(moduleName, 'index.ts'), moduleContent.join('\n'))
117
+ }
118
+
119
+ return genFiles
120
+ }
121
+
122
+ /**
123
+ * Generate the method parameters for a given SDK method
124
+ * @param params
125
+ * @returns
126
+ */
127
+ export function generateSdkMethodParams(params: SdkMethodParams): string {
128
+ // List of parameters (e.g. `id` in `/get/:id`, analyzed from the usages of the `@Param` decorator)
129
+ const parameters = params.parameters ? [...params.parameters].map(([name, type]) => `${name}: ${type.resolvedType}`) : []
130
+
131
+ // List of query values (e.g. `id` in `?id=xxx`, analyzed from the usages of the `@Query` decorator)
132
+ const query = params.query ? [...params.query].map(([name, type]) => `${name}: ${type.resolvedType}`) : []
133
+
134
+ // Body's content (type used with the `@Body` decorator)
135
+ const body = params.body
136
+ ? params.body.full
137
+ ? params.body.type.resolvedType
138
+ : '{ ' + [...params.body.fields].map(([name, type]) => `${name}: ${type.resolvedType}`).join(', ') + ' }'
139
+ : null
140
+
141
+ // The ternary conditions below are made to eclipse useless parameters
142
+ // For instance, if we're not expecting any query nor body, these two parameters can be omitted when calling the method
143
+ return [
144
+ `params: {${' ' + parameters.join(', ') + ' '}}${parameters.length === 0 && !body && query.length === 0 ? ' = {}' : ''}`,
145
+ `body: ${body ?? '{}'}${!body && query.length === 0 ? ' = {}' : ''}`,
146
+ `query: {${' ' + query.join(', ') + ' '}}${query.length === 0 ? ' = {}' : ''}`,
147
+ ].join(', ')
148
+ }
149
+
150
+ /**
151
+ * Generate a request call to Central for the generated files
152
+ * @param method
153
+ * @returns
154
+ */
155
+ export function generateCentralRequest(method: SdkMethod): string {
156
+ const resolvedRoute = resolveRouteWith(method.route, (param) => '${params.' + param + '}')
157
+
158
+ if (resolvedRoute instanceof Error) {
159
+ panic('Internal error: failed to resolve route: ' + resolvedRoute.message)
160
+ }
161
+
162
+ return `return request('${method.httpMethod}', \`${resolvedRoute}\`, body, query) as any`
163
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * @file Generate type files for the SDK
3
+ */
4
+
5
+ import * as path from 'path'
6
+ import { TypesExtractorContent } from '../analyzer/extractor'
7
+ import { normalizeExternalFilePath } from '../analyzer/typedeps'
8
+
9
+ /**
10
+ * Generate the non-formatted type files which will be used by the SDK's route functions
11
+ * @param sdkTypes
12
+ * @returns
13
+ */
14
+ export function generateSdkTypeFiles(sdkTypes: TypesExtractorContent): Map<string, string> {
15
+ /** Generated type files */
16
+ const genFiles = new Map<string, string>()
17
+
18
+ // While analyzing the controllers' content, all types used by their methods have been extracted
19
+ // The list of these types is then provided to this function as the `sdkTypes` argument, allowing us
20
+ // to establish the list of all dependencies.
21
+ for (const [file, types] of sdkTypes) {
22
+ const out = []
23
+
24
+ /** List of imports in the current file */
25
+ const imports = new Map<string, string[]>()
26
+
27
+ // Iterate over all types in the current `file`
28
+ for (const extracted of types.values()) {
29
+ // Iterate over all of the extracted type's dependencies
30
+ for (const dep of extracted.dependencies) {
31
+ // If the dependency is from the same file, then we have nothing to do
32
+ if (dep.relativePath === file) {
33
+ continue
34
+ }
35
+
36
+ // Push the typename to the list of imports
37
+ let imported = imports.get(dep.relativePathNoExt)
38
+
39
+ if (!imported) {
40
+ imported = [dep.typename]
41
+ imports.set(dep.relativePathNoExt, imported)
42
+ }
43
+
44
+ if (!imported.includes(dep.typename)) {
45
+ imported.push(dep.typename)
46
+ }
47
+ }
48
+ }
49
+
50
+ // Generate an import statement for each imported type
51
+ out.push(
52
+ [...imports]
53
+ .map(([depFile, types]) => {
54
+ let depPath = path.relative(path.dirname(file), normalizeExternalFilePath(depFile)).replace(/\\/g, '/')
55
+ if (!depPath.includes('/')) depPath = './' + depPath
56
+ return `import type { ${types.join(', ')} } from "${
57
+ depPath.startsWith('./') || depPath.startsWith('../') ? depPath : './' + depPath
58
+ }"`
59
+ })
60
+ .join('\n')
61
+ )
62
+
63
+ // Add the extracted types' declaration, indented
64
+ for (const extracted of types.values()) {
65
+ out.push(extracted.content.replace(/^/gm, ' '))
66
+ }
67
+
68
+ // Generate the type file
69
+ genFiles.set(file, out.join('\n'))
70
+ }
71
+
72
+ return genFiles
73
+ }