vovk-cli 0.0.1-draft.38 → 0.0.1-draft.381

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 +19 -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 +18 -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 +27 -0
  20. package/dist/bundle/index.d.mts +8 -0
  21. package/dist/bundle/index.mjs +94 -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 -65
  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 +306 -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 +19 -0
  43. package/dist/generate/getTemplateClientImports.mjs +49 -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 +139 -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 +68 -0
  118. package/module-templates/type/controller.ts.ejs +56 -0
  119. package/module-templates/type/service.ts.ejs +28 -0
  120. package/module-templates/valibot/controller.ts.ejs +68 -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
@@ -1,32 +1,48 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import { VovkSchemaIdEnum } from 'vovk';
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';
9
+ import once from 'lodash/once.js';
8
10
  import isEmpty from 'lodash/isEmpty.js';
9
- import { debouncedEnsureSchemaFiles } from './ensureSchemaFiles.mjs';
10
- import writeOneSchemaFile from './writeOneSchemaFile.mjs';
11
+ import ensureSchemaFiles, { debouncedEnsureSchemaFiles } from './ensureSchemaFiles.mjs';
12
+ import writeOneSegmentSchemaFile from './writeOneSegmentSchemaFile.mjs';
11
13
  import logDiffResult from './logDiffResult.mjs';
12
- import ensureClient from './ensureClient.mjs';
14
+ import ensureClient from '../generate/ensureClient.mjs';
13
15
  import getProjectInfo from '../getProjectInfo/index.mjs';
14
- import generateClient from '../generateClient.mjs';
15
- import locateSegments from '../locateSegments.mjs';
16
+ import { generate } from '../generate/generate.mjs';
17
+ import { locateSegments } from '../locateSegments.mjs';
16
18
  import debounceWithArgs from '../utils/debounceWithArgs.mjs';
17
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';
18
23
  export class VovkDev {
19
24
  #projectInfo;
20
25
  #segments = [];
21
- #schemas = {};
26
+ #schemaSegments = {};
22
27
  #isWatching = false;
23
28
  #modulesWatcher = null;
24
29
  #segmentWatcher = null;
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
+ }
25
39
  #watchSegments = (callback) => {
26
40
  const segmentReg = /\/?\[\[\.\.\.[a-zA-Z-_]+\]\]\/route.ts$/;
27
- const { cwd, log, config, apiDir } = this.#projectInfo;
28
- const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
29
- 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);
30
46
  const getSegmentName = (filePath) => path.relative(apiDirAbsolutePath, filePath).replace(segmentReg, '');
31
47
  log.debug(`Watching segments at ${apiDirAbsolutePath}`);
32
48
  this.#segmentWatcher = chokidar
@@ -40,7 +56,13 @@ export class VovkDev {
40
56
  const segmentName = getSegmentName(filePath);
41
57
  this.#segments = this.#segments.find((s) => s.segmentName === segmentName)
42
58
  ? this.#segments
43
- : [...this.#segments, { routeFilePath: filePath, segmentName }];
59
+ : [
60
+ ...this.#segments,
61
+ {
62
+ routeFilePath: filePath,
63
+ segmentName,
64
+ },
65
+ ];
44
66
  log.info(`${capitalize(formatLoggedSegmentName(segmentName))} has been added`);
45
67
  log.debug(`Full list of segments: ${this.#segments.map((s) => s.segmentName).join(', ')}`);
46
68
  void debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
@@ -54,14 +76,22 @@ export class VovkDev {
54
76
  })
55
77
  .on('addDir', async (dirPath) => {
56
78
  log.debug(`Directory ${dirPath} has been added to segments folder`);
57
- this.#segments = await locateSegments(apiDirAbsolutePath);
79
+ this.#segments = await locateSegments({
80
+ dir: apiDirAbsolutePath,
81
+ config,
82
+ log: this.#projectInfo.log,
83
+ });
58
84
  for (const { segmentName } of this.#segments) {
59
85
  void this.#requestSchema(segmentName);
60
86
  }
61
87
  })
62
88
  .on('unlinkDir', async (dirPath) => {
63
89
  log.debug(`Directory ${dirPath} has been removed from segments folder`);
64
- this.#segments = await locateSegments(apiDirAbsolutePath);
90
+ this.#segments = await locateSegments({
91
+ dir: apiDirAbsolutePath,
92
+ config,
93
+ log: this.#projectInfo.log,
94
+ });
65
95
  for (const { segmentName } of this.#segments) {
66
96
  void this.#requestSchema(segmentName);
67
97
  }
@@ -129,24 +159,36 @@ export class VovkDev {
129
159
  let isInitial = true;
130
160
  let isReady = false;
131
161
  const handle = debounce(async () => {
132
- 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 });
133
165
  await this.#modulesWatcher?.close();
134
166
  await this.#segmentWatcher?.close();
135
167
  await Promise.all([
136
168
  new Promise((resolve) => this.#watchModules(() => resolve(0))),
137
- new Promise((resolve) => this.#watchSegments(() => resolve(0)))
169
+ new Promise((resolve) => this.#watchSegments(() => resolve(0))),
138
170
  ]);
171
+ const schemaOutAbsolutePath = path.join(cwd, this.#schemaOut ?? this.#projectInfo.config.schemaOutDir);
139
172
  if (isInitial) {
140
173
  callback();
141
174
  }
142
175
  else {
143
176
  log.info('Config file has been updated');
177
+ this.#generate();
144
178
  }
179
+ await writeMetaJson(schemaOutAbsolutePath, this.#projectInfo);
145
180
  isInitial = false;
146
181
  }, 1000);
147
182
  chokidar
148
183
  // .watch(['vovk.config.{js,mjs,cjs}', '.config/vovk.config.{js,mjs,cjs}'], {
149
- .watch(['vovk.config.js', 'vovk.config.mjs', 'vovk.config.cjs', '.config/vovk.config.js', '.config/vovk.config.mjs', '.config/vovk.config.cjs'], {
184
+ .watch([
185
+ 'vovk.config.js',
186
+ 'vovk.config.mjs',
187
+ 'vovk.config.cjs',
188
+ '.config/vovk.config.js',
189
+ '.config/vovk.config.mjs',
190
+ '.config/vovk.config.cjs',
191
+ ], {
150
192
  persistent: true,
151
193
  cwd,
152
194
  ignoreInitial: false,
@@ -170,7 +212,6 @@ export class VovkDev {
170
212
  throw new Error('Already watching');
171
213
  const { log } = this.#projectInfo;
172
214
  log.debug(`Starting segments and modules watcher. Detected initial segments: ${JSON.stringify(this.#segments.map((s) => s.segmentName))}.`);
173
- await ensureClient(this.#projectInfo);
174
215
  // automatically watches segments and modules
175
216
  this.#watchConfig(callback);
176
217
  }
@@ -183,21 +224,17 @@ export class VovkDev {
183
224
  }
184
225
  const nameOfClasReg = /\bclass\s+([A-Za-z_]\w*)(?:\s*<[^>]*>)?\s*\{/g;
185
226
  const namesOfClasses = [...code.matchAll(nameOfClasReg)].map((match) => match[1]);
186
- const importRegex = /import\s*{[^}]*\b(get|post|put|del|head|options|worker)\b[^}]*}\s*from\s*['"]vovk['"]/;
227
+ const importRegex = /import\s*{[^}]*\b(get|post|put|del|head|options)\b[^}]*}\s*from\s*['"]vovk['"]/;
187
228
  if (importRegex.test(code) && namesOfClasses.length) {
188
229
  const affectedSegments = this.#segments.filter((s) => {
189
- const schema = this.#schemas[s.segmentName];
190
- if (!schema)
230
+ const segmentSchema = this.#schemaSegments[s.segmentName];
231
+ if (!segmentSchema)
191
232
  return false;
192
- const controllersByOriginalName = keyBy(schema.controllers, 'originalControllerName');
193
- const workersByOriginalName = keyBy(schema.workers, 'originalWorkerName');
194
- return namesOfClasses.some((name) => schema.controllers[name] ||
195
- schema.workers[name] ||
196
- controllersByOriginalName[name] ||
197
- workersByOriginalName[name]);
233
+ const controllersByOriginalName = keyBy(segmentSchema.controllers, 'originalControllerName');
234
+ return namesOfClasses.some((name) => segmentSchema.controllers[name] || controllersByOriginalName[name]);
198
235
  });
199
236
  if (affectedSegments.length) {
200
- log.debug(`A file with controller or worker ${namesOfClasses.join(', ')} have been modified at path "${filePath}". Segment(s) affected: ${JSON.stringify(affectedSegments.map((s) => s.segmentName))}`);
237
+ log.debug(`A file with controller ${namesOfClasses.join(', ')} have been modified at path "${filePath}". Segment(s) affected: ${JSON.stringify(affectedSegments.map((s) => s.segmentName))}`);
201
238
  for (const segment of affectedSegments) {
202
239
  await this.#requestSchema(segment.segmentName);
203
240
  }
@@ -207,57 +244,75 @@ export class VovkDev {
207
244
  }
208
245
  }
209
246
  else {
210
- log.debug(`The file does not contain any controller or worker`);
247
+ log.debug(`The file ${filePath} does not contain any controller`);
211
248
  }
212
249
  };
213
250
  #requestSchema = debounceWithArgs(async (segmentName) => {
214
- const { apiEntryPoint, log, port, config } = this.#projectInfo;
215
- const { devHttps } = config;
216
- const endpoint = `${apiEntryPoint.startsWith(`http${devHttps ? 's' : ''}://`) ? apiEntryPoint : `http${devHttps ? 's' : ''}://localhost:${port}${apiEntryPoint}`}/${segmentName ? `${segmentName}/` : ''}_schema_`;
251
+ const { apiRoot, log, port, config } = this.#projectInfo;
252
+ const devHttps = this.#devHttps ?? config.devHttps;
253
+ const endpoint = `${apiRoot.startsWith(`http${devHttps ? 's' : ''}://`) ? apiRoot : `http${devHttps ? 's' : ''}://localhost:${port}${apiRoot}`}/${segmentName ? `${segmentName}/` : ''}_schema_`;
217
254
  log.debug(`Requesting schema for ${formatLoggedSegmentName(segmentName)} at ${endpoint}`);
218
255
  try {
219
256
  const resp = await fetch(endpoint);
257
+ const text = await resp.text();
258
+ const json = text ? JSON.parse(text) : null;
220
259
  if (resp.status !== 200) {
221
260
  const probableCause = {
222
261
  404: 'The segment did not compile or config.origin is wrong.',
223
262
  }[resp.status];
224
- log.warn(`Schema request to ${formatLoggedSegmentName(segmentName)} failed with status code ${resp.status} but expected 200.${probableCause ? ` Probable cause: ${probableCause}` : ''}`);
263
+ log.warn(`Schema request to ${chalkHighlightThing(endpoint)} for ${formatLoggedSegmentName(segmentName)} failed with status code ${resp.status} but expected 200.${probableCause ? ` Probable cause: ${probableCause}` : ''}. Response text will be logged below on "debug" level.`);
264
+ log.debug(`Response from ${formatLoggedSegmentName(segmentName)}: ${text}`);
225
265
  return { isError: true };
226
266
  }
227
- let schema = null;
267
+ let segmentSchema = null;
228
268
  try {
229
- ({ schema } = (await resp.json()));
269
+ ({ schema: segmentSchema } = json);
230
270
  }
231
271
  catch (error) {
232
- log.error(`Error parsing schema for ${formatLoggedSegmentName(segmentName)}: ${error.message}`);
272
+ log.error(`Error parsing schema for ${formatLoggedSegmentName(segmentName)}: ${error?.message}`);
233
273
  }
234
- await this.#handleSchema(schema);
274
+ await this.#handleSegmentSchema(segmentName, segmentSchema);
235
275
  }
236
276
  catch (error) {
237
- log.error(`Error requesting schema for ${formatLoggedSegmentName(segmentName)}: ${error.message}`);
277
+ log.error(`Error requesting schema for ${formatLoggedSegmentName(segmentName)} at ${endpoint}: ${error?.message}`);
238
278
  return { isError: true };
239
279
  }
240
280
  return { isError: false };
241
281
  }, 500);
242
- async #handleSchema(schema) {
282
+ #generate = debounce(() => {
283
+ const fullSchema = {
284
+ $schema: VovkSchemaIdEnum.SCHEMA,
285
+ segments: this.#schemaSegments,
286
+ meta: getMetaSchema({
287
+ config: this.#projectInfo.config,
288
+ useEmitConfig: false,
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) {
243
298
  const { log, config, cwd } = this.#projectInfo;
244
- if (!schema) {
245
- log.warn('Segment schema is null');
299
+ if (!segmentSchema) {
300
+ log.warn(`${formatLoggedSegmentName(segmentName, { upperFirst: true })} schema is null`);
246
301
  return;
247
302
  }
248
- log.debug(`Handling received schema from ${formatLoggedSegmentName(schema.segmentName)}`);
249
- const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
250
- 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);
251
306
  if (!segment) {
252
- log.warn(`Segment "${schema.segmentName}" not found`);
307
+ log.warn(`${formatLoggedSegmentName(segmentName)} not found`);
253
308
  return;
254
309
  }
255
- this.#schemas[schema.segmentName] = schema;
256
- if (schema.emitSchema) {
310
+ this.#schemaSegments[segmentName] = segmentSchema;
311
+ if (segmentSchema.emitSchema) {
257
312
  const now = Date.now();
258
- const { diffResult } = await writeOneSchemaFile({
313
+ const { diffResult } = await writeOneSegmentSchemaFile({
259
314
  schemaOutAbsolutePath,
260
- schema,
315
+ segmentSchema,
261
316
  skipIfExists: false,
262
317
  });
263
318
  const timeTook = Date.now() - now;
@@ -266,19 +321,27 @@ export class VovkDev {
266
321
  log.info(`Schema for ${formatLoggedSegmentName(segment.segmentName)} has been updated in ${timeTook}ms`);
267
322
  }
268
323
  }
269
- else if (schema && (!isEmpty(schema.controllers) || !isEmpty(schema.workers))) {
270
- log.error(`Non-empty schema provided for ${formatLoggedSegmentName(segment.segmentName)} but emitSchema is false`);
324
+ else if (segmentSchema && !isEmpty(segmentSchema.controllers)) {
325
+ log.error(`Non-empty schema provided for ${formatLoggedSegmentName(segment.segmentName)} but "emitSchema" is false`);
271
326
  }
272
- if (this.#segments.every((s) => this.#schemas[s.segmentName])) {
327
+ if (this.#segments.every((s) => this.#schemaSegments[s.segmentName])) {
273
328
  log.debug(`All segments with "emitSchema" have schema.`);
274
- await generateClient(this.#projectInfo, this.#segments, this.#schemas);
329
+ this.#generate();
275
330
  }
276
331
  }
277
- async start() {
332
+ async start({ exit }) {
278
333
  const now = Date.now();
279
- this.#projectInfo = await getProjectInfo();
280
- const { log, config, cwd, apiDir } = this.#projectInfo;
281
- if (config.devHttps) {
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 });
337
+ log.info('Starting...');
338
+ if (exit) {
339
+ this.#onFirstTimeGenerate = once(() => {
340
+ log.info('The schemas and the RPC client have been generated. Exiting...');
341
+ });
342
+ }
343
+ const devHttps = this.#devHttps ?? config.devHttps;
344
+ if (devHttps) {
282
345
  const agent = new Agent({
283
346
  connect: {
284
347
  rejectUnauthorized: false,
@@ -292,14 +355,15 @@ export class VovkDev {
292
355
  process.on('unhandledRejection', (reason) => {
293
356
  log.error(`Unhandled Rejection: ${String(reason)}`);
294
357
  });
295
- const apiDirAbsolutePath = path.join(cwd, apiDir);
296
- const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
297
- this.#segments = await locateSegments(apiDirAbsolutePath);
298
- await debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
299
- // Request schema every segment in 5 seconds in order to update schema and start watching
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);
362
+ const MAX_ATTEMPTS = 5;
363
+ const DELAY = 5000;
364
+ // Request schema every segment in 5 seconds in order to update schema on start
300
365
  setTimeout(() => {
301
366
  for (const { segmentName } of this.#segments) {
302
- const MAX_ATTEMPTS = 3;
303
367
  let attempts = 0;
304
368
  void this.#requestSchema(segmentName).then(({ isError }) => {
305
369
  if (isError) {
@@ -313,19 +377,31 @@ export class VovkDev {
313
377
  void this.#requestSchema(segmentName).then(({ isError: isError2 }) => {
314
378
  if (!isError2) {
315
379
  clearInterval(interval);
380
+ log.info(`Requested schema for ${formatLoggedSegmentName(segmentName)} after ${attempts} attempt${attempts === 1 ? '' : 's'}`);
316
381
  }
317
382
  });
318
- }, 5000);
383
+ }, DELAY);
319
384
  }
320
385
  });
321
386
  }
387
+ }, DELAY);
388
+ if (!exit) {
322
389
  this.#watch(() => {
323
- log.info(`Ready in ${Date.now() - now}ms`);
390
+ log.info(`Ready in ${Date.now() - now}ms. Making initial requests for schemas in a moment...`);
324
391
  });
325
- }, 5000);
392
+ }
393
+ else {
394
+ log.info(`Ready in ${Date.now() - now}ms. Making requests for schemas in a moment...`);
395
+ }
326
396
  }
327
397
  }
328
398
  const env = process.env;
329
399
  if (env.__VOVK_START_WATCHER_IN_STANDALONE_MODE__ === 'true') {
330
- void new VovkDev().start();
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
+ });
331
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
3
  export default function logDiffResult(segmentName: string, diffResult: DiffResult, projectInfo: ProjectInfo): void;
@@ -3,23 +3,6 @@ import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
3
3
  import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
4
4
  export default function logDiffResult(segmentName, diffResult, projectInfo) {
5
5
  const diffNormalized = [];
6
- diffResult.workers.added.forEach((name) => {
7
- diffNormalized.push({ what: 'worker', type: 'added', name });
8
- });
9
- diffResult.workers.removed.forEach((name) => {
10
- diffNormalized.push({ what: 'worker', type: 'removed', name });
11
- });
12
- diffResult.workers.handlers.forEach((handler) => {
13
- handler.added.forEach((name) => {
14
- diffNormalized.push({ what: 'workerHandler', type: 'added', name: `${handler.nameOfClass}.${name}` });
15
- });
16
- handler.removed.forEach((name) => {
17
- diffNormalized.push({ what: 'workerHandler', type: 'removed', name: `${handler.nameOfClass}.${name}` });
18
- });
19
- handler.changed.forEach((name) => {
20
- diffNormalized.push({ what: 'workerHandler', type: 'changed', name: `${handler.nameOfClass}.${name}` });
21
- });
22
- });
23
6
  diffResult.controllers.added.forEach((name) => {
24
7
  diffNormalized.push({ what: 'controller', type: 'added', name });
25
8
  });
@@ -37,52 +20,32 @@ export default function logDiffResult(segmentName, diffResult, projectInfo) {
37
20
  diffNormalized.push({ what: 'controllerHandler', type: 'changed', name: `${handler.nameOfClass}.${name}` });
38
21
  });
39
22
  });
40
- const LIMIT = diffNormalized.length < 12 ? diffNormalized.length : 10;
23
+ const LIMIT = diffNormalized.length < 17 ? diffNormalized.length : 15;
41
24
  const addedText = chalk.green('added');
42
25
  const removedText = chalk.red('removed');
43
26
  const changedText = chalk.cyan('changed');
44
27
  for (const diffNormalizedItem of diffNormalized.slice(0, LIMIT)) {
45
28
  switch (diffNormalizedItem.what) {
46
- case 'worker':
47
- switch (diffNormalizedItem.type) {
48
- case 'added':
49
- projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
50
- break;
51
- case 'removed':
52
- projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
53
- break;
54
- }
55
- break;
56
29
  case 'controller':
57
30
  switch (diffNormalizedItem.type) {
58
31
  case 'added':
59
- projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
60
- break;
61
- case 'removed':
62
- projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
63
- break;
64
- }
65
- break;
66
- case 'workerHandler':
67
- switch (diffNormalizedItem.type) {
68
- case 'added':
69
- projectInfo.log.info(`Schema for worker method ${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)}`);
70
33
  break;
71
34
  case 'removed':
72
- projectInfo.log.info(`Schema for worker method ${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)}`);
73
36
  break;
74
37
  }
75
38
  break;
76
39
  case 'controllerHandler':
77
40
  switch (diffNormalizedItem.type) {
78
41
  case 'added':
79
- projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
42
+ projectInfo.log.info(`Schema for RPC method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
80
43
  break;
81
44
  case 'removed':
82
- projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
45
+ projectInfo.log.info(`Schema for RPC method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
83
46
  break;
84
47
  case 'changed':
85
- projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${changedText} at ${formatLoggedSegmentName(segmentName)}`);
48
+ projectInfo.log.info(`Schema for RPC method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${changedText} at ${formatLoggedSegmentName(segmentName)}`);
86
49
  break;
87
50
  }
88
51
  break;
@@ -0,0 +1,2 @@
1
+ import type { ProjectInfo } from '../getProjectInfo/index.mjs';
2
+ export default function writeMetaJson(schemaOutAbsolutePath: string, projectInfo: ProjectInfo): Promise<void>;
@@ -0,0 +1,20 @@
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 default 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
+ useEmitConfig: true,
11
+ }), null, 2);
12
+ const existingStr = await fs.readFile(metaJsonPath, 'utf-8').catch(() => null);
13
+ if (existingStr !== metaStr) {
14
+ await fs.writeFile(metaJsonPath, metaStr);
15
+ projectInfo?.log.info(`Meta JSON is written to ${chalkHighlightThing(metaJsonPath)}`);
16
+ }
17
+ else {
18
+ projectInfo?.log.debug(`Meta JSON is up to date at ${chalkHighlightThing(metaJsonPath)}`);
19
+ }
20
+ }
@@ -0,0 +1,12 @@
1
+ import type { VovkSegmentSchema } from 'vovk';
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 default 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,10 +1,11 @@
1
1
  import path from 'node:path';
2
2
  import fs from 'node:fs/promises';
3
- import diffSchema from './diffSchema.mjs';
3
+ import diffSegmentSchema from './diffSegmentSchema.mjs';
4
4
  import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
5
- export const ROOT_SEGMENT_SCHEMA_NAME = '_root';
6
- export default async function writeOneSchemaFile({ schemaOutAbsolutePath, schema, skipIfExists = false, }) {
7
- const segmentPath = path.join(schemaOutAbsolutePath, `${schema.segmentName || ROOT_SEGMENT_SCHEMA_NAME}.json`);
5
+ export const ROOT_SEGMENT_FILE_NAME = 'root';
6
+ export const META_FILE_NAME = '_meta';
7
+ export default async function writeOneSegmentSchemaFile({ schemaOutAbsolutePath, segmentSchema, skipIfExists = false, }) {
8
+ const segmentPath = path.join(schemaOutAbsolutePath, `${segmentSchema.segmentName || ROOT_SEGMENT_FILE_NAME}.json`);
8
9
  if (skipIfExists && (await getFileSystemEntryType(segmentPath))) {
9
10
  try {
10
11
  await fs.stat(segmentPath);
@@ -15,14 +16,17 @@ export default async function writeOneSchemaFile({ schemaOutAbsolutePath, schema
15
16
  }
16
17
  }
17
18
  await fs.mkdir(path.dirname(segmentPath), { recursive: true });
18
- const schemaStr = JSON.stringify(schema, null, 2);
19
+ const schemaStr = JSON.stringify(segmentSchema, null, 2);
19
20
  const existing = await fs.readFile(segmentPath, 'utf-8').catch(() => null);
20
21
  if (existing === schemaStr) {
21
22
  return { isCreated: false, diffResult: null };
22
23
  }
23
24
  await fs.writeFile(segmentPath, schemaStr);
24
25
  if (existing) {
25
- return { isCreated: false, diffResult: diffSchema(JSON.parse(existing), schema) };
26
+ return {
27
+ isCreated: false,
28
+ diffResult: diffSegmentSchema(JSON.parse(existing), segmentSchema),
29
+ };
26
30
  }
27
31
  return { isCreated: true, diffResult: null };
28
32
  }
@@ -0,0 +1,3 @@
1
+ import type { ProjectInfo } from '../getProjectInfo/index.mjs';
2
+ import type { Segment } from '../locateSegments.mjs';
3
+ export default function ensureClient(projectInfo: ProjectInfo, locatedSegments: Segment[]): Promise<void>;
@@ -0,0 +1,28 @@
1
+ import { VovkSchemaIdEnum } from 'vovk';
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
+ };
14
+ }
15
+ return result;
16
+ };
17
+ export default 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, useEmitConfig: false }),
25
+ },
26
+ locatedSegments,
27
+ });
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 '../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>;