vovk-cli 0.0.1-draft.98 → 0.0.1

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 +1 -1
  23. package/dist/dev/ensureSchemaFiles.mjs +15 -46
  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 +306 -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 +32 -12
  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 +143 -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 +117 -102
  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 +2 -2
  73. package/dist/init/updateDependenciesWithoutInstalling.mjs +41 -11
  74. package/dist/init/updateNPMScripts.d.mts +6 -1
  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 +31 -25
  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 +261 -0
  97. package/dist/utils/debounceWithArgs.d.mts +1 -2
  98. package/dist/utils/debounceWithArgs.mjs +2 -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 +76 -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 +1 -1
  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
@@ -1,34 +1,48 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import { VovkSchemaIdEnum } from 'vovk/internal';
3
4
  import * as chokidar from 'chokidar';
4
5
  import { Agent, setGlobalDispatcher } from 'undici';
5
6
  import keyBy from 'lodash/keyBy.js';
6
7
  import capitalize from 'lodash/capitalize.js';
7
8
  import debounce from 'lodash/debounce.js';
8
9
  import once from 'lodash/once.js';
9
- import { debouncedEnsureSchemaFiles } from './ensureSchemaFiles.mjs';
10
- import writeOneSchemaFile, { JSON_DIR_NAME } from './writeOneSchemaFile.mjs';
11
- import logDiffResult from './logDiffResult.mjs';
12
- import ensureClient from '../generate/ensureClient.mjs';
13
- import getProjectInfo from '../getProjectInfo/index.mjs';
14
- import generate from '../generate/index.mjs';
15
- import locateSegments from '../locateSegments.mjs';
16
- import debounceWithArgs from '../utils/debounceWithArgs.mjs';
17
- import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
18
- import isSchemaEmpty from './isSchemaEmpty.mjs';
10
+ import isEmpty from 'lodash/isEmpty.js';
11
+ import { ensureSchemaFiles, debouncedEnsureSchemaFiles } from './ensureSchemaFiles.mjs';
12
+ import { writeOneSegmentSchemaFile } from './writeOneSegmentSchemaFile.mjs';
13
+ import { logDiffResult } from './logDiffResult.mjs';
14
+ import { ensureClient } from '../generate/ensureClient.mjs';
15
+ import { getProjectInfo } from '../getProjectInfo/index.mjs';
16
+ import { generate } from '../generate/generate.mjs';
17
+ import { locateSegments } from '../utils/locateSegments.mjs';
18
+ import { debounceWithArgs } from '../utils/debounceWithArgs.mjs';
19
+ import { formatLoggedSegmentName } from '../utils/formatLoggedSegmentName.mjs';
20
+ import { writeMetaJson } from './writeMetaJson.mjs';
21
+ import { chalkHighlightThing } from '../utils/chalkHighlightThing.mjs';
22
+ import { getMetaSchema } from '../getProjectInfo/getMetaSchema.mjs';
19
23
  export class VovkDev {
20
24
  #projectInfo;
21
25
  #segments = [];
22
- #schemas = {};
26
+ #schemaSegments = {};
23
27
  #isWatching = false;
24
28
  #modulesWatcher = null;
25
29
  #segmentWatcher = null;
26
30
  #onFirstTimeGenerate = null;
31
+ #schemaOut = null;
32
+ #devHttps;
33
+ #logLevel;
34
+ constructor({ schemaOut, devHttps, logLevel }) {
35
+ this.#schemaOut = schemaOut || null;
36
+ this.#devHttps = devHttps || false;
37
+ this.#logLevel = logLevel || 'info';
38
+ }
27
39
  #watchSegments = (callback) => {
28
40
  const segmentReg = /\/?\[\[\.\.\.[a-zA-Z-_]+\]\]\/route.ts$/;
29
- const { cwd, log, config, apiDir } = this.#projectInfo;
30
- const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
31
- const apiDirAbsolutePath = path.join(cwd, apiDir);
41
+ const { cwd, log, config, apiDirAbsolutePath } = this.#projectInfo;
42
+ if (!apiDirAbsolutePath) {
43
+ throw new Error('Unable to watch segments. It looks like CWD is not a Next.js app.');
44
+ }
45
+ const schemaOutAbsolutePath = path.resolve(cwd, this.#schemaOut ?? config.schemaOutDir);
32
46
  const getSegmentName = (filePath) => path.relative(apiDirAbsolutePath, filePath).replace(segmentReg, '');
33
47
  log.debug(`Watching segments at ${apiDirAbsolutePath}`);
34
48
  this.#segmentWatcher = chokidar
@@ -47,7 +61,6 @@ export class VovkDev {
47
61
  {
48
62
  routeFilePath: filePath,
49
63
  segmentName,
50
- segmentImportPath: path.relative(config.clientOutDir, filePath), // TODO DRY locateSegments
51
64
  },
52
65
  ];
53
66
  log.info(`${capitalize(formatLoggedSegmentName(segmentName))} has been added`);
@@ -63,14 +76,22 @@ export class VovkDev {
63
76
  })
64
77
  .on('addDir', async (dirPath) => {
65
78
  log.debug(`Directory ${dirPath} has been added to segments folder`);
66
- this.#segments = await locateSegments({ dir: apiDirAbsolutePath, config });
79
+ this.#segments = await locateSegments({
80
+ dir: apiDirAbsolutePath,
81
+ config,
82
+ log: this.#projectInfo.log,
83
+ });
67
84
  for (const { segmentName } of this.#segments) {
68
85
  void this.#requestSchema(segmentName);
69
86
  }
70
87
  })
71
88
  .on('unlinkDir', async (dirPath) => {
72
89
  log.debug(`Directory ${dirPath} has been removed from segments folder`);
73
- this.#segments = await locateSegments({ dir: apiDirAbsolutePath, config });
90
+ this.#segments = await locateSegments({
91
+ dir: apiDirAbsolutePath,
92
+ config,
93
+ log: this.#projectInfo.log,
94
+ });
74
95
  for (const { segmentName } of this.#segments) {
75
96
  void this.#requestSchema(segmentName);
76
97
  }
@@ -138,13 +159,16 @@ export class VovkDev {
138
159
  let isInitial = true;
139
160
  let isReady = false;
140
161
  const handle = debounce(async () => {
141
- this.#projectInfo = await getProjectInfo();
162
+ this.#projectInfo = await getProjectInfo({ logLevel: this.#logLevel });
163
+ const { config, apiDirAbsolutePath } = this.#projectInfo;
164
+ this.#segments = await locateSegments({ dir: apiDirAbsolutePath, config, log });
142
165
  await this.#modulesWatcher?.close();
143
166
  await this.#segmentWatcher?.close();
144
167
  await Promise.all([
145
168
  new Promise((resolve) => this.#watchModules(() => resolve(0))),
146
169
  new Promise((resolve) => this.#watchSegments(() => resolve(0))),
147
170
  ]);
171
+ const schemaOutAbsolutePath = path.join(cwd, this.#schemaOut ?? this.#projectInfo.config.schemaOutDir);
148
172
  if (isInitial) {
149
173
  callback();
150
174
  }
@@ -152,18 +176,12 @@ export class VovkDev {
152
176
  log.info('Config file has been updated');
153
177
  this.#generate();
154
178
  }
179
+ await writeMetaJson(schemaOutAbsolutePath, this.#projectInfo);
155
180
  isInitial = false;
156
181
  }, 1000);
157
182
  chokidar
158
- // .watch(['vovk.config.{js,mjs,cjs}', '.config/vovk.config.{js,mjs,cjs}'], {
159
- .watch([
160
- 'vovk.config.js',
161
- 'vovk.config.mjs',
162
- 'vovk.config.cjs',
163
- '.config/vovk.config.js',
164
- '.config/vovk.config.mjs',
165
- '.config/vovk.config.cjs',
166
- ], {
183
+ // .watch(['vovk.config.{js,mjs}', '.config/vovk.config.{js,mjs}'], {
184
+ .watch(['vovk.config.js', 'vovk.config.mjs', '.config/vovk.config.js', '.config/vovk.config.mjs'], {
167
185
  persistent: true,
168
186
  cwd,
169
187
  ignoreInitial: false,
@@ -187,7 +205,6 @@ export class VovkDev {
187
205
  throw new Error('Already watching');
188
206
  const { log } = this.#projectInfo;
189
207
  log.debug(`Starting segments and modules watcher. Detected initial segments: ${JSON.stringify(this.#segments.map((s) => s.segmentName))}.`);
190
- await ensureClient(this.#projectInfo);
191
208
  // automatically watches segments and modules
192
209
  this.#watchConfig(callback);
193
210
  }
@@ -203,11 +220,11 @@ export class VovkDev {
203
220
  const importRegex = /import\s*{[^}]*\b(get|post|put|del|head|options)\b[^}]*}\s*from\s*['"]vovk['"]/;
204
221
  if (importRegex.test(code) && namesOfClasses.length) {
205
222
  const affectedSegments = this.#segments.filter((s) => {
206
- const schema = this.#schemas[s.segmentName];
207
- if (!schema)
223
+ const segmentSchema = this.#schemaSegments[s.segmentName];
224
+ if (!segmentSchema)
208
225
  return false;
209
- const controllersByOriginalName = keyBy(schema.controllers, 'originalControllerName');
210
- return namesOfClasses.some((name) => schema.controllers[name] || controllersByOriginalName[name]);
226
+ const controllersByOriginalName = keyBy(segmentSchema.controllers, 'originalControllerName');
227
+ return namesOfClasses.some((name) => segmentSchema.controllers[name] || controllersByOriginalName[name]);
211
228
  });
212
229
  if (affectedSegments.length) {
213
230
  log.debug(`A file with controller ${namesOfClasses.join(', ')} have been modified at path "${filePath}". Segment(s) affected: ${JSON.stringify(affectedSegments.map((s) => s.segmentName))}`);
@@ -220,58 +237,82 @@ export class VovkDev {
220
237
  }
221
238
  }
222
239
  else {
223
- log.debug(`The file does not contain any controller`);
240
+ log.debug(`The file ${filePath} does not contain any controller`);
224
241
  }
225
242
  };
226
243
  #requestSchema = debounceWithArgs(async (segmentName) => {
227
244
  const { apiRoot, log, port, config } = this.#projectInfo;
228
- const { devHttps } = config;
245
+ const devHttps = this.#devHttps ?? config.devHttps;
229
246
  const endpoint = `${apiRoot.startsWith(`http${devHttps ? 's' : ''}://`) ? apiRoot : `http${devHttps ? 's' : ''}://localhost:${port}${apiRoot}`}/${segmentName ? `${segmentName}/` : ''}_schema_`;
230
247
  log.debug(`Requesting schema for ${formatLoggedSegmentName(segmentName)} at ${endpoint}`);
231
248
  try {
232
249
  const resp = await fetch(endpoint);
250
+ const text = await resp.text();
251
+ let json;
252
+ try {
253
+ json = JSON.parse(text);
254
+ }
255
+ catch (error) {
256
+ log.error(`Error parsing JSON from ${chalkHighlightThing(endpoint)} for ${formatLoggedSegmentName(segmentName)}: ${error?.message}`);
257
+ log.error(`Response text: ${text.length > 2000 ? text.slice(0, 2000) + '...' : text}`);
258
+ return { isError: true };
259
+ }
233
260
  if (resp.status !== 200) {
234
261
  const probableCause = {
235
262
  404: 'The segment did not compile or config.origin is wrong.',
236
263
  }[resp.status];
237
- log.warn(`Schema request to ${formatLoggedSegmentName(segmentName)} failed with status code ${resp.status} but expected 200.${probableCause ? ` Probable cause: ${probableCause}` : ''}`);
264
+ log.warn(`Schema request to ${chalkHighlightThing(endpoint)} for ${formatLoggedSegmentName(segmentName)} failed with status code ${resp.status} but expected 200.${probableCause ? ` Probable cause: ${probableCause}.` : ''}.`);
265
+ log.warn(`Response from ${formatLoggedSegmentName(segmentName)}: ${text}`);
238
266
  return { isError: true };
239
267
  }
240
- let schema = null;
268
+ let segmentSchema = null;
241
269
  try {
242
- ({ schema } = (await resp.json()));
270
+ ({ schema: segmentSchema } = json);
243
271
  }
244
272
  catch (error) {
245
- log.error(`Error parsing schema for ${formatLoggedSegmentName(segmentName)}: ${error.message}`);
273
+ log.error(`Error parsing schema for ${formatLoggedSegmentName(segmentName)}: ${error?.message}`);
246
274
  }
247
- await this.#handleSchema(schema);
275
+ await this.#handleSegmentSchema(segmentName, segmentSchema);
248
276
  }
249
277
  catch (error) {
250
- log.error(`Error requesting schema for ${formatLoggedSegmentName(segmentName)} at ${endpoint}: ${error.message}`);
278
+ log.error(`Error requesting schema for ${formatLoggedSegmentName(segmentName)} at ${endpoint}: ${error?.message}`);
251
279
  return { isError: true };
252
280
  }
253
281
  return { isError: false };
254
282
  }, 500);
255
- #generate = debounce(() => generate({ projectInfo: this.#projectInfo, segments: this.#segments, segmentsSchema: this.#schemas }).then(this.#onFirstTimeGenerate), 1000);
256
- async #handleSchema(schema) {
283
+ #generate = debounce(() => {
284
+ const fullSchema = {
285
+ $schema: VovkSchemaIdEnum.SCHEMA,
286
+ segments: this.#schemaSegments,
287
+ meta: getMetaSchema({
288
+ config: this.#projectInfo.config,
289
+ }),
290
+ };
291
+ return generate({
292
+ projectInfo: this.#projectInfo,
293
+ fullSchema,
294
+ locatedSegments: this.#segments,
295
+ }).then(this.#onFirstTimeGenerate);
296
+ }, 1000);
297
+ async #handleSegmentSchema(segmentName, segmentSchema) {
257
298
  const { log, config, cwd } = this.#projectInfo;
258
- if (!schema) {
259
- log.warn('Segment schema is null');
299
+ if (!segmentSchema) {
300
+ log.warn(`${formatLoggedSegmentName(segmentName, { upperFirst: true })} schema is null`);
260
301
  return;
261
302
  }
262
- log.debug(`Handling received schema from ${formatLoggedSegmentName(schema.segmentName)}`);
263
- const schemaJsonOutAbsolutePath = path.join(cwd, config.schemaOutDir, JSON_DIR_NAME);
264
- const segment = this.#segments.find((s) => s.segmentName === schema.segmentName);
303
+ log.debug(`Handling received schema from ${formatLoggedSegmentName(segmentName)}`);
304
+ const schemaOutAbsolutePath = path.resolve(cwd, this.#schemaOut ?? config.schemaOutDir);
305
+ const segment = this.#segments.find((s) => s.segmentName === segmentName);
265
306
  if (!segment) {
266
- log.warn(`Segment "${schema.segmentName}" not found`);
307
+ log.warn(`${formatLoggedSegmentName(segmentName)} not found`);
267
308
  return;
268
309
  }
269
- this.#schemas[schema.segmentName] = schema;
270
- if (schema.emitSchema) {
310
+ this.#schemaSegments[segmentName] = segmentSchema;
311
+ if (segmentSchema.emitSchema) {
271
312
  const now = Date.now();
272
- const { diffResult } = await writeOneSchemaFile({
273
- schemaJsonOutAbsolutePath,
274
- schema,
313
+ const { diffResult } = await writeOneSegmentSchemaFile({
314
+ schemaOutAbsolutePath,
315
+ segmentSchema,
275
316
  skipIfExists: false,
276
317
  });
277
318
  const timeTook = Date.now() - now;
@@ -280,25 +321,27 @@ export class VovkDev {
280
321
  log.info(`Schema for ${formatLoggedSegmentName(segment.segmentName)} has been updated in ${timeTook}ms`);
281
322
  }
282
323
  }
283
- else if (schema && !isSchemaEmpty(schema)) {
324
+ else if (segmentSchema && !isEmpty(segmentSchema.controllers)) {
284
325
  log.error(`Non-empty schema provided for ${formatLoggedSegmentName(segment.segmentName)} but "emitSchema" is false`);
285
326
  }
286
- if (this.#segments.every((s) => this.#schemas[s.segmentName])) {
327
+ if (this.#segments.every((s) => this.#schemaSegments[s.segmentName])) {
287
328
  log.debug(`All segments with "emitSchema" have schema.`);
288
329
  this.#generate();
289
330
  }
290
331
  }
291
332
  async start({ exit }) {
292
333
  const now = Date.now();
293
- this.#projectInfo = await getProjectInfo();
294
- const { log, config, cwd, apiDir } = this.#projectInfo;
334
+ this.#projectInfo = await getProjectInfo({ logLevel: this.#logLevel });
335
+ const { log, config, cwd, apiDirAbsolutePath } = this.#projectInfo;
336
+ this.#segments = await locateSegments({ dir: apiDirAbsolutePath, config, log });
295
337
  log.info('Starting...');
296
338
  if (exit) {
297
339
  this.#onFirstTimeGenerate = once(() => {
298
340
  log.info('The schemas and the RPC client have been generated. Exiting...');
299
341
  });
300
342
  }
301
- if (config.devHttps) {
343
+ const devHttps = this.#devHttps ?? config.devHttps;
344
+ if (devHttps) {
302
345
  const agent = new Agent({
303
346
  connect: {
304
347
  rejectUnauthorized: false,
@@ -312,10 +355,10 @@ export class VovkDev {
312
355
  process.on('unhandledRejection', (reason) => {
313
356
  log.error(`Unhandled Rejection: ${String(reason)}`);
314
357
  });
315
- const apiDirAbsolutePath = path.join(cwd, apiDir);
316
- const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
317
- this.#segments = await locateSegments({ dir: apiDirAbsolutePath, config });
318
- await debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
358
+ const schemaOutAbsolutePath = path.resolve(cwd, this.#schemaOut ?? config.schemaOutDir);
359
+ const segmentNames = this.#segments.map((s) => s.segmentName);
360
+ await ensureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, segmentNames);
361
+ await ensureClient(this.#projectInfo, this.#segments);
319
362
  const MAX_ATTEMPTS = 5;
320
363
  const DELAY = 5000;
321
364
  // Request schema every segment in 5 seconds in order to update schema on start
@@ -334,7 +377,7 @@ export class VovkDev {
334
377
  void this.#requestSchema(segmentName).then(({ isError: isError2 }) => {
335
378
  if (!isError2) {
336
379
  clearInterval(interval);
337
- log.info(`Requested schema for ${formatLoggedSegmentName(segmentName)} after ${attempts} attempts`);
380
+ log.info(`Requested schema for ${formatLoggedSegmentName(segmentName)} after ${attempts} attempt${attempts === 1 ? '' : 's'}`);
338
381
  }
339
382
  });
340
383
  }, DELAY);
@@ -354,5 +397,11 @@ export class VovkDev {
354
397
  }
355
398
  const env = process.env;
356
399
  if (env.__VOVK_START_WATCHER_IN_STANDALONE_MODE__ === 'true') {
357
- void new VovkDev().start({ exit: env.__VOVK_EXIT__ === 'true' });
400
+ void new VovkDev({
401
+ schemaOut: env.__VOVK_SCHEMA_OUT_FLAG__ || undefined,
402
+ devHttps: env.__VOVK_DEV_HTTPS_FLAG__ === 'true',
403
+ logLevel: env.__VOVK_LOG_LEVEL__,
404
+ }).start({
405
+ exit: env.__VOVK_EXIT__ === 'true',
406
+ });
358
407
  }
@@ -1,3 +1,3 @@
1
- import type { DiffResult } from './diffSchema.mjs';
1
+ import type { DiffResult } from './diffSegmentSchema.mjs';
2
2
  import type { ProjectInfo } from '../getProjectInfo/index.mjs';
3
- export default function logDiffResult(segmentName: string, diffResult: DiffResult, projectInfo: ProjectInfo): void;
3
+ export declare function logDiffResult(segmentName: string, diffResult: DiffResult, projectInfo: ProjectInfo): void;
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
- import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
3
- import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
4
- export default function logDiffResult(segmentName, diffResult, projectInfo) {
2
+ import { formatLoggedSegmentName } from '../utils/formatLoggedSegmentName.mjs';
3
+ import { chalkHighlightThing } from '../utils/chalkHighlightThing.mjs';
4
+ export function logDiffResult(segmentName, diffResult, projectInfo) {
5
5
  const diffNormalized = [];
6
6
  diffResult.controllers.added.forEach((name) => {
7
7
  diffNormalized.push({ what: 'controller', type: 'added', name });
@@ -20,7 +20,7 @@ export default function logDiffResult(segmentName, diffResult, projectInfo) {
20
20
  diffNormalized.push({ what: 'controllerHandler', type: 'changed', name: `${handler.nameOfClass}.${name}` });
21
21
  });
22
22
  });
23
- const LIMIT = diffNormalized.length < 12 ? diffNormalized.length : 10;
23
+ const LIMIT = diffNormalized.length < 17 ? diffNormalized.length : 15;
24
24
  const addedText = chalk.green('added');
25
25
  const removedText = chalk.red('removed');
26
26
  const changedText = chalk.cyan('changed');
@@ -29,10 +29,10 @@ export default function logDiffResult(segmentName, diffResult, projectInfo) {
29
29
  case 'controller':
30
30
  switch (diffNormalizedItem.type) {
31
31
  case 'added':
32
- projectInfo.log.info(`Schema for RPC ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
32
+ projectInfo.log.info(`Schema for RPC module ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
33
33
  break;
34
34
  case 'removed':
35
- projectInfo.log.info(`Schema for RPC ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
35
+ projectInfo.log.info(`Schema for RPC module ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
36
36
  break;
37
37
  }
38
38
  break;
@@ -0,0 +1,2 @@
1
+ import type { ProjectInfo } from '../getProjectInfo/index.mjs';
2
+ export declare function writeMetaJson(schemaOutAbsolutePath: string, projectInfo: ProjectInfo): Promise<void>;
@@ -0,0 +1,19 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { META_FILE_NAME } from './writeOneSegmentSchemaFile.mjs';
4
+ import { chalkHighlightThing } from '../utils/chalkHighlightThing.mjs';
5
+ import { getMetaSchema } from '../getProjectInfo/getMetaSchema.mjs';
6
+ export async function writeMetaJson(schemaOutAbsolutePath, projectInfo) {
7
+ const metaJsonPath = path.join(schemaOutAbsolutePath, META_FILE_NAME + '.json');
8
+ const metaStr = JSON.stringify(getMetaSchema({
9
+ config: projectInfo.config,
10
+ }), null, 2);
11
+ const existingStr = await fs.readFile(metaJsonPath, 'utf-8').catch(() => null);
12
+ if (existingStr !== metaStr) {
13
+ await fs.writeFile(metaJsonPath, metaStr);
14
+ projectInfo?.log.info(`Meta JSON is written to ${chalkHighlightThing(metaJsonPath)}`);
15
+ }
16
+ else {
17
+ projectInfo?.log.debug(`Meta JSON is up to date at ${chalkHighlightThing(metaJsonPath)}`);
18
+ }
19
+ }
@@ -0,0 +1,12 @@
1
+ import type { VovkSegmentSchema } from 'vovk/internal';
2
+ import { type DiffResult } from './diffSegmentSchema.mjs';
3
+ export declare const ROOT_SEGMENT_FILE_NAME = "root";
4
+ export declare const META_FILE_NAME = "_meta";
5
+ export declare function writeOneSegmentSchemaFile({ schemaOutAbsolutePath, segmentSchema, skipIfExists, }: {
6
+ schemaOutAbsolutePath: string;
7
+ segmentSchema: VovkSegmentSchema;
8
+ skipIfExists?: boolean;
9
+ }): Promise<{
10
+ isCreated: boolean;
11
+ diffResult: DiffResult | null;
12
+ }>;
@@ -1,11 +1,11 @@
1
1
  import path from 'node:path';
2
2
  import fs from 'node:fs/promises';
3
- import diffSchema from './diffSchema.mjs';
4
- import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
5
- export const ROOT_SEGMENT_SCHEMA_NAME = '_root';
6
- export const JSON_DIR_NAME = 'json';
7
- export default async function writeOneSchemaFile({ schemaJsonOutAbsolutePath, schema, skipIfExists = false, }) {
8
- const segmentPath = path.join(schemaJsonOutAbsolutePath, `${schema.segmentName || ROOT_SEGMENT_SCHEMA_NAME}.json`);
3
+ import { diffSegmentSchema } from './diffSegmentSchema.mjs';
4
+ import { getFileSystemEntryType } from '../utils/getFileSystemEntryType.mjs';
5
+ export const ROOT_SEGMENT_FILE_NAME = 'root';
6
+ export const META_FILE_NAME = '_meta';
7
+ export async function writeOneSegmentSchemaFile({ schemaOutAbsolutePath, segmentSchema, skipIfExists = false, }) {
8
+ const segmentPath = path.join(schemaOutAbsolutePath, `${segmentSchema.segmentName || ROOT_SEGMENT_FILE_NAME}.json`);
9
9
  if (skipIfExists && (await getFileSystemEntryType(segmentPath))) {
10
10
  try {
11
11
  await fs.stat(segmentPath);
@@ -16,14 +16,17 @@ export default async function writeOneSchemaFile({ schemaJsonOutAbsolutePath, sc
16
16
  }
17
17
  }
18
18
  await fs.mkdir(path.dirname(segmentPath), { recursive: true });
19
- const schemaStr = JSON.stringify(schema, null, 2);
19
+ const schemaStr = JSON.stringify(segmentSchema, null, 2);
20
20
  const existing = await fs.readFile(segmentPath, 'utf-8').catch(() => null);
21
21
  if (existing === schemaStr) {
22
22
  return { isCreated: false, diffResult: null };
23
23
  }
24
24
  await fs.writeFile(segmentPath, schemaStr);
25
25
  if (existing) {
26
- return { isCreated: false, diffResult: diffSchema(JSON.parse(existing), schema) };
26
+ return {
27
+ isCreated: false,
28
+ diffResult: diffSegmentSchema(JSON.parse(existing), segmentSchema),
29
+ };
27
30
  }
28
31
  return { isCreated: true, diffResult: null };
29
32
  }
@@ -1,5 +1,3 @@
1
1
  import type { ProjectInfo } from '../getProjectInfo/index.mjs';
2
- export default function ensureClient({ config, cwd, log }: ProjectInfo): Promise<{
3
- written: boolean;
4
- path: string;
5
- }>;
2
+ import type { Segment } from '../utils/locateSegments.mjs';
3
+ export declare function ensureClient(projectInfo: ProjectInfo, locatedSegments: Segment[]): Promise<void>;
@@ -1,30 +1,28 @@
1
- import fs from 'node:fs/promises';
2
- import getClientTemplates from './getClientTemplates.mjs';
3
- import uniq from 'lodash/uniq.js';
4
- import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
5
- export default async function ensureClient({ config, cwd, log }) {
6
- const now = Date.now();
7
- const { clientOutDirAbsolutePath, templateFiles } = getClientTemplates({
8
- config,
9
- cwd,
10
- generateFrom: config.generateFrom,
11
- });
12
- let usedTemplateNames = [];
13
- const text = `// auto-generated ${new Date().toISOString()}
14
- // This is a temporary placeholder to avoid compilation errors if client is imported before it's generated.
15
- // If you still see this text, the client is not generated yet because of an unknown problem.
16
- // Feel free to report an issue at https://github.com/finom/vovk/issues`;
17
- for (const { outPath, templateName } of templateFiles) {
18
- const existing = await fs.readFile(outPath, 'utf-8').catch(() => null);
19
- if (!existing) {
20
- await fs.mkdir(clientOutDirAbsolutePath, { recursive: true });
21
- await fs.writeFile(outPath, outPath.endsWith('.py') ? text.replace(/\/\//g, '#') : text);
22
- usedTemplateNames.push(templateName);
23
- }
24
- }
25
- usedTemplateNames = uniq(usedTemplateNames);
26
- if (usedTemplateNames.length) {
27
- log.info(`Placeholder client files from template${usedTemplateNames.length !== 1 ? 's' : ''} ${chalkHighlightThing(usedTemplateNames.map((s) => `"${s}"`).join(', '))} are generated at ${clientOutDirAbsolutePath} in ${Date.now() - now}ms`);
1
+ import { VovkSchemaIdEnum } from 'vovk/internal';
2
+ import { generate } from './generate.mjs';
3
+ import { getMetaSchema } from '../getProjectInfo/getMetaSchema.mjs';
4
+ const getEmptySegmentRecordSchema = (segmentNames) => {
5
+ const result = {};
6
+ for (const segmentName of segmentNames) {
7
+ result[segmentName] = {
8
+ $schema: VovkSchemaIdEnum.SEGMENT,
9
+ segmentName,
10
+ segmentType: 'segment',
11
+ emitSchema: false,
12
+ controllers: {},
13
+ };
28
14
  }
29
- return { written: !!usedTemplateNames.length, path: clientOutDirAbsolutePath };
15
+ return result;
16
+ };
17
+ export async function ensureClient(projectInfo, locatedSegments) {
18
+ return generate({
19
+ isEnsuringClient: true,
20
+ projectInfo,
21
+ fullSchema: {
22
+ $schema: VovkSchemaIdEnum.SCHEMA,
23
+ segments: getEmptySegmentRecordSchema(locatedSegments.map(({ segmentName }) => segmentName)),
24
+ meta: getMetaSchema({ config: projectInfo.config }),
25
+ },
26
+ locatedSegments,
27
+ });
30
28
  }
@@ -0,0 +1,13 @@
1
+ import type { VovkSchema } from 'vovk';
2
+ import type { ProjectInfo } from '../getProjectInfo/index.mjs';
3
+ import type { GenerateOptions } from '../types.mjs';
4
+ import type { Segment } from '../utils/locateSegments.mjs';
5
+ export declare function generate({ isEnsuringClient, isBundle, projectInfo, forceNothingWrittenLog, fullSchema, locatedSegments, cliGenerateOptions, }: {
6
+ isEnsuringClient?: boolean;
7
+ isBundle?: boolean;
8
+ projectInfo: ProjectInfo;
9
+ forceNothingWrittenLog?: boolean;
10
+ fullSchema: VovkSchema;
11
+ locatedSegments: Segment[];
12
+ cliGenerateOptions?: GenerateOptions;
13
+ }): Promise<void>;