vovk-cli 0.0.1-draft.39 → 0.0.1-draft.392

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