vovk-cli 0.0.1-draft.99 → 0.0.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 (164) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +24 -16
  3. package/client-templates/jsBase/index.d.ts.ejs +21 -0
  4. package/client-templates/jsBase/index.js.ejs +18 -0
  5. package/client-templates/mixins/mixins.d.ts.ejs +64 -0
  6. package/client-templates/mixins/mixins.json.ejs +1 -0
  7. package/client-templates/openapiJs/openapi.d.ts.ejs +4 -0
  8. package/client-templates/openapiJs/openapi.js.ejs +4 -0
  9. package/client-templates/openapiJson/openapi.json.ejs +1 -0
  10. package/client-templates/openapiTs/openapi.ts.ejs +4 -0
  11. package/client-templates/packageJson/package.json.ejs +1 -0
  12. package/client-templates/readme/README.md.ejs +39 -0
  13. package/client-templates/schemaJs/schema.d.ts.ejs +10 -0
  14. package/client-templates/schemaJs/schema.js.ejs +29 -0
  15. package/client-templates/schemaJson/schema.json.ejs +1 -0
  16. package/client-templates/schemaTs/schema.ts.ejs +28 -0
  17. package/client-templates/tsBase/index.ts.ejs +27 -0
  18. package/dist/bundle/index.d.mts +8 -0
  19. package/dist/bundle/index.mjs +76 -0
  20. package/dist/dev/{diffSchema.d.mts → diffSegmentSchema.d.mts} +3 -3
  21. package/dist/dev/{diffSchema.mjs → diffSegmentSchema.mjs} +1 -1
  22. package/dist/dev/ensureSchemaFiles.d.mts +2 -2
  23. package/dist/dev/ensureSchemaFiles.mjs +16 -47
  24. package/dist/dev/index.d.mts +2 -0
  25. package/dist/dev/index.mjs +113 -64
  26. package/dist/dev/logDiffResult.d.mts +2 -2
  27. package/dist/dev/logDiffResult.mjs +6 -6
  28. package/dist/dev/writeMetaJson.d.mts +2 -0
  29. package/dist/dev/writeMetaJson.mjs +19 -0
  30. package/dist/dev/writeOneSegmentSchemaFile.d.mts +12 -0
  31. package/dist/dev/{writeOneSchemaFile.mjs → writeOneSegmentSchemaFile.mjs} +11 -8
  32. package/dist/generate/ensureClient.d.mts +2 -4
  33. package/dist/generate/ensureClient.mjs +26 -28
  34. package/dist/generate/generate.d.mts +13 -0
  35. package/dist/generate/generate.mjs +307 -0
  36. package/dist/generate/getClientTemplateFiles.d.mts +20 -0
  37. package/dist/generate/getClientTemplateFiles.mjs +85 -0
  38. package/dist/generate/getProjectFullSchema.d.mts +9 -0
  39. package/dist/generate/getProjectFullSchema.mjs +64 -0
  40. package/dist/generate/getTemplateClientImports.d.mts +18 -0
  41. package/dist/generate/getTemplateClientImports.mjs +36 -0
  42. package/dist/generate/index.d.mts +31 -11
  43. package/dist/generate/index.mjs +177 -85
  44. package/dist/generate/writeOneClientFile.d.mts +43 -0
  45. package/dist/generate/writeOneClientFile.mjs +150 -0
  46. package/dist/getProjectInfo/getConfig/getConfigAbsolutePaths.d.mts +5 -0
  47. package/dist/getProjectInfo/{getConfigAbsolutePaths.mjs → getConfig/getConfigAbsolutePaths.mjs} +4 -1
  48. package/dist/getProjectInfo/getConfig/getRelativeSrcRoot.d.mts +3 -0
  49. package/dist/getProjectInfo/{getRelativeSrcRoot.mjs → getConfig/getRelativeSrcRoot.mjs} +3 -3
  50. package/dist/getProjectInfo/getConfig/getTemplateDefs.d.mts +25 -0
  51. package/dist/getProjectInfo/getConfig/getTemplateDefs.mjs +168 -0
  52. package/dist/getProjectInfo/getConfig/getUserConfig.d.mts +9 -0
  53. package/dist/getProjectInfo/getConfig/getUserConfig.mjs +142 -0
  54. package/dist/getProjectInfo/getConfig/index.d.mts +15 -0
  55. package/dist/getProjectInfo/getConfig/index.mjs +92 -0
  56. package/dist/getProjectInfo/getMetaSchema.d.mts +4 -0
  57. package/dist/getProjectInfo/getMetaSchema.mjs +12 -0
  58. package/dist/getProjectInfo/index.d.mts +12 -16
  59. package/dist/getProjectInfo/index.mjs +23 -29
  60. package/dist/index.d.mts +3 -3
  61. package/dist/index.mjs +119 -40
  62. package/dist/init/checkTSConfigForExperimentalDecorators.d.mts +1 -1
  63. package/dist/init/checkTSConfigForExperimentalDecorators.mjs +2 -2
  64. package/dist/init/createConfig.d.mts +5 -3
  65. package/dist/init/createConfig.mjs +77 -27
  66. package/dist/init/index.d.mts +2 -2
  67. package/dist/init/index.mjs +118 -103
  68. package/dist/init/installDependencies.d.mts +8 -6
  69. package/dist/init/installDependencies.mjs +7 -5
  70. package/dist/init/logUpdateDependenciesError.d.mts +6 -6
  71. package/dist/init/logUpdateDependenciesError.mjs +8 -4
  72. package/dist/init/updateDependenciesWithoutInstalling.d.mts +3 -3
  73. package/dist/init/updateDependenciesWithoutInstalling.mjs +41 -11
  74. package/dist/init/updateNPMScripts.d.mts +7 -2
  75. package/dist/init/updateNPMScripts.mjs +9 -5
  76. package/dist/init/updateTypeScriptConfig.d.mts +4 -1
  77. package/dist/init/updateTypeScriptConfig.mjs +12 -8
  78. package/dist/new/addClassToSegmentCode.d.mts +1 -1
  79. package/dist/new/addClassToSegmentCode.mjs +3 -3
  80. package/dist/new/addCommonTerms.d.mts +1 -1
  81. package/dist/new/addCommonTerms.mjs +1 -1
  82. package/dist/new/index.d.mts +2 -1
  83. package/dist/new/index.mjs +6 -5
  84. package/dist/new/newModule.d.mts +5 -3
  85. package/dist/new/newModule.mjs +34 -27
  86. package/dist/new/newSegment.d.mts +5 -2
  87. package/dist/new/newSegment.mjs +23 -16
  88. package/dist/new/render.d.mts +6 -3
  89. package/dist/new/render.mjs +15 -14
  90. package/dist/types.d.mts +64 -61
  91. package/dist/utils/chalkHighlightThing.d.mts +1 -1
  92. package/dist/utils/chalkHighlightThing.mjs +1 -1
  93. package/dist/utils/compileJSONSchemaToTypeScriptType.d.mts +5 -0
  94. package/dist/utils/compileJSONSchemaToTypeScriptType.mjs +9 -0
  95. package/dist/utils/compileTs.d.mts +12 -0
  96. package/dist/utils/compileTs.mjs +263 -0
  97. package/dist/utils/debounceWithArgs.d.mts +3 -2
  98. package/dist/utils/debounceWithArgs.mjs +1 -1
  99. package/dist/utils/formatLoggedSegmentName.d.mts +3 -1
  100. package/dist/utils/formatLoggedSegmentName.mjs +4 -3
  101. package/dist/utils/generateFnName.d.mts +23 -0
  102. package/dist/utils/generateFnName.mjs +78 -0
  103. package/dist/utils/getAvailablePort.d.mts +1 -2
  104. package/dist/utils/getAvailablePort.mjs +1 -2
  105. package/dist/utils/getFileSystemEntryType.d.mts +1 -1
  106. package/dist/utils/getFileSystemEntryType.mjs +1 -1
  107. package/dist/utils/getLogger.d.mts +1 -1
  108. package/dist/utils/getLogger.mjs +1 -1
  109. package/dist/utils/getNPMPackageMetadata.d.mts +2 -3
  110. package/dist/utils/getNPMPackageMetadata.mjs +1 -1
  111. package/dist/utils/getPackageJson.d.mts +3 -0
  112. package/dist/utils/getPackageJson.mjs +23 -0
  113. package/dist/utils/getPublicModuleNameFromPath.d.mts +4 -0
  114. package/dist/utils/getPublicModuleNameFromPath.mjs +9 -0
  115. package/dist/utils/locateSegments.d.mts +12 -0
  116. package/dist/{locateSegments.mjs → utils/locateSegments.mjs} +14 -7
  117. package/dist/utils/normalizeOpenAPIMixin.d.mts +15 -0
  118. package/dist/utils/normalizeOpenAPIMixin.mjs +96 -0
  119. package/dist/utils/pickSegmentFullSchema.d.mts +3 -0
  120. package/dist/utils/pickSegmentFullSchema.mjs +15 -0
  121. package/dist/utils/prettify.d.mts +1 -1
  122. package/dist/utils/prettify.mjs +1 -1
  123. package/dist/utils/removeUnlistedDirectories.d.mts +9 -0
  124. package/dist/utils/removeUnlistedDirectories.mjs +60 -0
  125. package/dist/utils/resolveAbsoluteModulePath.d.mts +2 -0
  126. package/dist/utils/resolveAbsoluteModulePath.mjs +32 -0
  127. package/dist/utils/updateConfigProperty.d.mts +2 -0
  128. package/dist/utils/updateConfigProperty.mjs +132 -0
  129. package/module-templates/arktype/controller.ts.ejs +90 -0
  130. package/module-templates/type/controller.ts.ejs +80 -0
  131. package/module-templates/type/service.ts.ejs +43 -0
  132. package/module-templates/valibot/controller.ts.ejs +91 -0
  133. package/module-templates/zod/controller.ts.ejs +98 -0
  134. package/package.json +50 -25
  135. package/client-templates/main/main.cjs.ejs +0 -15
  136. package/client-templates/main/main.d.cts.ejs +0 -14
  137. package/client-templates/module/module.d.mts.ejs +0 -14
  138. package/client-templates/module/module.mjs.ejs +0 -24
  139. package/client-templates/python/__init__.py +0 -276
  140. package/client-templates/ts/index.ts.ejs +0 -25
  141. package/dist/dev/isSchemaEmpty.d.mts +0 -2
  142. package/dist/dev/isSchemaEmpty.mjs +0 -4
  143. package/dist/dev/writeOneSchemaFile.d.mts +0 -12
  144. package/dist/generate/getClientTemplates.d.mts +0 -16
  145. package/dist/generate/getClientTemplates.mjs +0 -42
  146. package/dist/getProjectInfo/getConfig.d.mts +0 -11
  147. package/dist/getProjectInfo/getConfig.mjs +0 -35
  148. package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +0 -4
  149. package/dist/getProjectInfo/getRelativeSrcRoot.d.mts +0 -3
  150. package/dist/getProjectInfo/getUserConfig.d.mts +0 -9
  151. package/dist/getProjectInfo/getUserConfig.mjs +0 -27
  152. package/dist/getProjectInfo/importUncachedModule.d.mts +0 -3
  153. package/dist/getProjectInfo/importUncachedModule.mjs +0 -40
  154. package/dist/getProjectInfo/importUncachedModuleWorker.d.mts +0 -1
  155. package/dist/getProjectInfo/importUncachedModuleWorker.mjs +0 -25
  156. package/dist/init/getTemplateFilesFromPackage.d.mts +0 -7
  157. package/dist/init/getTemplateFilesFromPackage.mjs +0 -59
  158. package/dist/initProgram.d.mts +0 -2
  159. package/dist/initProgram.mjs +0 -22
  160. package/dist/locateSegments.d.mts +0 -11
  161. package/dist/postinstall.d.mts +0 -1
  162. package/dist/postinstall.mjs +0 -21
  163. package/templates/controller.ejs +0 -50
  164. package/templates/service.ejs +0 -27
@@ -0,0 +1,307 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ import matter from 'gray-matter';
4
+ import _ from 'lodash';
5
+ import { openAPIToVovkSchema, vovkSchemaToOpenAPI } from 'vovk/internal';
6
+ import { getClientTemplateFiles } from './getClientTemplateFiles.mjs';
7
+ import { chalkHighlightThing } from '../utils/chalkHighlightThing.mjs';
8
+ import { pickSegmentFullSchema } from '../utils/pickSegmentFullSchema.mjs';
9
+ import { removeUnlistedDirectories } from '../utils/removeUnlistedDirectories.mjs';
10
+ import { writeOneClientFile, normalizeOutTemplatePath } from './writeOneClientFile.mjs';
11
+ import { ROOT_SEGMENT_FILE_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
12
+ import { getTsconfig } from 'get-tsconfig';
13
+ import { normalizeOpenAPIMixin } from '../utils/normalizeOpenAPIMixin.mjs';
14
+ import { BuiltInTemplateName } from '../getProjectInfo/getConfig/getTemplateDefs.mjs';
15
+ const getIncludedSegmentNames = (config, fullSchema, configKey, cliGenerateOptions) => {
16
+ const segments = Object.values(fullSchema.segments);
17
+ const includeSegments = cliGenerateOptions?.[configKey === 'segmentedClient' ? 'segmentedIncludeSegments' : 'composedIncludeSegments'] ??
18
+ config[configKey].includeSegments;
19
+ const excludeSegments = cliGenerateOptions?.[configKey === 'segmentedClient' ? 'segmentedExcludeSegments' : 'composedExcludeSegments'] ??
20
+ config[configKey].excludeSegments;
21
+ if (includeSegments?.length && excludeSegments?.length) {
22
+ throw new Error(`Both includeSegments and excludeSegments are set in "${configKey}" config. Please use only one of them.`);
23
+ }
24
+ const includedSegmentNames = Array.isArray(includeSegments) && includeSegments.length
25
+ ? includeSegments.map((segmentName) => {
26
+ const segment = segments.find(({ segmentName: sName }) => sName === segmentName);
27
+ if (!segment) {
28
+ throw new Error(`Segment "${segmentName}" not found in the config for "${configKey}"`);
29
+ }
30
+ return segment.segmentName;
31
+ })
32
+ : Array.isArray(excludeSegments) && excludeSegments.length // TODO: Warn if excludeSegments includes a segment name that is not listed at segments
33
+ ? segments
34
+ .filter(({ segmentName }) => !excludeSegments?.includes(segmentName))
35
+ .map(({ segmentName }) => segmentName)
36
+ : segments.map(({ segmentName }) => segmentName);
37
+ return includedSegmentNames;
38
+ };
39
+ function logClientGenerationResults({ results, log, isEnsuringClient = false, forceNothingWrittenLog = false, clientType = 'Composed', startTime, fromTemplates, }) {
40
+ const writtenResults = results.filter(({ written }) => written);
41
+ const origins = _.uniq(results.map((result) => result.origin).filter((origin) => !!origin));
42
+ const duration = Date.now() - startTime;
43
+ const groupedByDir = _.groupBy(writtenResults, ({ outAbsoluteDir }) => outAbsoluteDir);
44
+ const logOrDebug = forceNothingWrittenLog ? log.info : log.debug;
45
+ if (writtenResults.length) {
46
+ for (const [outAbsoluteDir, dirResults] of Object.entries(groupedByDir)) {
47
+ const templateNames = _.uniq(dirResults.map(({ templateName }) => templateName));
48
+ log.info(`${clientType} client${isEnsuringClient ? ' placeholder' : ''} is generated to ${chalkHighlightThing(normalizeOutTemplatePath(outAbsoluteDir, dirResults[0].package))} from template${templateNames.length !== 1 ? 's' : ''} ${chalkHighlightThing(templateNames.map((s) => `"${s}"`).join(', '))}${origins.length && !isEnsuringClient ? ` with origin${origins.length !== 1 ? 's' : ''} ${chalkHighlightThing(origins.join(', '))}` : ''} in ${duration}ms`);
49
+ }
50
+ }
51
+ else if (fromTemplates.length) {
52
+ if (!writtenResults.length) {
53
+ logOrDebug(`${clientType} client${isEnsuringClient ? ' placeholder' : ''} is up to date (${duration}ms)`);
54
+ }
55
+ else if (!isEnsuringClient) {
56
+ for (const [outAbsoluteDir, dirResults] of Object.entries(groupedByDir)) {
57
+ const templateNames = _.uniq(dirResults.map(({ templateName }) => templateName));
58
+ logOrDebug(`${clientType} client that was generated to ${chalkHighlightThing(normalizeOutTemplatePath(outAbsoluteDir, dirResults[0].package))} from template${templateNames.length !== 1 ? 's' : ''} ${chalkHighlightThing(templateNames.map((s) => `"${s}"`).join(', '))} is up to date and doesn't need to be regenerated (${duration}ms)`);
59
+ }
60
+ }
61
+ }
62
+ else {
63
+ logOrDebug(`${clientType} client${isEnsuringClient ? ' placeholder' : ''} is not generated because no files were written (${duration}ms)`);
64
+ }
65
+ }
66
+ const cliOptionsToOpenAPIMixins = ({ openapiGetMethodName, openapiGetModuleName, openapiRootUrl, openapiSpec, openapiFallback, openapiMixinName, }) => {
67
+ return Object.fromEntries((openapiSpec?.map((spec, i) => {
68
+ return {
69
+ source: spec.startsWith('http://') || spec.startsWith('https://')
70
+ ? { url: spec, fallback: openapiFallback?.[i] }
71
+ : { file: spec },
72
+ apiRoot: openapiRootUrl?.[i] ?? '',
73
+ getModuleName: openapiGetModuleName?.[i] ?? 'api',
74
+ getMethodName: openapiGetMethodName?.[i] ?? 'auto',
75
+ mixinName: openapiMixinName?.[i] ?? `mixin${i > 0 ? i + 1 : ''}`,
76
+ };
77
+ }) || []).map(({ source, apiRoot, getModuleName, getMethodName, mixinName }) => [
78
+ mixinName,
79
+ {
80
+ source,
81
+ apiRoot,
82
+ getModuleName,
83
+ getMethodName,
84
+ mixinName,
85
+ },
86
+ ]));
87
+ };
88
+ export async function generate({ isEnsuringClient = false, isBundle = false, projectInfo, forceNothingWrittenLog, fullSchema, locatedSegments, cliGenerateOptions, }) {
89
+ fullSchema = {
90
+ ...fullSchema,
91
+ // sort segments by name to avoid unnecessary rendering
92
+ segments: Object.fromEntries(Object.entries(fullSchema.segments)
93
+ .sort(([a], [b]) => a.localeCompare(b))
94
+ // preserve original object, so segments can be extended
95
+ .map((segment) => ({ ...segment }))),
96
+ };
97
+ const { config, cwd, log, srcRoot, vovkCliPackage, packageJson: projectPackageJson } = projectInfo;
98
+ Object.entries(config.outputConfig.segments ?? {})
99
+ .filter(([, segmentConfig]) => segmentConfig.openAPIMixin)
100
+ .forEach(([segmentName, segmentConfig]) => {
101
+ fullSchema.segments = {
102
+ ...fullSchema.segments,
103
+ // biome-ignore lint/style/noNonNullAssertion: TODO
104
+ [segmentName]: openAPIToVovkSchema({ ...segmentConfig.openAPIMixin, segmentName }).segments[segmentName],
105
+ };
106
+ });
107
+ const cliMixins = cliOptionsToOpenAPIMixins(cliGenerateOptions ?? {});
108
+ fullSchema.segments = {
109
+ ...fullSchema.segments,
110
+ ...Object.fromEntries(await Promise.all(Object.entries(cliMixins).map(async ([mixinName, mixinModule]) => {
111
+ return [
112
+ mixinName,
113
+ openAPIToVovkSchema({
114
+ segmentName: mixinName,
115
+ ...(await normalizeOpenAPIMixin({ mixinModule, log })),
116
+ }).segments[mixinName],
117
+ ];
118
+ }))),
119
+ };
120
+ const moduleResolution = await getTsconfig(cwd)?.config?.compilerOptions?.moduleResolution?.toLowerCase();
121
+ const isNodeNextResolution = !moduleResolution || ['node16', 'nodenext'].includes(moduleResolution ?? '');
122
+ const isVovkProject = !!srcRoot;
123
+ const isComposedEnabled = cliGenerateOptions?.composedOnly ||
124
+ !!cliGenerateOptions?.composedFrom ||
125
+ !!cliGenerateOptions?.composedOut ||
126
+ (config.composedClient?.enabled && !cliGenerateOptions?.segmentedOnly);
127
+ const isSegmentedEnabled = cliGenerateOptions?.segmentedOnly ||
128
+ !!cliGenerateOptions?.segmentedFrom ||
129
+ !!cliGenerateOptions?.segmentedOut ||
130
+ (config.segmentedClient?.enabled && !cliGenerateOptions?.composedOnly);
131
+ if (isComposedEnabled) {
132
+ const now = Date.now();
133
+ const segmentNames = getIncludedSegmentNames(config, fullSchema, 'composedClient', cliGenerateOptions);
134
+ const { templateFiles: composedClientTemplateFiles, fromTemplates } = await getClientTemplateFiles({
135
+ config,
136
+ cwd,
137
+ log,
138
+ cliGenerateOptions,
139
+ configKey: 'composedClient',
140
+ });
141
+ const composedClientResults = await Promise.all(composedClientTemplateFiles.map(async (clientTemplateFile) => {
142
+ const { templateFilePath, templateName, templateDef, outCwdRelativeDir } = clientTemplateFile;
143
+ const templateContent = await fs.readFile(templateFilePath, 'utf-8');
144
+ const matterResult = templateFilePath.endsWith('.ejs')
145
+ ? matter(templateContent)
146
+ : { data: { imports: [] }, content: templateContent };
147
+ const { package: packageJson, readme, origin, samples, reExports, openAPIObject, } = vovkSchemaToOpenAPI({
148
+ config: projectInfo.config,
149
+ rootEntry: config.rootEntry,
150
+ schema: fullSchema,
151
+ outputConfigs: [config.composedClient.outputConfig ?? {}, templateDef.outputConfig ?? {}],
152
+ forceOutputConfigs: [{ origin: cliGenerateOptions?.origin }],
153
+ projectPackageJson,
154
+ isBundle,
155
+ segmentName: null,
156
+ });
157
+ const composedFullSchema = pickSegmentFullSchema(fullSchema, segmentNames);
158
+ const hasMixins = Object.values(composedFullSchema.segments).some((segment) => segment.segmentType === 'mixin');
159
+ if (templateName === BuiltInTemplateName.mixins && !hasMixins) {
160
+ return null;
161
+ }
162
+ const { written } = await writeOneClientFile({
163
+ cwd,
164
+ projectInfo,
165
+ clientTemplateFile,
166
+ fullSchema: composedFullSchema,
167
+ prettifyClient: cliGenerateOptions?.prettify ?? config.composedClient.prettifyClient,
168
+ segmentName: null,
169
+ templateContent,
170
+ matterResult,
171
+ openAPIObject,
172
+ package: packageJson,
173
+ readme,
174
+ samples,
175
+ reExports,
176
+ isEnsuringClient,
177
+ outCwdRelativeDir,
178
+ templateDef,
179
+ locatedSegments,
180
+ isNodeNextResolution,
181
+ hasMixins,
182
+ isVovkProject,
183
+ vovkCliPackage,
184
+ isBundle,
185
+ origin,
186
+ configKey: 'composedClient',
187
+ cliSchemaPath: cliGenerateOptions?.schemaPath ?? null,
188
+ projectConfig: config,
189
+ });
190
+ const outAbsoluteDir = path.resolve(cwd, outCwdRelativeDir);
191
+ return {
192
+ written,
193
+ templateName,
194
+ outAbsoluteDir,
195
+ package: packageJson,
196
+ origin,
197
+ };
198
+ }));
199
+ if (composedClientTemplateFiles.length) {
200
+ logClientGenerationResults({
201
+ results: composedClientResults.filter((result) => !!result),
202
+ log,
203
+ isEnsuringClient,
204
+ forceNothingWrittenLog,
205
+ clientType: 'Composed',
206
+ startTime: now,
207
+ fromTemplates,
208
+ });
209
+ }
210
+ else {
211
+ log.warn('No composed client template files found. Skipping composed client generation.');
212
+ }
213
+ }
214
+ if (isSegmentedEnabled) {
215
+ const now = Date.now();
216
+ const segmentNames = getIncludedSegmentNames(config, fullSchema, 'segmentedClient', cliGenerateOptions);
217
+ const { templateFiles: segmentedClientTemplateFiles, fromTemplates } = await getClientTemplateFiles({
218
+ config,
219
+ cwd,
220
+ log,
221
+ cliGenerateOptions,
222
+ configKey: 'segmentedClient',
223
+ });
224
+ const segmentedClientResults = await Promise.all(segmentedClientTemplateFiles.map(async (clientTemplateFile) => {
225
+ const { templateFilePath, templateName, templateDef, outCwdRelativeDir } = clientTemplateFile;
226
+ const templateContent = await fs.readFile(templateFilePath, 'utf-8');
227
+ const matterResult = templateFilePath.endsWith('.ejs')
228
+ ? matter(templateContent)
229
+ : { data: { imports: [] }, content: templateContent };
230
+ const results = await Promise.all(segmentNames.map(async (segmentName) => {
231
+ const segmentedFullSchema = pickSegmentFullSchema(fullSchema, [segmentName]);
232
+ const hasMixins = Object.values(segmentedFullSchema.segments).some((segment) => segment.segmentType === 'mixin');
233
+ if (templateName === BuiltInTemplateName.mixins && !hasMixins) {
234
+ return null;
235
+ }
236
+ const { package: packageJson, readme, origin, samples, reExports, openAPIObject, } = vovkSchemaToOpenAPI({
237
+ config: projectInfo.config,
238
+ schema: fullSchema,
239
+ rootEntry: config.rootEntry,
240
+ segmentName,
241
+ outputConfigs: [config.segmentedClient.outputConfig ?? {}, templateDef.outputConfig ?? {}],
242
+ forceOutputConfigs: [{ origin: cliGenerateOptions?.origin }],
243
+ isBundle,
244
+ projectPackageJson,
245
+ });
246
+ const { written } = await writeOneClientFile({
247
+ cwd,
248
+ projectInfo,
249
+ clientTemplateFile,
250
+ fullSchema: segmentedFullSchema,
251
+ prettifyClient: cliGenerateOptions?.prettify ?? config.segmentedClient.prettifyClient,
252
+ segmentName,
253
+ templateContent,
254
+ matterResult,
255
+ openAPIObject,
256
+ package: packageJson,
257
+ readme,
258
+ samples,
259
+ reExports,
260
+ isEnsuringClient,
261
+ outCwdRelativeDir,
262
+ templateDef,
263
+ locatedSegments,
264
+ isNodeNextResolution,
265
+ hasMixins,
266
+ isVovkProject,
267
+ vovkCliPackage,
268
+ isBundle,
269
+ origin,
270
+ configKey: 'segmentedClient',
271
+ cliSchemaPath: cliGenerateOptions?.schemaPath ?? null,
272
+ projectConfig: config,
273
+ });
274
+ return {
275
+ written,
276
+ templateName,
277
+ package: packageJson,
278
+ origin,
279
+ };
280
+ }));
281
+ const outAbsoluteDir = path.resolve(cwd, outCwdRelativeDir);
282
+ // Remove unlisted directories in the output directory
283
+ await removeUnlistedDirectories(outAbsoluteDir, segmentNames.map((s) => s || ROOT_SEGMENT_FILE_NAME));
284
+ return {
285
+ written: results.filter((result) => !!result).some(({ written }) => written),
286
+ templateName,
287
+ outAbsoluteDir,
288
+ package: results[0]?.package || {}, // TODO: Might be wrong in Python segmented client (unknown use case)
289
+ origin: results[0]?.origin || '',
290
+ };
291
+ }));
292
+ if (segmentedClientTemplateFiles.length) {
293
+ logClientGenerationResults({
294
+ results: segmentedClientResults,
295
+ log,
296
+ isEnsuringClient,
297
+ forceNothingWrittenLog,
298
+ clientType: 'Segmented',
299
+ startTime: now,
300
+ fromTemplates,
301
+ });
302
+ }
303
+ else {
304
+ log.warn('No segmented client template files found. Skipping segmented client generation.');
305
+ }
306
+ }
307
+ }
@@ -0,0 +1,20 @@
1
+ import type { VovkStrictConfig } from 'vovk/internal';
2
+ import type { ProjectInfo } from '../getProjectInfo/index.mjs';
3
+ import type { GenerateOptions } from '../types.mjs';
4
+ export interface ClientTemplateFile {
5
+ templateName: string;
6
+ templateFilePath: string;
7
+ relativeDir: string;
8
+ outCwdRelativeDir: string;
9
+ templateDef: VovkStrictConfig['clientTemplateDefs'][string];
10
+ }
11
+ export declare function getClientTemplateFiles({ config, cwd, log, configKey, cliGenerateOptions, }: {
12
+ config: VovkStrictConfig;
13
+ cwd: string;
14
+ log: ProjectInfo['log'];
15
+ configKey: 'composedClient' | 'segmentedClient';
16
+ cliGenerateOptions?: GenerateOptions;
17
+ }): Promise<{
18
+ fromTemplates: string[];
19
+ templateFiles: ClientTemplateFile[];
20
+ }>;
@@ -0,0 +1,85 @@
1
+ import path from 'node:path';
2
+ import { glob } from 'glob';
3
+ import { resolveAbsoluteModulePath } from '../utils/resolveAbsoluteModulePath.mjs';
4
+ import { getFileSystemEntryType, FileSystemEntryType } from '../utils/getFileSystemEntryType.mjs';
5
+ import { getPublicModuleNameFromPath } from '../utils/getPublicModuleNameFromPath.mjs';
6
+ import omit from 'lodash/omit.js';
7
+ import merge from 'lodash/merge.js';
8
+ export async function getClientTemplateFiles({ config, cwd, log, configKey, cliGenerateOptions, }) {
9
+ const usedTemplateDefs = {};
10
+ const fromTemplates = configKey === 'composedClient'
11
+ ? cliGenerateOptions?.composedFrom || cliGenerateOptions?.segmentedFrom
12
+ ? (cliGenerateOptions?.composedFrom ?? [])
13
+ : config.composedClient.fromTemplates
14
+ : cliGenerateOptions?.composedFrom || cliGenerateOptions?.segmentedFrom
15
+ ? (cliGenerateOptions?.segmentedFrom ?? [])
16
+ : config.segmentedClient.fromTemplates;
17
+ const cliOutDir = configKey === 'composedClient' ? cliGenerateOptions?.composedOut : cliGenerateOptions?.segmentedOut;
18
+ const configOutDir = config[configKey].outDir;
19
+ for (const templateName of fromTemplates) {
20
+ if (!(templateName in config.clientTemplateDefs)) {
21
+ throw new Error(`Unknown template name: ${templateName}`);
22
+ }
23
+ usedTemplateDefs[templateName] = config.clientTemplateDefs[templateName];
24
+ }
25
+ const templateFiles = [];
26
+ const entries = Object.entries(usedTemplateDefs);
27
+ for (let i = 0; i < entries.length; i++) {
28
+ const [templateName, templateDef, forceOutCwdRelativeDir] = entries[i];
29
+ const templateAbsolutePath = templateDef.templatePath
30
+ ? resolveAbsoluteModulePath(templateDef.templatePath, cwd)
31
+ : null;
32
+ const entryType = templateAbsolutePath ? await getFileSystemEntryType(templateAbsolutePath) : null;
33
+ if (templateAbsolutePath && !entryType) {
34
+ const { moduleName } = templateDef.templatePath ? getPublicModuleNameFromPath(templateDef.templatePath) : {};
35
+ if (moduleName) {
36
+ throw new Error(`Unable to locate template path "${templateDef.templatePath}" resolved as "${templateAbsolutePath}". You may need to install the package "${moduleName}" first.`);
37
+ }
38
+ throw new Error(`Unable to locate template path "${templateDef.templatePath}" resolved as "${templateAbsolutePath}"`);
39
+ }
40
+ const defOutDir = configKey === 'composedClient' ? templateDef.composedClient?.outDir : templateDef.segmentedClient?.outDir;
41
+ let files;
42
+ const outCwdRelativeDir = forceOutCwdRelativeDir ?? cliOutDir ?? defOutDir ?? configOutDir;
43
+ if (templateAbsolutePath) {
44
+ if (entryType === FileSystemEntryType.FILE) {
45
+ files = [{ filePath: templateAbsolutePath, isSingleFileTemplate: true }];
46
+ }
47
+ else {
48
+ const globPath = path.join(templateAbsolutePath, '**/*.*');
49
+ files = (await glob(globPath)).map((filePath) => ({
50
+ filePath,
51
+ isSingleFileTemplate: false,
52
+ }));
53
+ }
54
+ if (files.length === 0) {
55
+ log.error(`Template "${templateAbsolutePath}" not found`);
56
+ continue;
57
+ }
58
+ for (const { filePath, isSingleFileTemplate } of files) {
59
+ templateFiles.push({
60
+ templateName,
61
+ templateFilePath: filePath,
62
+ relativeDir: path.relative(isSingleFileTemplate ? path.dirname(templateAbsolutePath) : templateAbsolutePath, `${path.dirname(filePath)}/`),
63
+ outCwdRelativeDir,
64
+ templateDef,
65
+ });
66
+ }
67
+ }
68
+ if (templateDef.requires) {
69
+ for (const [tName, reqRelativeDir] of Object.entries(templateDef.requires)) {
70
+ let def = config.clientTemplateDefs[tName];
71
+ if (!def) {
72
+ throw new Error(`Template "${tName}" required by "${templateName}" not found`);
73
+ }
74
+ def = {
75
+ ...def,
76
+ outputConfig: merge({}, templateDef?.outputConfig, def.outputConfig),
77
+ composedClient: merge(omit(templateDef?.composedClient ?? {}, ['outDir']), def.composedClient),
78
+ segmentedClient: merge(omit(templateDef?.segmentedClient ?? {}, ['outDir']), def.segmentedClient),
79
+ };
80
+ entries.push([tName, def, path.join(outCwdRelativeDir, reqRelativeDir)]);
81
+ }
82
+ }
83
+ }
84
+ return { fromTemplates, templateFiles };
85
+ }
@@ -0,0 +1,9 @@
1
+ import type { VovkSchema } from 'vovk';
2
+ import type { ProjectInfo } from '../getProjectInfo/index.mjs';
3
+ import { type VovkStrictConfig } from 'vovk/internal';
4
+ export declare function getProjectFullSchema({ schemaOutAbsolutePath, isNextInstalled, log, config, }: {
5
+ schemaOutAbsolutePath: string;
6
+ isNextInstalled: boolean;
7
+ log: ProjectInfo['log'];
8
+ config: VovkStrictConfig;
9
+ }): Promise<VovkSchema>;
@@ -0,0 +1,64 @@
1
+ import { readFile, access } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { glob } from 'glob';
4
+ import { META_FILE_NAME, ROOT_SEGMENT_FILE_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
5
+ import { getMetaSchema } from '../getProjectInfo/getMetaSchema.mjs';
6
+ import { deepExtend, VovkSchemaIdEnum } from 'vovk/internal';
7
+ export async function getProjectFullSchema({ schemaOutAbsolutePath, isNextInstalled, log, config, }) {
8
+ const result = {
9
+ $schema: VovkSchemaIdEnum.SCHEMA,
10
+ segments: {},
11
+ meta: getMetaSchema({
12
+ config,
13
+ }),
14
+ };
15
+ const isEmptyLogOrWarn = isNextInstalled ? log.warn : log.debug;
16
+ // Handle config.json
17
+ const metaPath = path.join(schemaOutAbsolutePath, `${META_FILE_NAME}.json`);
18
+ try {
19
+ const metaContent = await readFile(metaPath, 'utf-8');
20
+ const fromFileMeta = JSON.parse(metaContent);
21
+ result.meta = deepExtend({}, result.meta, fromFileMeta);
22
+ }
23
+ catch {
24
+ isEmptyLogOrWarn(`${META_FILE_NAME}.json not found at ${metaPath}. Using empty meta as fallback.`);
25
+ }
26
+ // Handle segments directory
27
+ const segmentsDir = path.join(schemaOutAbsolutePath);
28
+ try {
29
+ await access(segmentsDir); // Check if directory exists
30
+ // Use glob to get all JSON files recursively
31
+ const files = await glob(`${segmentsDir}/**/*.json`);
32
+ const filePaths = [];
33
+ for await (const filePath of files) {
34
+ if (path.basename(filePath) === `${META_FILE_NAME}.json`)
35
+ continue; // Skip _meta.json
36
+ filePaths.push(filePath);
37
+ }
38
+ // Process each JSON file
39
+ for (const filePath of filePaths.toSorted()) {
40
+ try {
41
+ const content = await readFile(filePath, 'utf-8');
42
+ const jsonData = JSON.parse(content);
43
+ // Get relative path from segments directory and convert to key
44
+ let relativePath = path
45
+ .relative(segmentsDir, filePath)
46
+ .replace(/\.json$/, '') // Remove .json extension
47
+ .replace(/\\/g, '/'); // Normalize to forward slashes
48
+ // Special case for _root.json
49
+ if (path.basename(filePath) === `${ROOT_SEGMENT_FILE_NAME}.json` && path.dirname(filePath) === segmentsDir) {
50
+ relativePath = '';
51
+ }
52
+ result.segments[relativePath] = jsonData;
53
+ }
54
+ catch (error) {
55
+ log.warn(`Failed to process file ${filePath}: ${error}`);
56
+ }
57
+ }
58
+ }
59
+ catch {
60
+ isEmptyLogOrWarn(`Segments directory not found at ${segmentsDir}. Using empty segments as fallback.`);
61
+ result.segments = {};
62
+ }
63
+ return result;
64
+ }
@@ -0,0 +1,18 @@
1
+ import type { VovkSchema } from 'vovk';
2
+ import { type VovkStrictConfig } from 'vovk/internal';
3
+ export type ClientImports = {
4
+ fetcher: string;
5
+ validateOnClient: string | null;
6
+ createRPC: string;
7
+ };
8
+ export declare function getTemplateClientImports({ config, fullSchema, outCwdRelativeDir, segmentName, isBundle, outputConfigs, }: {
9
+ config: VovkStrictConfig;
10
+ fullSchema: VovkSchema;
11
+ outCwdRelativeDir: string;
12
+ segmentName: string | null;
13
+ isBundle: boolean;
14
+ outputConfigs: VovkStrictConfig['outputConfig'][];
15
+ }): {
16
+ composedClient: ClientImports;
17
+ segmentedClient: Record<string, ClientImports>;
18
+ };
@@ -0,0 +1,36 @@
1
+ import path from 'node:path';
2
+ import { resolveGeneratorConfigValues } from 'vovk/internal';
3
+ import { ROOT_SEGMENT_FILE_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
4
+ export function getTemplateClientImports({ config, fullSchema, outCwdRelativeDir, segmentName, isBundle, outputConfigs, }) {
5
+ const { imports: configImports } = resolveGeneratorConfigValues({
6
+ config,
7
+ segmentName,
8
+ isBundle,
9
+ projectPackageJson: undefined,
10
+ outputConfigs,
11
+ });
12
+ const imports = {
13
+ fetcher: configImports?.fetcher ?? 'vovk/fetcher',
14
+ validateOnClient: configImports?.validateOnClient ?? null,
15
+ createRPC: configImports?.createRPC ?? 'vovk/createRPC',
16
+ };
17
+ const getImportPath = (p, s = '') => p.startsWith('.') ? path.relative(path.join(outCwdRelativeDir, s), p) : p;
18
+ const clientImports = {
19
+ composedClient: {
20
+ fetcher: getImportPath(imports.fetcher),
21
+ createRPC: getImportPath(imports.createRPC),
22
+ validateOnClient: imports.validateOnClient ? getImportPath(imports.validateOnClient) : null,
23
+ },
24
+ segmentedClient: Object.fromEntries(Object.values(fullSchema.segments).map((segment) => [
25
+ segment.segmentName,
26
+ {
27
+ fetcher: getImportPath(imports.fetcher, segment.segmentName || ROOT_SEGMENT_FILE_NAME),
28
+ createRPC: getImportPath(imports.createRPC, segment.segmentName || ROOT_SEGMENT_FILE_NAME),
29
+ validateOnClient: imports.validateOnClient
30
+ ? getImportPath(imports.validateOnClient, segment.segmentName || ROOT_SEGMENT_FILE_NAME)
31
+ : null,
32
+ },
33
+ ])),
34
+ };
35
+ return clientImports;
36
+ }
@@ -1,13 +1,33 @@
1
1
  import type { VovkSchema } from 'vovk';
2
+ import type { GenerateOptions } from '../types.mjs';
2
3
  import type { ProjectInfo } from '../getProjectInfo/index.mjs';
3
- import type { Segment } from '../locateSegments.mjs';
4
- import { GenerateOptions } from '../types.mjs';
5
- export default function generate({ projectInfo, segments, segmentsSchema, forceNothingWrittenLog, templates, prettify: prettifyClient, fullSchema, }: {
6
- projectInfo: ProjectInfo;
7
- segments: Segment[];
8
- segmentsSchema: Record<string, VovkSchema>;
9
- forceNothingWrittenLog?: boolean;
10
- } & Pick<GenerateOptions, 'templates' | 'prettify' | 'fullSchema'>): Promise<{
11
- written: boolean;
12
- path: string;
13
- }>;
4
+ export declare class VovkGenerate {
5
+ #private;
6
+ constructor({ cliGenerateOptions, projectInfo, forceNothingWrittenLog, }: {
7
+ cliGenerateOptions: GenerateOptions;
8
+ projectInfo: ProjectInfo;
9
+ forceNothingWrittenLog?: boolean;
10
+ });
11
+ start(): void;
12
+ generate(): Promise<void>;
13
+ getFullSchema(): Promise<VovkSchema>;
14
+ watch({ throttleDelay }: {
15
+ throttleDelay: number;
16
+ }): void;
17
+ watchSchema({ schemaPath, throttleDelay }: {
18
+ schemaPath: string;
19
+ throttleDelay: number;
20
+ }): void;
21
+ watchOpenApiSpec({ openApiSpec, throttleDelay }: {
22
+ openApiSpec: string[];
23
+ throttleDelay: number;
24
+ }): void;
25
+ watchOpenApiSpecLocal({ openApiSpecPaths, throttleDelay }: {
26
+ openApiSpecPaths: string[];
27
+ throttleDelay: number;
28
+ }): void;
29
+ watchOpenApiSpecRemote({ openApiSpecUrl, throttleDelay }: {
30
+ openApiSpecUrl: string;
31
+ throttleDelay: number;
32
+ }): void;
33
+ }