vovk-cli 0.0.1-draft.3 → 0.0.1-draft.30

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 (72) hide show
  1. package/README.md +1 -1
  2. package/dist/{watcher → dev}/diffSchema.d.mts +2 -2
  3. package/dist/{watcher → dev}/diffSchema.mjs +1 -1
  4. package/dist/dev/ensureClient.d.mts +5 -0
  5. package/dist/dev/ensureClient.mjs +31 -0
  6. package/dist/{watcher → dev}/ensureSchemaFiles.mjs +20 -8
  7. package/dist/dev/index.d.mts +4 -0
  8. package/dist/{watcher → dev}/index.mjs +30 -30
  9. package/dist/{watcher → dev}/logDiffResult.d.mts +2 -2
  10. package/dist/{watcher → dev}/logDiffResult.mjs +13 -9
  11. package/dist/{watcher → dev}/writeOneSchemaFile.d.mts +1 -1
  12. package/dist/{watcher → dev}/writeOneSchemaFile.mjs +4 -3
  13. package/dist/generateClient.d.mts +1 -1
  14. package/dist/generateClient.mjs +6 -6
  15. package/dist/getProjectInfo/getConfigAbsolutePaths.mjs +2 -2
  16. package/dist/getProjectInfo/getRelativeSrcRoot.mjs +4 -4
  17. package/dist/getProjectInfo/getUserConfig.mjs +1 -1
  18. package/dist/getProjectInfo/index.mjs +1 -1
  19. package/dist/index.d.mts +2 -24
  20. package/dist/index.mjs +15 -41
  21. package/dist/init/checkTSConfigForExperimentalDecorators.mjs +2 -2
  22. package/dist/init/createConfig.d.mts +3 -4
  23. package/dist/init/createConfig.mjs +5 -5
  24. package/dist/init/getTemplateFilesFromPackage.d.mts +2 -1
  25. package/dist/init/getTemplateFilesFromPackage.mjs +3 -4
  26. package/dist/init/index.d.mts +1 -2
  27. package/dist/init/index.mjs +36 -85
  28. package/dist/init/installDependencies.d.mts +4 -1
  29. package/dist/init/installDependencies.mjs +2 -2
  30. package/dist/init/logUpdateDependenciesError.d.mts +11 -0
  31. package/dist/init/logUpdateDependenciesError.mjs +45 -0
  32. package/dist/init/updateDependenciesWithoutInstalling.d.mts +3 -2
  33. package/dist/init/updateDependenciesWithoutInstalling.mjs +12 -7
  34. package/dist/init/updateNPMScripts.mjs +2 -1
  35. package/dist/init/updateTypeScriptConfig.mjs +2 -2
  36. package/dist/initProgram.d.mts +2 -0
  37. package/dist/initProgram.mjs +21 -0
  38. package/dist/locateSegments.mjs +2 -2
  39. package/dist/new/addClassToSegmentCode.mjs +6 -2
  40. package/dist/new/index.d.mts +2 -2
  41. package/dist/new/index.mjs +13 -3
  42. package/dist/new/newModule.d.mts +6 -2
  43. package/dist/new/newModule.mjs +50 -26
  44. package/dist/new/newSegment.d.mts +3 -2
  45. package/dist/new/newSegment.mjs +5 -5
  46. package/dist/new/render.d.mts +3 -7
  47. package/dist/new/render.mjs +11 -7
  48. package/dist/postinstall.mjs +5 -3
  49. package/dist/types.d.mts +37 -2
  50. package/dist/utils/debounceWithArgs.mjs +1 -4
  51. package/dist/utils/formatLoggedSegmentName.mjs +1 -1
  52. package/dist/utils/getAvailablePort.mjs +3 -2
  53. package/dist/utils/getFileSystemEntryType.mjs +1 -1
  54. package/package.json +8 -6
  55. package/templates/controller.ejs +18 -17
  56. package/templates/service.ejs +24 -4
  57. package/templates/worker.ejs +24 -1
  58. package/dist/getProjectInfo/directoryExists.d.mts +0 -1
  59. package/dist/getProjectInfo/directoryExists.mjs +0 -10
  60. package/dist/watcher/index.d.mts +0 -6
  61. package/templates_old/MyThingController.c.only.template.ts +0 -32
  62. package/templates_old/MyThingController.c.template.ts +0 -34
  63. package/templates_old/MyThingService.s.template.ts +0 -18
  64. package/templates_old/controller.ejs +0 -85
  65. package/templates_old/service.ejs +0 -9
  66. package/templates_old/worker.ejs +0 -9
  67. package/templates_old/zod/MyThingController.c.only.template.ts +0 -32
  68. package/templates_old/zod/MyThingController.c.template.ts +0 -39
  69. package/templates_old/zod/MyThingService.s.template.ts +0 -18
  70. /package/dist/{watcher → dev}/ensureSchemaFiles.d.mts +0 -0
  71. /package/dist/{watcher → dev}/isMetadataEmpty.d.mts +0 -0
  72. /package/dist/{watcher → dev}/isMetadataEmpty.mjs +0 -0
package/README.md CHANGED
@@ -1 +1 @@
1
- Description is coming soon.
1
+ Description is coming soon.
@@ -1,5 +1,5 @@
1
1
  import type { VovkSchema } from 'vovk';
2
- import { _VovkControllerSchema, _VovkWorkerSchema } from 'vovk/types';
2
+ import type { _VovkControllerSchema, _VovkWorkerSchema } from 'vovk/types';
3
3
  interface HandlersDiff {
4
4
  nameOfClass: string;
5
5
  added: string[];
@@ -15,7 +15,7 @@ export interface DiffResult {
15
15
  workers: WorkersOrControllersDiff;
16
16
  controllers: WorkersOrControllersDiff;
17
17
  }
18
- export declare function diffHandlers<T extends _VovkWorkerSchema['_handlers'] | _VovkControllerSchema['_handlers']>(oldHandlers: T, newHandlers: T, nameOfClass: string): HandlersDiff;
18
+ export declare function diffHandlers<T extends _VovkWorkerSchema['handlers'] | _VovkControllerSchema['handlers']>(oldHandlers: T, newHandlers: T, nameOfClass: string): HandlersDiff;
19
19
  export declare function diffWorkersOrControllers<T extends VovkSchema['controllers'] | VovkSchema['workers']>(oldItems: T, newItems: T): WorkersOrControllersDiff;
20
20
  /**
21
21
  example output:
@@ -27,7 +27,7 @@ export function diffWorkersOrControllers(oldItems, newItems) {
27
27
  added.push(item);
28
28
  }
29
29
  else {
30
- const handlers = diffHandlers(oldItems[item]._handlers, newItem._handlers, item);
30
+ const handlers = diffHandlers(oldItems[item].handlers, newItem.handlers, item);
31
31
  if (handlers.added.length || handlers.removed.length || handlers.changed.length) {
32
32
  handlersDiff.push(handlers);
33
33
  }
@@ -0,0 +1,5 @@
1
+ import type { ProjectInfo } from '../getProjectInfo/index.mjs';
2
+ export default function ensureClient(projectInfo: ProjectInfo): Promise<{
3
+ written: boolean;
4
+ path: string;
5
+ }>;
@@ -0,0 +1,31 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ export default async function ensureClient(projectInfo) {
4
+ const { config, cwd, log } = projectInfo;
5
+ const now = Date.now();
6
+ const clientoOutDirAbsolutePath = path.join(cwd, config.clientOutDir);
7
+ const dts = `// auto-generated
8
+ // This is a temporary placeholder to avoid errors if client is imported before it's generated.
9
+ // If you still see this text, the client is not generated yet because of an unknown problem.
10
+ // Feel free to report an issue at https://github.com/finom/vovk/issues`;
11
+ const js = dts;
12
+ const ts = dts;
13
+ const localJsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'client.js');
14
+ const localDtsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'client.d.ts');
15
+ const localTsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'index.ts');
16
+ const existingJs = await fs.readFile(localJsAbsolutePath, 'utf-8').catch(() => null);
17
+ const existingDts = await fs.readFile(localDtsAbsolutePath, 'utf-8').catch(() => null);
18
+ const existingTs = await fs.readFile(localTsAbsolutePath, 'utf-8').catch(() => null);
19
+ if (existingJs && existingDts && existingTs) {
20
+ return { written: false, path: clientoOutDirAbsolutePath };
21
+ }
22
+ await fs.mkdir(clientoOutDirAbsolutePath, { recursive: true });
23
+ if (!existingJs)
24
+ await fs.writeFile(localJsAbsolutePath, js);
25
+ if (!existingDts)
26
+ await fs.writeFile(localDtsAbsolutePath, dts);
27
+ if (!existingTs)
28
+ await fs.writeFile(localTsAbsolutePath, ts);
29
+ log.info(`Empty client files are generated in ${Date.now() - now}ms`);
30
+ return { written: true, path: clientoOutDirAbsolutePath };
31
+ }
@@ -1,5 +1,5 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
1
+ import fs from 'node:fs/promises';
2
+ 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';
@@ -7,17 +7,29 @@ export default async function ensureSchemaFiles(projectInfo, schemaOutAbsolutePa
7
7
  const now = Date.now();
8
8
  let hasChanged = false;
9
9
  // Create index.js file
10
- const indexContent = segmentNames
10
+ const indexContent = `// auto-generated
11
+ ${segmentNames
11
12
  .map((segmentName) => {
12
13
  return `module.exports['${segmentName}'] = require('./${segmentName || ROOT_SEGMENT_SCHEMA_NAME}.json');`;
13
14
  })
14
- .join('\n');
15
- const dTsContent = `import type { VovkSchema } from 'vovk';
16
- declare const segmentSchema: Record<string, VovkSchema>;
15
+ .join('\n')}`;
16
+ const dTsContent = `// auto-generated
17
+ import type { VovkSchema } from 'vovk';
18
+ declare const segmentSchema: {
19
+ ${segmentNames.map((segmentName) => ` '${segmentName}': VovkSchema;`).join('\n')}
20
+ };
17
21
  export default segmentSchema;`;
22
+ const jsAbsolutePath = path.join(schemaOutAbsolutePath, 'index.js');
23
+ const dTsAbsolutePath = path.join(schemaOutAbsolutePath, 'index.d.ts');
24
+ const existingJs = await fs.readFile(jsAbsolutePath, 'utf-8').catch(() => null);
25
+ const existingDTs = await fs.readFile(dTsAbsolutePath, 'utf-8').catch(() => null);
18
26
  await fs.mkdir(schemaOutAbsolutePath, { recursive: true });
19
- await fs.writeFile(path.join(schemaOutAbsolutePath, 'index.js'), indexContent);
20
- await fs.writeFile(path.join(schemaOutAbsolutePath, 'index.d.ts'), dTsContent);
27
+ if (existingJs !== indexContent) {
28
+ await fs.writeFile(jsAbsolutePath, indexContent);
29
+ }
30
+ if (existingDTs !== dTsContent) {
31
+ await fs.writeFile(dTsAbsolutePath, dTsContent);
32
+ }
21
33
  // Create JSON files (if not exist) with name [segmentName].json (where segmentName can include /, which means the folder structure can be nested)
22
34
  await Promise.all(segmentNames.map(async (segmentName) => {
23
35
  const { isCreated } = await writeOneSchemaFile({
@@ -0,0 +1,4 @@
1
+ export declare class VovkDev {
2
+ #private;
3
+ start(): Promise<void>;
4
+ }
@@ -1,20 +1,21 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
1
3
  import * as chokidar from 'chokidar';
2
- import fs from 'fs/promises';
3
- import getProjectInfo from '../getProjectInfo/index.mjs';
4
- import path from 'path';
4
+ import { Agent, setGlobalDispatcher } from 'undici';
5
+ import keyBy from 'lodash/keyBy.js';
6
+ import capitalize from 'lodash/capitalize.js';
7
+ import debounce from 'lodash/debounce.js';
8
+ import isEmpty from 'lodash/isEmpty.js';
5
9
  import { debouncedEnsureSchemaFiles } from './ensureSchemaFiles.mjs';
6
10
  import writeOneSchemaFile from './writeOneSchemaFile.mjs';
7
11
  import logDiffResult from './logDiffResult.mjs';
12
+ import ensureClient from './ensureClient.mjs';
13
+ import getProjectInfo from '../getProjectInfo/index.mjs';
8
14
  import generateClient from '../generateClient.mjs';
9
15
  import locateSegments from '../locateSegments.mjs';
10
16
  import debounceWithArgs from '../utils/debounceWithArgs.mjs';
11
- import debounce from 'lodash/debounce.js';
12
- import isEmpty from 'lodash/isEmpty.js';
13
17
  import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
14
- import keyBy from 'lodash/keyBy.js';
15
- import capitalize from 'lodash/capitalize.js';
16
- import { Agent, setGlobalDispatcher } from 'undici';
17
- export class VovkCLIWatcher {
18
+ export class VovkDev {
18
19
  #projectInfo;
19
20
  #segments = [];
20
21
  #schemas = {};
@@ -27,7 +28,7 @@ export class VovkCLIWatcher {
27
28
  const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
28
29
  const apiDirAbsolutePath = path.join(cwd, apiDir);
29
30
  const getSegmentName = (filePath) => path.relative(apiDirAbsolutePath, filePath).replace(segmentReg, '');
30
- log.debug(`Watching segments in ${apiDirAbsolutePath}`);
31
+ log.debug(`Watching segments at ${apiDirAbsolutePath}`);
31
32
  this.#segmentWatcher = chokidar
32
33
  .watch(apiDirAbsolutePath, {
33
34
  persistent: true,
@@ -51,7 +52,6 @@ export class VovkCLIWatcher {
51
52
  void this.#requestSchema(getSegmentName(filePath));
52
53
  }
53
54
  })
54
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
55
55
  .on('addDir', async (dirPath) => {
56
56
  log.debug(`Directory ${dirPath} has been added to segments folder`);
57
57
  this.#segments = await locateSegments(apiDirAbsolutePath);
@@ -59,7 +59,6 @@ export class VovkCLIWatcher {
59
59
  void this.#requestSchema(segmentName);
60
60
  }
61
61
  })
62
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
63
62
  .on('unlinkDir', async (dirPath) => {
64
63
  log.debug(`Directory ${dirPath} has been removed from segments folder`);
65
64
  this.#segments = await locateSegments(apiDirAbsolutePath);
@@ -81,13 +80,13 @@ export class VovkCLIWatcher {
81
80
  log.debug('Segments watcher is ready');
82
81
  })
83
82
  .on('error', (error) => {
84
- log.error(`Error watching segments folder: ${error.message}`);
83
+ log.error(`Error watching segments folder: ${error?.message ?? 'Unknown error'}`);
85
84
  });
86
85
  };
87
86
  #watchModules = () => {
88
87
  const { config, cwd, log } = this.#projectInfo;
89
88
  const modulesDirAbsolutePath = path.join(cwd, config.modulesDir);
90
- log.debug(`Watching modules in ${modulesDirAbsolutePath}`);
89
+ log.debug(`Watching modules at ${modulesDirAbsolutePath}`);
91
90
  const processControllerChange = debounceWithArgs(this.#processControllerChange, 500);
92
91
  this.#modulesWatcher = chokidar
93
92
  .watch(modulesDirAbsolutePath, {
@@ -119,13 +118,14 @@ export class VovkCLIWatcher {
119
118
  log.debug('Modules watcher is ready');
120
119
  })
121
120
  .on('error', (error) => {
122
- log.error(`Error watching modules folder: ${error.message}`);
121
+ log.error(`Error watching modules folder: ${error?.message ?? 'Unknown error'}`);
123
122
  });
124
123
  };
125
124
  #watchConfig = () => {
126
125
  const { log, cwd } = this.#projectInfo;
127
126
  log.debug(`Watching config files`);
128
127
  let isInitial = true;
128
+ let isReady = false;
129
129
  const handle = debounce(async () => {
130
130
  this.#projectInfo = await getProjectInfo();
131
131
  if (!isInitial) {
@@ -137,7 +137,6 @@ export class VovkCLIWatcher {
137
137
  this.#watchModules();
138
138
  this.#watchSegments();
139
139
  }, 1000);
140
- let ready = false;
141
140
  chokidar
142
141
  .watch(['vovk.config.{js,mjs,cjs}', '.config/vovk.config.{js,mjs,cjs}'], {
143
142
  persistent: true,
@@ -149,22 +148,21 @@ export class VovkCLIWatcher {
149
148
  .on('change', () => void handle())
150
149
  .on('unlink', () => void handle())
151
150
  .on('ready', () => {
152
- if (ready)
151
+ if (isReady)
153
152
  return;
154
153
  // for some reason this watcher triggers ready event twice
155
154
  log.debug('Config files watcher is ready');
156
- ready = true;
155
+ isReady = true;
157
156
  })
158
- .on('error', (error) => {
159
- log.error(`Error watching config files: ${error.message}`);
160
- });
157
+ .on('error', (error) => log.error(`Error watching config files: ${error?.message ?? 'Unknown error'}`));
161
158
  void handle();
162
159
  };
163
- #watch() {
160
+ async #watch() {
164
161
  if (this.#isWatching)
165
162
  throw new Error('Already watching');
166
163
  const { log } = this.#projectInfo;
167
164
  log.debug(`Starting segments and modules watcher. Detected initial segments: ${JSON.stringify(this.#segments.map((s) => s.segmentName))}.`);
165
+ await ensureClient(this.#projectInfo);
168
166
  // automatically watches segments and modules
169
167
  this.#watchConfig();
170
168
  }
@@ -183,8 +181,8 @@ export class VovkCLIWatcher {
183
181
  const schema = this.#schemas[s.segmentName];
184
182
  if (!schema)
185
183
  return false;
186
- const controllersByOriginalName = keyBy(schema.controllers, '_originalControllerName');
187
- const workersByOriginalName = keyBy(schema.workers, '_originalWorkerName');
184
+ const controllersByOriginalName = keyBy(schema.controllers, 'originalControllerName');
185
+ const workersByOriginalName = keyBy(schema.workers, 'originalWorkerName');
188
186
  return namesOfClasses.some((name) => schema.controllers[name] ||
189
187
  schema.workers[name] ||
190
188
  controllersByOriginalName[name] ||
@@ -196,6 +194,9 @@ export class VovkCLIWatcher {
196
194
  await this.#requestSchema(segment.segmentName);
197
195
  }
198
196
  }
197
+ else {
198
+ log.debug(`The class ${namesOfClasses.join(', ')} does not belong to any segment`);
199
+ }
199
200
  }
200
201
  else {
201
202
  log.debug(`The file does not contain any controller or worker`);
@@ -210,7 +211,6 @@ export class VovkCLIWatcher {
210
211
  if (resp.status !== 200) {
211
212
  const probableCause = {
212
213
  404: 'The segment is not compiled.',
213
- 500: 'Syntax error in one of controllers.',
214
214
  }[resp.status];
215
215
  log.warn(`Schema request to ${formatLoggedSegmentName(segmentName)} failed with status code ${resp.status} but expected 200.${probableCause ? ` Probable cause: ${probableCause}` : ''}`);
216
216
  return;
@@ -259,8 +259,8 @@ export class VovkCLIWatcher {
259
259
  await generateClient(this.#projectInfo, this.#segments, this.#schemas);
260
260
  }
261
261
  }
262
- async start({ clientOutDir } = {}) {
263
- this.#projectInfo = await getProjectInfo({ clientOutDir });
262
+ async start() {
263
+ this.#projectInfo = await getProjectInfo();
264
264
  const { log, config, cwd, apiDir } = this.#projectInfo;
265
265
  if (config.devHttps) {
266
266
  const agent = new Agent({
@@ -280,16 +280,16 @@ export class VovkCLIWatcher {
280
280
  const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
281
281
  this.#segments = await locateSegments(apiDirAbsolutePath);
282
282
  await debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
283
- // Request schema every segment in 3 seconds in order to update schema and start watching
283
+ // Request schema every segment in 5 seconds in order to update schema and start watching
284
284
  setTimeout(() => {
285
285
  for (const { segmentName } of this.#segments) {
286
286
  void this.#requestSchema(segmentName);
287
287
  }
288
288
  this.#watch();
289
- }, 3000);
289
+ }, 5000);
290
290
  }
291
291
  }
292
292
  const env = process.env;
293
293
  if (env.__VOVK_START_WATCHER_IN_STANDALONE_MODE__ === 'true') {
294
- void new VovkCLIWatcher().start();
294
+ void new VovkDev().start();
295
295
  }
@@ -1,3 +1,3 @@
1
- import { ProjectInfo } from '../getProjectInfo/index.mjs';
2
- import { DiffResult } from './diffSchema.mjs';
1
+ import type { DiffResult } from './diffSchema.mjs';
2
+ import type { ProjectInfo } from '../getProjectInfo/index.mjs';
3
3
  export default function logDiffResult(segmentName: string, diffResult: DiffResult, projectInfo: ProjectInfo): void;
@@ -1,3 +1,4 @@
1
+ import chalk from 'chalk';
1
2
  import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
2
3
  import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
3
4
  export default function logDiffResult(segmentName, diffResult, projectInfo) {
@@ -37,48 +38,51 @@ export default function logDiffResult(segmentName, diffResult, projectInfo) {
37
38
  });
38
39
  });
39
40
  const LIMIT = diffNormalized.length < 12 ? diffNormalized.length : 10;
41
+ const addedText = chalk.green('added');
42
+ const removedText = chalk.red('removed');
43
+ const changedText = chalk.cyan('changed');
40
44
  for (const diffNormalizedItem of diffNormalized.slice(0, LIMIT)) {
41
45
  switch (diffNormalizedItem.what) {
42
46
  case 'worker':
43
47
  switch (diffNormalizedItem.type) {
44
48
  case 'added':
45
- projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been added at ${formatLoggedSegmentName(segmentName)}`);
49
+ projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
46
50
  break;
47
51
  case 'removed':
48
- projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been removed from ${formatLoggedSegmentName(segmentName)}`);
52
+ projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
49
53
  break;
50
54
  }
51
55
  break;
52
56
  case 'controller':
53
57
  switch (diffNormalizedItem.type) {
54
58
  case 'added':
55
- projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been added at ${formatLoggedSegmentName(segmentName)}`);
59
+ projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
56
60
  break;
57
61
  case 'removed':
58
- projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been removed from ${formatLoggedSegmentName(segmentName)}`);
62
+ projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
59
63
  break;
60
64
  }
61
65
  break;
62
66
  case 'workerHandler':
63
67
  switch (diffNormalizedItem.type) {
64
68
  case 'added':
65
- projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been added at ${formatLoggedSegmentName(segmentName)}`);
69
+ projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
66
70
  break;
67
71
  case 'removed':
68
- projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been removed from ${formatLoggedSegmentName(segmentName)}`);
72
+ projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
69
73
  break;
70
74
  }
71
75
  break;
72
76
  case 'controllerHandler':
73
77
  switch (diffNormalizedItem.type) {
74
78
  case 'added':
75
- projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been added at ${formatLoggedSegmentName(segmentName)}`);
79
+ projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
76
80
  break;
77
81
  case 'removed':
78
- projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been removed from ${formatLoggedSegmentName(segmentName)}`);
82
+ projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
79
83
  break;
80
84
  case 'changed':
81
- projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been changed at ${formatLoggedSegmentName(segmentName)}`);
85
+ projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${changedText} at ${formatLoggedSegmentName(segmentName)}`);
82
86
  break;
83
87
  }
84
88
  break;
@@ -1,5 +1,5 @@
1
1
  import type { VovkSchema } from 'vovk';
2
- import { DiffResult } from './diffSchema.mjs';
2
+ import { type DiffResult } from './diffSchema.mjs';
3
3
  export declare const ROOT_SEGMENT_SCHEMA_NAME = "_root";
4
4
  export default function writeOneSchemaFile({ schemaOutAbsolutePath, schema, skipIfExists, }: {
5
5
  schemaOutAbsolutePath: string;
@@ -1,10 +1,11 @@
1
- import path from 'path';
2
- import fs from 'fs/promises';
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
3
  import diffSchema from './diffSchema.mjs';
4
+ import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
4
5
  export const ROOT_SEGMENT_SCHEMA_NAME = '_root';
5
6
  export default async function writeOneSchemaFile({ schemaOutAbsolutePath, schema, skipIfExists = false, }) {
6
7
  const segmentPath = path.join(schemaOutAbsolutePath, `${schema.segmentName || ROOT_SEGMENT_SCHEMA_NAME}.json`);
7
- if (skipIfExists) {
8
+ if (skipIfExists && (await getFileSystemEntryType(segmentPath))) {
8
9
  try {
9
10
  await fs.stat(segmentPath);
10
11
  return { isCreated: false, diffResult: null };
@@ -1,6 +1,6 @@
1
+ import type { VovkSchema } from 'vovk';
1
2
  import type { ProjectInfo } from './getProjectInfo/index.mjs';
2
3
  import type { Segment } from './locateSegments.mjs';
3
- import type { VovkSchema } from 'vovk';
4
4
  export default function generateClient(projectInfo: ProjectInfo, segments: Segment[], segmentsSchema: Record<string, VovkSchema>): Promise<{
5
5
  written: boolean;
6
6
  path: string;
@@ -1,5 +1,5 @@
1
- import path from 'path';
2
- import fs from 'fs/promises';
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
3
  import formatLoggedSegmentName from './utils/formatLoggedSegmentName.mjs';
4
4
  import prettify from './utils/prettify.mjs';
5
5
  export default async function generateClient(projectInfo, segments, segmentsSchema) {
@@ -48,11 +48,11 @@ type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
48
48
  ts += `
49
49
  ${validateOnClientImportPath ? `import validateOnClient from '${validateOnClientImportPath}';\n` : '\nconst validateOnClient = undefined;'}
50
50
  type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
51
- const prefix = '${apiEntryPoint}';
51
+ const apiRoot = '${apiEntryPoint}';
52
52
  `;
53
53
  js += `
54
54
  const { default: validateOnClient = null } = ${validateOnClientImportPath ? `require('${validateOnClientImportPath}')` : '{}'};
55
- const prefix = '${apiEntryPoint}';
55
+ const apiRoot = '${apiEntryPoint}';
56
56
  `;
57
57
  for (let i = 0; i < segments.length; i++) {
58
58
  const { segmentName } = segments[i];
@@ -64,8 +64,8 @@ const prefix = '${apiEntryPoint}';
64
64
  continue;
65
65
  for (const key of Object.keys(schema.controllers)) {
66
66
  dts += `export const ${key}: ReturnType<typeof clientizeController<Controllers${i}["${key}"], Options>>;\n`;
67
- js += `exports.${key} = clientizeController(schema['${segmentName}'].controllers.${key}, '${segmentName}', { fetcher, validateOnClient, defaultOptions: { prefix } });\n`;
68
- ts += `export const ${key} = clientizeController<Controllers${i}["${key}"], Options>(schema['${segmentName}'].controllers.${key}, '${segmentName}', { fetcher, validateOnClient, defaultOptions: { prefix } });\n`;
67
+ js += `exports.${key} = clientizeController(schema['${segmentName}'].controllers.${key}, '${segmentName}', { fetcher, validateOnClient, defaultOptions: { apiRoot } });\n`;
68
+ ts += `export const ${key} = clientizeController<Controllers${i}["${key}"], Options>(schema['${segmentName}'].controllers.${key}, '${segmentName}', { fetcher, validateOnClient, defaultOptions: { apiRoot } });\n`;
69
69
  }
70
70
  for (const key of Object.keys(schema.workers)) {
71
71
  dts += `export const ${key}: ReturnType<typeof promisifyWorker<Workers${i}["${key}"]>>;\n`;
@@ -1,5 +1,5 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
3
  export default async function getConfigAbsolutePaths({ cwd, relativePath, }) {
4
4
  const rootDir = path.resolve(cwd, relativePath || '');
5
5
  const baseName = 'vovk.config';
@@ -1,11 +1,11 @@
1
- import path from 'path';
2
- import directoryExists from './directoryExists.mjs';
1
+ import path from 'node:path';
2
+ import getFileSystemEntryType, { FileSystemEntryType } from '../utils/getFileSystemEntryType.mjs';
3
3
  export default async function getRelativeSrcRoot({ cwd }) {
4
4
  // Next.js Docs: src/app or src/pages will be ignored if app or pages are present in the root directory.
5
- if (await directoryExists(path.join(cwd, 'app'))) {
5
+ if ((await getFileSystemEntryType(path.join(cwd, 'app'))) === FileSystemEntryType.DIRECTORY) {
6
6
  return '.';
7
7
  }
8
- else if (await directoryExists(path.join(cwd, 'src/app'))) {
8
+ else if ((await getFileSystemEntryType(path.join(cwd, 'src/app'))) === FileSystemEntryType.DIRECTORY) {
9
9
  return './src';
10
10
  }
11
11
  throw new Error(`${cwd} Could not find app router directory. Check Next.js docs for more info.`);
@@ -11,7 +11,7 @@ async function getUserConfig({ cwd, }) {
11
11
  try {
12
12
  userConfig = await importUncachedModule(configPath);
13
13
  }
14
- catch (e) {
14
+ catch {
15
15
  try {
16
16
  const cacheBuster = Date.now();
17
17
  ({ default: userConfig } = (await import(`${configPath}?cache=${cacheBuster}`)));
@@ -1,4 +1,4 @@
1
- import path from 'path';
1
+ import path from 'node:path';
2
2
  import getConfig from './getConfig.mjs';
3
3
  import getLogger from '../utils/getLogger.mjs';
4
4
  export default async function getProjectInfo({ port: givenPort, clientOutDir, cwd = process.cwd(), } = {}) {
package/dist/index.d.mts CHANGED
@@ -1,26 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import type { LogLevelNames } from 'loglevel';
4
- import type { VovkConfig, VovkEnv } from './types.mjs';
2
+ import type { VovkConfig, VovkDevEnv } from './types.mjs';
5
3
  import 'dotenv/config';
6
- export type { VovkConfig, VovkEnv };
7
- export interface InitOptions {
8
- yes?: boolean;
9
- logLevel: LogLevelNames;
10
- useNpm?: boolean;
11
- useYarn?: boolean;
12
- usePnpm?: boolean;
13
- useBun?: boolean;
14
- skipInstall?: boolean;
15
- updateTsConfig?: boolean;
16
- updateScripts?: 'implicit' | 'explicit';
17
- validationLibrary?: string | null;
18
- validateOnClient?: boolean;
19
- dryRun?: boolean;
20
- channel?: 'latest' | 'beta' | 'dev';
21
- }
22
- export interface NewOptions {
23
- dryRun: boolean;
24
- }
25
- declare const program: Command;
26
- export declare function initProgram(p: typeof program, command: string): Command;
4
+ export type { VovkConfig, VovkDevEnv };
package/dist/index.mjs CHANGED
@@ -1,23 +1,23 @@
1
1
  #!/usr/bin/env node
2
- import path from 'path';
2
+ import path from 'node:path';
3
+ import { readFileSync } from 'node:fs';
3
4
  import { Command } from 'commander';
4
- import { readFileSync } from 'fs';
5
5
  import concurrently from 'concurrently';
6
6
  import getAvailablePort from './utils/getAvailablePort.mjs';
7
7
  import getProjectInfo from './getProjectInfo/index.mjs';
8
8
  import generateClient from './generateClient.mjs';
9
9
  import locateSegments from './locateSegments.mjs';
10
- import { VovkCLIWatcher } from './watcher/index.mjs';
11
- import { Init } from './init/index.mjs';
10
+ import { VovkDev } from './dev/index.mjs';
12
11
  import newComponents from './new/index.mjs';
13
12
  import 'dotenv/config';
13
+ import initProgram from './initProgram.mjs';
14
14
  const program = new Command();
15
15
  const packageJSON = JSON.parse(readFileSync(path.join(import.meta.dirname, '../package.json'), 'utf-8'));
16
16
  program.name('vovk').description('Vovk CLI').version(packageJSON.version);
17
+ initProgram(program.command('init'));
17
18
  program
18
19
  .command('dev')
19
20
  .description('Start schema watcher (optional flag --next-dev to start it with Next.js)')
20
- .option('--client-out <path>', 'Path to client output directory')
21
21
  .option('--next-dev', 'Start schema watcher and Next.js with automatic port allocation', false)
22
22
  .allowUnknownOption(true)
23
23
  .action(async (options, command) => {
@@ -35,16 +35,16 @@ program
35
35
  }
36
36
  if (options.nextDev) {
37
37
  const { result } = concurrently([
38
- {
39
- command: `node ${import.meta.dirname}/watcher/index.mjs`,
40
- name: 'Vovk.ts Schema Watcher',
41
- env: Object.assign({ PORT, __VOVK_START_WATCHER_IN_STANDALONE_MODE__: 'true' }, options.clientOut ? { VOVK_CLIENT_OUT_DIR: options.clientOut } : {}),
42
- },
43
38
  {
44
39
  command: `npx next dev ${command.args.join(' ')}`,
45
40
  name: 'Next.js Development Server',
46
41
  env: { PORT },
47
42
  },
43
+ {
44
+ command: `node ${import.meta.dirname}/dev/index.mjs`,
45
+ name: 'Vovk Dev Command',
46
+ env: { PORT, __VOVK_START_WATCHER_IN_STANDALONE_MODE__: 'true' },
47
+ },
48
48
  ], {
49
49
  killOthers: ['failure', 'success'],
50
50
  prefix: 'none',
@@ -57,7 +57,7 @@ program
57
57
  }
58
58
  }
59
59
  else {
60
- void new VovkCLIWatcher().start({ clientOutDir: options.clientOut });
60
+ void new VovkDev().start();
61
61
  }
62
62
  });
63
63
  program
@@ -72,46 +72,20 @@ program
72
72
  const schema = (await import(path.join(schemaOutAbsolutePath, 'index.js')));
73
73
  await generateClient(projectInfo, segments, schema.default);
74
74
  });
75
- // reused at vovk-init
76
- export function initProgram(p, command) {
77
- return p
78
- .command(command + '[prefix]')
79
- .description('Initialize Vovk project')
80
- .option('-Y, --yes', 'Skip all prompts and use default values')
81
- .option('--log-level <level>', 'Set log level', 'info')
82
- .option('--use-npm', 'Use npm as package manager')
83
- .option('--use-yarn', 'Use yarn as package manager')
84
- .option('--use-pnpm', 'Use pnpm as package manager')
85
- .option('--use-bun', 'Use bun as package manager')
86
- .option('--skip-install', 'Skip installing dependencies')
87
- .option('--update-ts-config', 'Update tsconfig.json')
88
- .option('--update-scripts <mode>', 'Update package.json scripts (implicit or explicit)')
89
- .option('--validation-library <library>', 'Validation library to use ("vovk-zod", "vovk-yup", "vovk-dto" or another). Set to "none" to skip validation')
90
- .option('--validate-on-client', 'Validate on client')
91
- .option('--dry-run', 'Do not write files to disk')
92
- .option('--channel <channel>', 'Channel to use for fetching packages', 'latest')
93
- .action((prefix = '.', options) => new Init().main(prefix, options));
94
- }
95
- initProgram(program, 'init ');
96
75
  program
97
76
  .command('new [components...]')
98
77
  .alias('n')
99
78
  .description('Create new components. "vovk new [...components] [segmentName/]moduleName" to create a new module or "vovk new segment [segmentName]" to create a new segment')
79
+ .option('-o, --overwrite', 'Overwrite existing files')
80
+ .option('--template, --templates <templates...>', 'Override config template. Accepts an array of strings that correspond the order of the components')
81
+ .option('--dir <dirname>', 'Override dirName in template file. Relative to the root of the project')
82
+ .option('--no-segment-update', 'Do not update segment files when creating a new module')
100
83
  .option('--dry-run', 'Do not write files to disk')
101
84
  .action((components, options) => newComponents(components, options));
102
85
  program
103
86
  .command('help')
104
87
  .description('Show help message')
105
88
  .action(() => program.help());
106
- /*
107
- TODO
108
- vovk new segment [segmentName]
109
- vovk new controller service [segmentName/]moduleName
110
- vovk new c s w [segmentName/]moduleName
111
-
112
- vovk c s w userApi/user
113
- vovk new c s w user
114
- */
115
89
  program.parse(process.argv);
116
90
  if (!process.argv.slice(2).length) {
117
91
  program.outputHelp();
@@ -1,5 +1,5 @@
1
- import path from 'path';
2
- import fs from 'fs/promises';
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
3
  import * as jsonc from 'jsonc-parser';
4
4
  export default async function checkTSConfigForExperimentalDecorators(root) {
5
5
  const tsconfigPath = path.resolve(root, 'tsconfig.json');