vovk-cli 0.0.1-draft.43 → 0.0.1-draft.46

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.
@@ -1,7 +1,6 @@
1
1
 
2
2
  <%- '// auto-generated\n/* eslint-disable */' %>
3
- import type { clientizeController, VovkClientFetcher } from 'vovk/client';
4
- import type { promisifyWorker } from 'vovk/worker';
3
+ import type { createRPC, createWPC, VovkClientFetcher } from 'vovk';
5
4
  import type fetcher from '<%= fetcherClientImportPath %>';
6
5
  <% segments.forEach((segment, i) => {
7
6
  const hasWorkers = !!Object.keys(segmentsSchema[segment.segmentName].workers).length;
@@ -17,9 +16,9 @@ type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
17
16
  const workers = Object.keys(segSchema.workers);
18
17
  %>
19
18
  <% controllers.forEach((key) => { %>
20
- export const <%= key %>: ReturnType<typeof clientizeController<Controllers<%= i %>["<%= key %>"], Options>>;
19
+ export const <%= key %>: ReturnType<typeof createRPC<Controllers<%= i %>["<%= key %>"], Options>>;
21
20
  <% }) %>
22
21
  <% workers.forEach((key) => { %>
23
- export const <%= key %>: ReturnType<typeof promisifyWorker<Workers<%= i %>["<%= key %>"]>>;
22
+ export const <%= key %>: ReturnType<typeof createWPC<Workers<%= i %>["<%= key %>"]>>;
24
23
  <% }) %>
25
24
  <% }) %>
@@ -1,6 +1,5 @@
1
1
  <%- '// auto-generated\n/* eslint-disable */' %>
2
- const { clientizeController } = require('vovk/client');
3
- const { promisifyWorker } = require('vovk/worker');
2
+ const { createRPC, createWPC } = require('vovk');
4
3
  const { default: fetcher } = require('<%= fetcherClientImportPath %>');
5
4
  const schema = require('<%= schemaOutImportPath %>');
6
5
 
@@ -14,13 +13,13 @@ const apiRoot = '<%= apiRoot %>';
14
13
  const workers = Object.keys(segSchema.workers);
15
14
  %>
16
15
  <% controllers.forEach((key) => { %>
17
- exports.<%= key %> = clientizeController(
16
+ exports.<%= key %> = createRPC(
18
17
  schema['<%= segment.segmentName %>'].controllers.<%= key %>,
19
18
  '<%= segment.segmentName %>',
20
19
  { fetcher, validateOnClient, defaultOptions: { apiRoot } }
21
20
  );
22
21
  <% }) %>
23
22
  <% workers.forEach((key) => { %>
24
- exports.<%= key %> = promisifyWorker(null, schema['<%= segment.segmentName %>'].workers.<%= key %>);
23
+ exports.<%= key %> = createWPC(null, schema['<%= segment.segmentName %>'].workers.<%= key %>);
25
24
  <% }) %>
26
25
  <% }) %>
@@ -1,6 +1,5 @@
1
1
  <%- '// auto-generated\n/* eslint-disable */' %>
2
- import { clientizeController, type VovkClientFetcher } from 'vovk/client';
3
- <% if(hasWorkers) { %>import { promisifyWorker } from 'vovk/worker';<% } %>
2
+ import { createRPC, createWPC, type VovkClientFetcher } from 'vovk';
4
3
  import fetcher from '<%= fetcherClientImportPath %>';
5
4
  import schema from '<%= schemaOutImportPath %>';
6
5
  <% if (validateOnClientImportPath) { %>
@@ -19,7 +18,7 @@ const apiRoot = '<%= apiRoot %>';
19
18
  import type { Controllers as Controllers<%= i %><% if(hasWorkers) { %>, Workers as Workers<%= i %> <% } %>} from "<%= segment.segmentImportPath %>";
20
19
 
21
20
  <% Object.keys(segSchema.controllers).forEach((key) => { %>
22
- export const <%= key %> = clientizeController<Controllers<%= i %>["<%= key %>"], Options>(
21
+ export const <%= key %> = createRPC<Controllers<%= i %>["<%= key %>"], Options>(
23
22
  schema['<%= segment.segmentName %>'].controllers.<%= key %>,
24
23
  '<%= segment.segmentName %>',
25
24
  { fetcher, validateOnClient, defaultOptions: { apiRoot } }
@@ -27,7 +26,7 @@ export const <%= key %> = clientizeController<Controllers<%= i %>["<%= key %>"],
27
26
  <% }) %>
28
27
 
29
28
  <% Object.keys(segSchema.workers).forEach((key) => { %>
30
- export const <%= key %> = promisifyWorker<Workers<%= i %>["<%= key %>"]>(
29
+ export const <%= key %> = createWPC<Workers<%= i %>["<%= key %>"]>(
31
30
  null,
32
31
  schema['<%= segment.segmentName %>'].workers.<%= key %>
33
32
  );
@@ -1,5 +1,4 @@
1
- import type { VovkSchema } from 'vovk';
2
- import type { VovkControllerSchema, VovkWorkerSchema } from 'vovk/types';
1
+ import type { VovkControllerSchema, VovkWorkerSchema, VovkSchema } from 'vovk';
3
2
  interface HandlersDiff {
4
3
  nameOfClass: string;
5
4
  added: string[];
@@ -1,3 +1,6 @@
1
1
  import { ProjectInfo } from '../getProjectInfo/index.mjs';
2
+ /**
3
+ * Ensure that the schema files are created to avoid any import errors.
4
+ */
2
5
  export default function ensureSchemaFiles(projectInfo: ProjectInfo | null, schemaOutAbsolutePath: string, segmentNames: string[]): Promise<void>;
3
6
  export declare const debouncedEnsureSchemaFiles: import("lodash").DebouncedFunc<typeof ensureSchemaFiles>;
@@ -3,6 +3,9 @@ import path from 'node:path';
3
3
  import debounce from 'lodash/debounce.js';
4
4
  import writeOneSchemaFile, { ROOT_SEGMENT_SCHEMA_NAME } from './writeOneSchemaFile.mjs';
5
5
  import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
6
+ /**
7
+ * Ensure that the schema files are created to avoid any import errors.
8
+ */
6
9
  export default async function ensureSchemaFiles(projectInfo, schemaOutAbsolutePath, segmentNames) {
7
10
  const now = Date.now();
8
11
  let hasChanged = false;
@@ -78,6 +81,6 @@ export default segmentSchema;`;
78
81
  // Start the recursive deletion from the root directory
79
82
  await deleteUnnecessaryJsonFiles(schemaOutAbsolutePath);
80
83
  if (hasChanged)
81
- projectInfo?.log.info(`Schema files updated in ${Date.now() - now}ms`);
84
+ projectInfo?.log.info(`Created empty schema files in ${Date.now() - now}ms`);
82
85
  }
83
86
  export const debouncedEnsureSchemaFiles = debounce(ensureSchemaFiles, 1000);
@@ -1,4 +1,6 @@
1
1
  export declare class VovkDev {
2
2
  #private;
3
- start(): Promise<void>;
3
+ start({ exit }: {
4
+ exit: boolean;
5
+ }): Promise<void>;
4
6
  }
@@ -6,6 +6,7 @@ import keyBy from 'lodash/keyBy.js';
6
6
  import capitalize from 'lodash/capitalize.js';
7
7
  import debounce from 'lodash/debounce.js';
8
8
  import isEmpty from 'lodash/isEmpty.js';
9
+ import once from 'lodash/once.js';
9
10
  import { debouncedEnsureSchemaFiles } from './ensureSchemaFiles.mjs';
10
11
  import writeOneSchemaFile from './writeOneSchemaFile.mjs';
11
12
  import logDiffResult from './logDiffResult.mjs';
@@ -22,6 +23,7 @@ export class VovkDev {
22
23
  #isWatching = false;
23
24
  #modulesWatcher = null;
24
25
  #segmentWatcher = null;
26
+ #onFirstTimeGenerate = null;
25
27
  #watchSegments = (callback) => {
26
28
  const segmentReg = /\/?\[\[\.\.\.[a-zA-Z-_]+\]\]\/route.ts$/;
27
29
  const { cwd, log, config, apiDir } = this.#projectInfo;
@@ -40,11 +42,14 @@ export class VovkDev {
40
42
  const segmentName = getSegmentName(filePath);
41
43
  this.#segments = this.#segments.find((s) => s.segmentName === segmentName)
42
44
  ? this.#segments
43
- : [...this.#segments, {
45
+ : [
46
+ ...this.#segments,
47
+ {
44
48
  routeFilePath: filePath,
45
49
  segmentName,
46
- segmentImportPath: path.relative(config.clientOutDir, filePath) // TODO DRY locateSegments
47
- }];
50
+ segmentImportPath: path.relative(config.clientOutDir, filePath), // TODO DRY locateSegments
51
+ },
52
+ ];
48
53
  log.info(`${capitalize(formatLoggedSegmentName(segmentName))} has been added`);
49
54
  log.debug(`Full list of segments: ${this.#segments.map((s) => s.segmentName).join(', ')}`);
50
55
  void debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
@@ -138,7 +143,7 @@ export class VovkDev {
138
143
  await this.#segmentWatcher?.close();
139
144
  await Promise.all([
140
145
  new Promise((resolve) => this.#watchModules(() => resolve(0))),
141
- new Promise((resolve) => this.#watchSegments(() => resolve(0)))
146
+ new Promise((resolve) => this.#watchSegments(() => resolve(0))),
142
147
  ]);
143
148
  if (isInitial) {
144
149
  callback();
@@ -150,7 +155,14 @@ export class VovkDev {
150
155
  }, 1000);
151
156
  chokidar
152
157
  // .watch(['vovk.config.{js,mjs,cjs}', '.config/vovk.config.{js,mjs,cjs}'], {
153
- .watch(['vovk.config.js', 'vovk.config.mjs', 'vovk.config.cjs', '.config/vovk.config.js', '.config/vovk.config.mjs', '.config/vovk.config.cjs'], {
158
+ .watch([
159
+ 'vovk.config.js',
160
+ 'vovk.config.mjs',
161
+ 'vovk.config.cjs',
162
+ '.config/vovk.config.js',
163
+ '.config/vovk.config.mjs',
164
+ '.config/vovk.config.cjs',
165
+ ], {
154
166
  persistent: true,
155
167
  cwd,
156
168
  ignoreInitial: false,
@@ -238,11 +250,12 @@ export class VovkDev {
238
250
  await this.#handleSchema(schema);
239
251
  }
240
252
  catch (error) {
241
- log.error(`Error requesting schema for ${formatLoggedSegmentName(segmentName)}: ${error.message}`);
253
+ log.error(`Error requesting schema for ${formatLoggedSegmentName(segmentName)} at ${endpoint}: ${error.message}`);
242
254
  return { isError: true };
243
255
  }
244
256
  return { isError: false };
245
257
  }, 500);
258
+ #generate = debounce(() => generate({ projectInfo: this.#projectInfo, segments: this.#segments, segmentsSchema: this.#schemas }).then(this.#onFirstTimeGenerate), 1000);
246
259
  async #handleSchema(schema) {
247
260
  const { log, config, cwd } = this.#projectInfo;
248
261
  if (!schema) {
@@ -275,14 +288,19 @@ export class VovkDev {
275
288
  }
276
289
  if (this.#segments.every((s) => this.#schemas[s.segmentName])) {
277
290
  log.debug(`All segments with "emitSchema" have schema.`);
278
- await generate({ projectInfo: this.#projectInfo, segments: this.#segments, segmentsSchema: this.#schemas });
291
+ this.#generate();
279
292
  }
280
293
  }
281
- async start() {
294
+ async start({ exit }) {
282
295
  const now = Date.now();
283
296
  this.#projectInfo = await getProjectInfo();
284
297
  const { log, config, cwd, apiDir } = this.#projectInfo;
285
298
  log.info('Starting...');
299
+ if (exit) {
300
+ this.#onFirstTimeGenerate = once(() => {
301
+ log.info('The schemas and the RPC client have been generated. Exiting...');
302
+ });
303
+ }
286
304
  if (config.devHttps) {
287
305
  const agent = new Agent({
288
306
  connect: {
@@ -301,10 +319,11 @@ export class VovkDev {
301
319
  const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
302
320
  this.#segments = await locateSegments({ dir: apiDirAbsolutePath, config });
303
321
  await debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
322
+ const MAX_ATTEMPTS = 5;
323
+ const DELAY = 5000;
304
324
  // Request schema every segment in 5 seconds in order to update schema on start
305
325
  setTimeout(() => {
306
326
  for (const { segmentName } of this.#segments) {
307
- const MAX_ATTEMPTS = 3;
308
327
  let attempts = 0;
309
328
  void this.#requestSchema(segmentName).then(({ isError }) => {
310
329
  if (isError) {
@@ -318,19 +337,25 @@ export class VovkDev {
318
337
  void this.#requestSchema(segmentName).then(({ isError: isError2 }) => {
319
338
  if (!isError2) {
320
339
  clearInterval(interval);
340
+ log.info(`Requested schema for ${formatLoggedSegmentName(segmentName)} after ${attempts} attempts`);
321
341
  }
322
342
  });
323
- }, 5000);
343
+ }, DELAY);
324
344
  }
325
345
  });
326
346
  }
327
- }, 5000);
328
- this.#watch(() => {
329
- log.info(`Ready in ${Date.now() - now}ms`);
330
- });
347
+ }, DELAY);
348
+ if (!exit) {
349
+ this.#watch(() => {
350
+ log.info(`Ready in ${Date.now() - now}ms. Making initial requests for schemas in a moment...`);
351
+ });
352
+ }
353
+ else {
354
+ log.info(`Ready in ${Date.now() - now}ms. Making initial requests for schemas in a moment...`);
355
+ }
331
356
  }
332
357
  }
333
358
  const env = process.env;
334
359
  if (env.__VOVK_START_WATCHER_IN_STANDALONE_MODE__ === 'true') {
335
- void new VovkDev().start();
360
+ void new VovkDev().start({ exit: env.__VOVK_EXIT__ === 'true' });
336
361
  }
@@ -46,43 +46,43 @@ export default function logDiffResult(segmentName, diffResult, projectInfo) {
46
46
  case 'worker':
47
47
  switch (diffNormalizedItem.type) {
48
48
  case 'added':
49
- projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
49
+ projectInfo.log.info(`Schema for WPC ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
50
50
  break;
51
51
  case 'removed':
52
- projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
52
+ projectInfo.log.info(`Schema for WPC ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
53
53
  break;
54
54
  }
55
55
  break;
56
56
  case 'controller':
57
57
  switch (diffNormalizedItem.type) {
58
58
  case 'added':
59
- projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
59
+ projectInfo.log.info(`Schema forn RPC ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
60
60
  break;
61
61
  case 'removed':
62
- projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
62
+ projectInfo.log.info(`Schema forn RPC ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
63
63
  break;
64
64
  }
65
65
  break;
66
66
  case 'workerHandler':
67
67
  switch (diffNormalizedItem.type) {
68
68
  case 'added':
69
- projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
69
+ projectInfo.log.info(`Schema for WPC method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
70
70
  break;
71
71
  case 'removed':
72
- projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
72
+ projectInfo.log.info(`Schema for WPC method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
73
73
  break;
74
74
  }
75
75
  break;
76
76
  case 'controllerHandler':
77
77
  switch (diffNormalizedItem.type) {
78
78
  case 'added':
79
- projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
79
+ projectInfo.log.info(`Schema forn RPC method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
80
80
  break;
81
81
  case 'removed':
82
- projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
82
+ projectInfo.log.info(`Schema forn RPC method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
83
83
  break;
84
84
  case 'changed':
85
- projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${changedText} at ${formatLoggedSegmentName(segmentName)}`);
85
+ projectInfo.log.info(`Schema forn RPC method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${changedText} at ${formatLoggedSegmentName(segmentName)}`);
86
86
  break;
87
87
  }
88
88
  break;
@@ -1,4 +1,4 @@
1
- import { VovkConfig } from "../types.mjs";
1
+ import { VovkConfig } from '../types.mjs';
2
2
  interface ClientTemplate {
3
3
  templatePath: string;
4
4
  outPath: string;
@@ -12,14 +12,17 @@ export default function getClientTemplates({ config, cwd, templateNames = [], })
12
12
  compiled: ['compiled.js.ejs', 'compiled.d.ts.ejs'].map(mapper('compiled')),
13
13
  python: ['__init__.py'].map(mapper('python')),
14
14
  };
15
- const templateFiles = (templateNames ?? config.clientGenerateTemplateNames).reduce((acc, template) => {
15
+ const templateFiles = (templateNames ?? config.experimental_clientGenerateTemplateNames).reduce((acc, template) => {
16
16
  if (template in builtInTemplatesMap) {
17
17
  return [...acc, ...builtInTemplatesMap[template]];
18
18
  }
19
- return [...acc, {
19
+ return [
20
+ ...acc,
21
+ {
20
22
  templatePath: path.resolve(cwd, template),
21
- outPath: path.join(clientOutDirAbsolutePath, path.basename(template).replace('.ejs', ''))
22
- }];
23
+ outPath: path.join(clientOutDirAbsolutePath, path.basename(template).replace('.ejs', '')),
24
+ },
25
+ ];
23
26
  }, []);
24
27
  return templateFiles;
25
28
  }
@@ -5,9 +5,9 @@ import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
5
5
  import prettify from '../utils/prettify.mjs';
6
6
  import getClientTemplates from './getClientTemplates.mjs';
7
7
  export default async function generate({ projectInfo, segments, segmentsSchema, templates, prettify: prettifyClient, fullSchema, }) {
8
- templates = templates ?? projectInfo.config.clientGenerateTemplateNames;
8
+ templates = templates ?? projectInfo.config.experimental_clientGenerateTemplateNames;
9
9
  const noClient = templates?.[0] === 'none';
10
- const { config, cwd, log, validateOnClientImportPath, apiRoot, fetcherClientImportPath, schemaOutImportPath, } = projectInfo;
10
+ const { config, cwd, log, validateOnClientImportPath, apiRoot, fetcherClientImportPath, schemaOutImportPath } = projectInfo;
11
11
  const clientOutDirAbsolutePath = path.resolve(cwd, config.clientOutDir);
12
12
  const templateFiles = getClientTemplates({ config, cwd, templateNames: templates });
13
13
  // Ensure that each segment has a matching schema if it needs to be emitted:
@@ -30,40 +30,43 @@ export default async function generate({ projectInfo, segments, segmentsSchema,
30
30
  segments,
31
31
  segmentsSchema,
32
32
  };
33
- // 1. Process each template in parallel
34
- const processedTemplates = noClient ? [] : await Promise.all(templateFiles.map(async ({ templatePath, outPath }) => {
35
- // Read the EJS template
36
- const templateContent = await fs.readFile(templatePath, 'utf-8');
37
- // Render the template
38
- let rendered = templatePath.endsWith('.ejs') ? ejs.render(templateContent, ejsData) : templateContent;
39
- // Optionally prettify
40
- if (prettifyClient || config.prettifyClient) {
41
- rendered = await prettify(rendered, outPath);
42
- }
43
- // Read existing file content to compare
44
- const existingContent = await fs.readFile(outPath, 'utf-8').catch(() => '');
45
- // Determine if we need to rewrite the file
46
- const needsWriting = existingContent !== rendered;
47
- return {
48
- outPath,
49
- rendered,
50
- needsWriting,
51
- };
52
- }));
33
+ // Process each template in parallel
34
+ const processedTemplates = noClient
35
+ ? []
36
+ : await Promise.all(templateFiles.map(async ({ templatePath, outPath }) => {
37
+ // Read the EJS template
38
+ const templateContent = await fs.readFile(templatePath, 'utf-8');
39
+ // Render the template
40
+ let rendered = templatePath.endsWith('.ejs') ? ejs.render(templateContent, ejsData) : templateContent;
41
+ // Optionally prettify
42
+ if (prettifyClient || config.prettifyClient) {
43
+ rendered = await prettify(rendered, outPath);
44
+ }
45
+ // Read existing file content to compare
46
+ const existingContent = await fs.readFile(outPath, 'utf-8').catch(() => '');
47
+ // Determine if we need to rewrite the file
48
+ const needsWriting = existingContent !== rendered;
49
+ return {
50
+ outPath,
51
+ rendered,
52
+ needsWriting,
53
+ };
54
+ }));
55
+ const anyNeedsWriting = processedTemplates.some(({ needsWriting }) => needsWriting);
56
+ if (fullSchema || anyNeedsWriting) {
57
+ // Make sure the output directory exists
58
+ await fs.mkdir(clientOutDirAbsolutePath, { recursive: true });
59
+ }
53
60
  if (fullSchema) {
54
61
  const fullSchemaOutAbsolutePath = path.resolve(clientOutDirAbsolutePath, typeof fullSchema === 'string' ? fullSchema : 'full-schema.json');
55
62
  await fs.writeFile(fullSchemaOutAbsolutePath, JSON.stringify(segmentsSchema, null, 2));
56
63
  log.info(`Full schema has ben written to ${fullSchemaOutAbsolutePath}`);
57
64
  }
58
- // 2. Check if any file needs rewriting
59
- const anyNeedsWriting = processedTemplates.some(({ needsWriting }) => needsWriting);
60
65
  if (!anyNeedsWriting) {
61
66
  log.debug(`Client is up to date and doesn't need to be regenerated (${Date.now() - now}ms)`);
62
67
  return { written: false, path: clientOutDirAbsolutePath };
63
68
  }
64
- // 3. Make sure the output directory exists
65
- await fs.mkdir(clientOutDirAbsolutePath, { recursive: true });
66
- // 4. Write updated files where needed
69
+ // Write updated files where needed
67
70
  await Promise.all(processedTemplates.map(({ outPath, rendered, needsWriting }) => {
68
71
  if (needsWriting) {
69
72
  return fs.writeFile(outPath, rendered);
@@ -9,7 +9,7 @@ export default async function getConfig({ clientOutDir, cwd }) {
9
9
  modulesDir: env.VOVK_MODULES_DIR ?? conf.modulesDir ?? './' + [srcRoot, 'modules'].filter(Boolean).join('/'),
10
10
  validateOnClient: env.VOVK_VALIDATE_ON_CLIENT ?? conf.validateOnClient ?? null,
11
11
  validationLibrary: env.VOVK_VALIDATION_LIBRARY ?? conf.validationLibrary ?? null,
12
- fetcher: env.VOVK_FETCHER ?? conf.fetcher ?? 'vovk/client/defaultFetcher',
12
+ fetcher: env.VOVK_FETCHER ?? conf.fetcher ?? 'vovk/dist/client/defaultFetcher',
13
13
  schemaOutDir: env.VOVK_SCHEMA_OUT_DIR ?? conf.schemaOutDir ?? './.vovk-schema',
14
14
  clientOutDir: clientOutDir ?? env.VOVK_CLIENT_OUT_DIR ?? conf.clientOutDir ?? './node_modules/.vovk-client',
15
15
  origin: (env.VOVK_ORIGIN ?? conf.origin ?? '').replace(/\/$/, ''), // Remove trailing slash
@@ -18,7 +18,7 @@ export default async function getConfig({ clientOutDir, cwd }) {
18
18
  logLevel: env.VOVK_LOG_LEVEL ?? conf.logLevel ?? 'info',
19
19
  prettifyClient: (env.VOVK_PRETTIFY_CLIENT ? !!env.VOVK_PRETTIFY_CLIENT : null) ?? conf.prettifyClient ?? false,
20
20
  devHttps: (env.VOVK_DEV_HTTPS ? !!env.VOVK_DEV_HTTPS : null) ?? conf.devHttps ?? false,
21
- clientGenerateTemplateNames: conf.clientGenerateTemplateNames ?? ['compiled'],
21
+ experimental_clientGenerateTemplateNames: conf.experimental_clientGenerateTemplateNames ?? ['ts', 'compiled'],
22
22
  templates: {
23
23
  service: 'vovk-cli/templates/service.ejs',
24
24
  controller: 'vovk-cli/templates/controller.ejs',
package/dist/index.mjs CHANGED
@@ -19,11 +19,13 @@ initProgram(program.command('init'));
19
19
  program
20
20
  .command('dev')
21
21
  .description('Start schema watcher (optional flag --next-dev to start it with Next.js)')
22
- .option('--next-dev', 'Start schema watcher and Next.js with automatic port allocation', false)
22
+ .option('--next-dev', 'Start schema watcher and Next.js with automatic port allocation')
23
+ .option('--exit', 'Kill the processe when schema and client is generated')
23
24
  .allowUnknownOption(true)
24
25
  .action(async (options, command) => {
26
+ const { nextDev, exit = false } = options;
25
27
  const portAttempts = 30;
26
- const PORT = !options.nextDev
28
+ const PORT = !nextDev
27
29
  ? process.env.PORT
28
30
  : process.env.PORT ||
29
31
  (await getAvailablePort(3000, portAttempts, 0, (failedPort, tryingPort) =>
@@ -34,7 +36,7 @@ program
34
36
  if (!PORT) {
35
37
  throw new Error('🐺 ❌ PORT env variable is required');
36
38
  }
37
- if (options.nextDev) {
39
+ if (nextDev) {
38
40
  const { result } = concurrently([
39
41
  {
40
42
  command: `npx next dev ${command.args.join(' ')}`,
@@ -43,12 +45,17 @@ program
43
45
  },
44
46
  {
45
47
  command: `node ${import.meta.dirname}/dev/index.mjs`,
46
- name: 'Vovk Dev Command',
47
- env: { PORT, __VOVK_START_WATCHER_IN_STANDALONE_MODE__: 'true' },
48
+ name: 'Vovk Dev Watcher',
49
+ env: {
50
+ PORT,
51
+ __VOVK_START_WATCHER_IN_STANDALONE_MODE__: 'true',
52
+ __VOVK_EXIT__: exit ? 'true' : 'false',
53
+ },
48
54
  },
49
55
  ], {
50
56
  killOthers: ['failure', 'success'],
51
57
  prefix: 'none',
58
+ successCondition: 'first',
52
59
  });
53
60
  try {
54
61
  await result;
@@ -58,7 +65,7 @@ program
58
65
  }
59
66
  }
60
67
  else {
61
- void new VovkDev().start();
68
+ void new VovkDev().start({ exit });
62
69
  }
63
70
  });
64
71
  program
@@ -76,7 +83,7 @@ program
76
83
  const segments = await locateSegments({ dir: apiDir, config });
77
84
  const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
78
85
  const schemaImportUrl = pathToFileURL(path.join(schemaOutAbsolutePath, 'index.js')).href;
79
- const { default: segmentsSchema } = await import(schemaImportUrl);
86
+ const { default: segmentsSchema } = (await import(schemaImportUrl));
80
87
  await generate({ projectInfo, segments, segmentsSchema, templates, prettify, fullSchema });
81
88
  });
82
89
  program
@@ -5,7 +5,7 @@ import getNPMPackageMetadata from '../utils/getNPMPackageMetadata.mjs';
5
5
  async function updateDeps({ packageJson, packageNames, channel, key, }) {
6
6
  return Promise.all(packageNames.map(async (packageName) => {
7
7
  const metadata = await getNPMPackageMetadata(packageName);
8
- const isVovk = packageName.startsWith('vovk');
8
+ const isVovk = packageName.startsWith('vovk') && packageName !== 'vovk-mapped-types';
9
9
  const latestVersion = metadata['dist-tags'][isVovk ? (channel ?? 'latest') : 'latest'];
10
10
  if (!packageJson[key]) {
11
11
  packageJson[key] = {};
@@ -4,7 +4,7 @@ export type Segment = {
4
4
  segmentName: string;
5
5
  segmentImportPath: string;
6
6
  };
7
- export default function locateSegments({ dir, rootDir, config }: {
7
+ export default function locateSegments({ dir, rootDir, config, }: {
8
8
  dir: string;
9
9
  rootDir?: string;
10
10
  config: Required<VovkConfig> | null;
@@ -2,7 +2,7 @@ import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import getFileSystemEntryType from './utils/getFileSystemEntryType.mjs';
4
4
  // config: null is used for testing
5
- export default async function locateSegments({ dir, rootDir, config }) {
5
+ export default async function locateSegments({ dir, rootDir, config, }) {
6
6
  let results = [];
7
7
  rootDir = rootDir ?? dir;
8
8
  // Read the contents of the directory
@@ -1,24 +1,21 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- /**
4
- * Checks if a file exists at the given path.
5
- * @param {string} filePath - The path to the file.
6
- * @returns {Promise<boolean>} - A promise that resolves to true if the file exists, false otherwise.
7
- */
8
- const getFileSystemEntryType = async (filePath) => !!(await fs.stat(filePath).catch(() => false));
3
+ import getFileSystemEntryType from './utils/getFileSystemEntryType.mjs';
9
4
  async function postinstall() {
5
+ // TODO: The function doesn't consider client templates, how to do that?
10
6
  const vovk = path.join(import.meta.dirname, '../../.vovk-client');
11
7
  const js = path.join(vovk, 'compiled.js');
12
8
  const ts = path.join(vovk, 'compiled.d.ts');
13
9
  const index = path.join(vovk, 'index.ts');
14
- if ((await getFileSystemEntryType(js)) ||
15
- (await getFileSystemEntryType(ts)) ||
16
- (await getFileSystemEntryType(index))) {
17
- return;
18
- }
19
10
  await fs.mkdir(vovk, { recursive: true });
20
- await fs.writeFile(js, '/* postinstall */');
21
- await fs.writeFile(ts, '/* postinstall */');
22
- await fs.writeFile(index, '/* postinstall */');
11
+ if (!(await getFileSystemEntryType(js))) {
12
+ await fs.writeFile(js, '/* postinstall */');
13
+ }
14
+ if (!(await getFileSystemEntryType(ts))) {
15
+ await fs.writeFile(ts, '/* postinstall */');
16
+ }
17
+ if (!(await getFileSystemEntryType(index))) {
18
+ await fs.writeFile(index, '/* postinstall */');
19
+ }
23
20
  }
24
21
  void postinstall();
package/dist/types.d.mts CHANGED
@@ -16,6 +16,7 @@ export type VovkEnv = {
16
16
  VOVK_PRETTIFY_CLIENT?: string;
17
17
  VOVK_DEV_HTTPS?: string;
18
18
  __VOVK_START_WATCHER_IN_STANDALONE_MODE__?: 'true';
19
+ __VOVK_EXIT__?: 'true' | 'false';
19
20
  };
20
21
  export type VovkConfig = {
21
22
  clientOutDir?: string;
@@ -30,7 +31,7 @@ export type VovkConfig = {
30
31
  logLevel?: LogLevelNames;
31
32
  prettifyClient?: boolean;
32
33
  devHttps?: boolean;
33
- clientGenerateTemplateNames?: string[];
34
+ experimental_clientGenerateTemplateNames?: string[];
34
35
  templates?: {
35
36
  service?: string;
36
37
  controller?: string;
@@ -47,6 +48,7 @@ export type VovkModuleRenderResult = {
47
48
  };
48
49
  export interface DevOptions {
49
50
  nextDev?: boolean;
51
+ exit?: boolean;
50
52
  }
51
53
  export interface GenerateOptions {
52
54
  clientOutDir?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vovk-cli",
3
- "version": "0.0.1-draft.43",
3
+ "version": "0.0.1-draft.46",
4
4
  "bin": {
5
5
  "vovk": "./dist/index.mjs"
6
6
  },
@@ -36,27 +36,27 @@
36
36
  },
37
37
  "homepage": "https://vovk.dev",
38
38
  "peerDependencies": {
39
- "vovk": "^3.0.0-draft.35"
39
+ "vovk": "^3.0.0-draft.52"
40
40
  },
41
41
  "dependencies": {
42
- "@inquirer/prompts": "^7.1.0",
43
- "@npmcli/package-json": "^6.1.0",
44
- "chalk": "^5.3.0",
45
- "chokidar": "^4.0.1",
46
- "commander": "^12.1.0",
47
- "concurrently": "^9.1.0",
48
- "dotenv": "^16.4.5",
42
+ "@inquirer/prompts": "^7.2.3",
43
+ "@npmcli/package-json": "^6.1.1",
44
+ "chalk": "^5.4.1",
45
+ "chokidar": "^4.0.3",
46
+ "commander": "^13.1.0",
47
+ "concurrently": "^9.1.2",
48
+ "dotenv": "^16.4.7",
49
49
  "ejs": "^3.1.10",
50
50
  "gray-matter": "^4.0.3",
51
- "inflection": "^3.0.0",
51
+ "inflection": "^3.0.2",
52
52
  "jsonc-parser": "^3.3.1",
53
53
  "lodash": "^4.17.21",
54
54
  "loglevel": "^1.9.2",
55
55
  "pluralize": "^8.0.0",
56
- "prettier": "^3.4.1",
56
+ "prettier": "^3.4.2",
57
57
  "tar-stream": "^3.1.7",
58
- "ts-morph": "^24.0.0",
59
- "undici": "^7.0.0"
58
+ "ts-morph": "^25.0.0",
59
+ "undici": "^7.3.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/concat-stream": "^2.0.3",
@@ -65,8 +65,8 @@
65
65
  "@types/pluralize": "^0.0.33",
66
66
  "@types/tar-stream": "^3.1.3",
67
67
  "concat-stream": "^2.0.0",
68
- "create-next-app": "^15.0.3",
68
+ "create-next-app": "^15.1.6",
69
69
  "node-pty": "^1.0.0",
70
- "type-fest": "^4.29.0"
70
+ "type-fest": "^4.33.0"
71
71
  }
72
72
  }