vovk-cli 0.0.1-draft.33 → 0.0.1-draft.330

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