vovk-cli 0.0.1-beta.3 → 0.0.1-beta.31

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 +23 -0
  2. package/dist/generateClient.d.mts +7 -0
  3. package/dist/generateClient.mjs +97 -0
  4. package/dist/getProjectInfo/getConfig.d.mts +3 -1
  5. package/dist/getProjectInfo/getConfig.mjs +19 -15
  6. package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +4 -0
  7. package/dist/getProjectInfo/getConfigAbsolutePaths.mjs +22 -0
  8. package/dist/getProjectInfo/getRelativeSrcRoot.d.mts +3 -0
  9. package/dist/getProjectInfo/{getSrcRoot.mjs → getRelativeSrcRoot.mjs} +3 -4
  10. package/dist/getProjectInfo/getUserConfig.d.mts +8 -0
  11. package/dist/getProjectInfo/getUserConfig.mjs +19 -0
  12. package/dist/getProjectInfo/index.d.mts +5 -7
  13. package/dist/getProjectInfo/index.mjs +13 -22
  14. package/dist/index.d.mts +21 -1
  15. package/dist/index.mjs +52 -17
  16. package/dist/init/checkTSConfigForExperimentalDecorators.d.mts +1 -0
  17. package/dist/init/checkTSConfigForExperimentalDecorators.mjs +15 -0
  18. package/dist/init/createConfig.d.mts +7 -0
  19. package/dist/init/createConfig.mjs +38 -0
  20. package/dist/init/getTemplateFilesFromPackage.d.mts +6 -0
  21. package/dist/init/getTemplateFilesFromPackage.mjs +76 -0
  22. package/dist/init/index.d.mts +9 -0
  23. package/dist/init/index.mjs +251 -0
  24. package/dist/init/installDependencies.d.mts +9 -0
  25. package/dist/init/installDependencies.mjs +84 -0
  26. package/dist/init/updateNPMScripts.d.mts +1 -0
  27. package/dist/init/updateNPMScripts.mjs +13 -0
  28. package/dist/init/updateTSConfig.d.mts +1 -0
  29. package/dist/init/updateTSConfig.mjs +14 -0
  30. package/dist/locateSegments.mjs +2 -2
  31. package/dist/new/addClassToSegmentCode.d.mts +6 -0
  32. package/dist/new/addClassToSegmentCode.mjs +32 -0
  33. package/dist/new/addCommonTerms.d.mts +1 -0
  34. package/dist/new/addCommonTerms.mjs +91 -0
  35. package/dist/new/index.d.mts +2 -0
  36. package/dist/new/index.mjs +24 -0
  37. package/dist/new/newModule.d.mts +5 -0
  38. package/dist/new/newModule.mjs +90 -0
  39. package/dist/new/newSegment.d.mts +4 -0
  40. package/dist/new/newSegment.mjs +33 -0
  41. package/dist/new/render.d.mts +12 -0
  42. package/dist/new/render.mjs +28 -0
  43. package/dist/postinstall.mjs +2 -2
  44. package/dist/types.d.mts +10 -4
  45. package/dist/utils/chalkHighlightThing.d.mts +1 -0
  46. package/dist/utils/chalkHighlightThing.mjs +4 -0
  47. package/dist/utils/formatLoggedSegmentName.d.mts +4 -0
  48. package/dist/utils/formatLoggedSegmentName.mjs +7 -0
  49. package/dist/utils/getFileSystemEntryType.d.mts +5 -0
  50. package/dist/utils/getFileSystemEntryType.mjs +23 -0
  51. package/dist/utils/getLogger.d.mts +8 -0
  52. package/dist/utils/getLogger.mjs +13 -0
  53. package/dist/utils/prettify.d.mts +1 -0
  54. package/dist/utils/prettify.mjs +10 -0
  55. package/dist/{server/diffMetadata.d.mts → watcher/diffSchema.d.mts} +5 -5
  56. package/dist/{server/diffMetadata.mjs → watcher/diffSchema.mjs} +1 -1
  57. package/dist/watcher/ensureSchemaFiles.d.mts +3 -0
  58. package/dist/{server/ensureMetadataFiles.mjs → watcher/ensureSchemaFiles.mjs} +26 -25
  59. package/dist/watcher/index.d.mts +6 -0
  60. package/dist/watcher/index.mjs +279 -0
  61. package/dist/watcher/isMetadataEmpty.d.mts +2 -0
  62. package/dist/watcher/isMetadataEmpty.mjs +4 -0
  63. package/dist/{server → watcher}/logDiffResult.d.mts +1 -1
  64. package/dist/watcher/logDiffResult.mjs +90 -0
  65. package/dist/watcher/writeOneSchemaFile.d.mts +11 -0
  66. package/dist/{server/writeOneMetadataFile.mjs → watcher/writeOneSchemaFile.mjs} +7 -7
  67. package/package.json +25 -6
  68. package/templates/controller.ejs +1 -0
  69. package/templates/service.ejs +1 -0
  70. package/templates/worker.ejs +1 -0
  71. package/templates_old/MyThingController.c.only.template.ts +32 -0
  72. package/templates_old/MyThingController.c.template.ts +34 -0
  73. package/templates_old/MyThingService.s.template.ts +18 -0
  74. package/templates_old/controller.ejs +85 -0
  75. package/templates_old/service.ejs +9 -0
  76. package/templates_old/worker.ejs +9 -0
  77. package/templates_old/zod/MyThingController.c.only.template.ts +32 -0
  78. package/templates_old/zod/MyThingController.c.template.ts +39 -0
  79. package/templates_old/zod/MyThingService.s.template.ts +18 -0
  80. package/tmp_test_dir/README.md +36 -0
  81. package/tmp_test_dir/next.config.mjs +4 -0
  82. package/tmp_test_dir/package-lock.json +2914 -0
  83. package/tmp_test_dir/package.json +24 -0
  84. package/tmp_test_dir/vovk.config.js +11 -0
  85. package/.eslintrc.mjs +0 -20
  86. package/dist/getProjectInfo/getCwdPath.d.mts +0 -1
  87. package/dist/getProjectInfo/getCwdPath.mjs +0 -13
  88. package/dist/getProjectInfo/getSrcRoot.d.mts +0 -1
  89. package/dist/getProjectInfo/readConfig.d.mts +0 -3
  90. package/dist/getProjectInfo/readConfig.mjs +0 -37
  91. package/dist/init.d.mts +0 -2
  92. package/dist/init.mjs +0 -171
  93. package/dist/server/createMetadataServer.d.mts +0 -5
  94. package/dist/server/createMetadataServer.mjs +0 -25
  95. package/dist/server/ensureMetadataFiles.d.mts +0 -3
  96. package/dist/server/generateClient.d.mts +0 -7
  97. package/dist/server/generateClient.mjs +0 -92
  98. package/dist/server/index.d.mts +0 -6
  99. package/dist/server/index.mjs +0 -256
  100. package/dist/server/isMetadataEmpty.d.mts +0 -2
  101. package/dist/server/isMetadataEmpty.mjs +0 -4
  102. package/dist/server/logDiffResult.mjs +0 -78
  103. package/dist/server/writeOneMetadataFile.d.mts +0 -11
  104. package/dist/utils/fileExists.d.mts +0 -1
  105. package/dist/utils/fileExists.mjs +0 -10
  106. package/src/getProjectInfo/directoryExists.mts +0 -10
  107. package/src/getProjectInfo/getConfig.mts +0 -29
  108. package/src/getProjectInfo/getCwdPath.mts +0 -15
  109. package/src/getProjectInfo/getSrcRoot.mts +0 -14
  110. package/src/getProjectInfo/index.mts +0 -57
  111. package/src/getProjectInfo/readConfig.mts +0 -44
  112. package/src/index.mts +0 -113
  113. package/src/init.mts +0 -174
  114. package/src/locateSegments.mts +0 -40
  115. package/src/postinstall.mts +0 -27
  116. package/src/server/createMetadataServer.mts +0 -30
  117. package/src/server/diffMetadata.mts +0 -110
  118. package/src/server/ensureMetadataFiles.mts +0 -92
  119. package/src/server/generateClient.mts +0 -108
  120. package/src/server/index.mts +0 -307
  121. package/src/server/isMetadataEmpty.mts +0 -6
  122. package/src/server/logDiffResult.mts +0 -114
  123. package/src/server/writeOneMetadataFile.mts +0 -44
  124. package/src/types.mts +0 -58
  125. package/src/utils/debounceWithArgs.mts +0 -22
  126. package/src/utils/fileExists.mts +0 -10
  127. package/src/utils/getAvailablePort.mts +0 -50
  128. package/test/data/segments/[[...vovk]]/route.ts +0 -0
  129. package/test/data/segments/bar/[[...custom]]/route.ts +0 -0
  130. package/test/data/segments/baz/[[...vovk]]/noroute.ts +0 -0
  131. package/test/data/segments/foo/[[...vovk]]/route.ts +0 -0
  132. package/test/data/segments/garply/waldo/route.ts +0 -0
  133. package/test/data/segments/grault/xxxx/[[...vovk]]/noroute.ts +0 -0
  134. package/test/data/segments/quux/corge/[[...vovk]]/route.ts +0 -0
  135. package/test/index.ts +0 -3
  136. package/test/metadata-diff.test.mts +0 -300
  137. package/test/metadata-write.test.mts +0 -82
  138. package/test/utils.test.mts +0 -49
  139. package/tsconfig.json +0 -17
  140. package/tsconfig.test.json +0 -4
package/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023-present Andrii Gubanov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+
@@ -0,0 +1,7 @@
1
+ import type { ProjectInfo } from './getProjectInfo/index.mjs';
2
+ import type { Segment } from './locateSegments.mjs';
3
+ import type { VovkSchema } from 'vovk';
4
+ export default function generateClient(projectInfo: ProjectInfo, segments: Segment[], segmentsSchema: Record<string, VovkSchema>): Promise<{
5
+ written: boolean;
6
+ path: string;
7
+ }>;
@@ -0,0 +1,97 @@
1
+ import path from 'path';
2
+ import fs from 'fs/promises';
3
+ import formatLoggedSegmentName from './utils/formatLoggedSegmentName.mjs';
4
+ import prettify from './utils/prettify.mjs';
5
+ export default async function generateClient(projectInfo, segments, segmentsSchema) {
6
+ const { config, cwd, log, validateOnClientImportPath, apiEntryPoint, fetcherClientImportPath, schemaOutImportPath } = projectInfo;
7
+ const now = Date.now();
8
+ const clientoOutDirAbsolutePath = path.join(cwd, config.clientOutDir);
9
+ let dts = `// auto-generated
10
+ /* eslint-disable */
11
+ import type { clientizeController } from 'vovk/client';
12
+ import type { promisifyWorker } from 'vovk/worker';
13
+ import type { VovkClientFetcher } from 'vovk/client';
14
+ import type fetcher from '${fetcherClientImportPath}';
15
+
16
+ `;
17
+ let js = `// auto-generated
18
+ /* eslint-disable */
19
+ const { clientizeController } = require('vovk/client');
20
+ const { promisifyWorker } = require('vovk/worker');
21
+ const { default: fetcher } = require('${fetcherClientImportPath}');
22
+ const schema = require('${schemaOutImportPath}');
23
+ `;
24
+ let ts = `// auto-generated
25
+ /* eslint-disable */
26
+ import { clientizeController } from 'vovk/client';
27
+ import { promisifyWorker } from 'vovk/worker';
28
+ import type { VovkClientFetcher } from 'vovk/client';
29
+ import fetcher from '${fetcherClientImportPath}';
30
+ import schema from '${schemaOutImportPath}';
31
+
32
+ `;
33
+ for (let i = 0; i < segments.length; i++) {
34
+ const { routeFilePath, segmentName } = segments[i];
35
+ const schema = segmentsSchema[segmentName];
36
+ if (!schema) {
37
+ throw new Error(`Unable to generate client. No schema found for ${formatLoggedSegmentName(segmentName)}`);
38
+ }
39
+ if (!schema.emitSchema)
40
+ continue;
41
+ const importRouteFilePath = path.relative(config.clientOutDir, routeFilePath);
42
+ dts += `import type { Controllers as Controllers${i}, Workers as Workers${i} } from "${importRouteFilePath}";\n`;
43
+ ts += `import type { Controllers as Controllers${i}, Workers as Workers${i} } from "${importRouteFilePath}";\n`;
44
+ }
45
+ dts += `
46
+ type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
47
+ `;
48
+ ts += `
49
+ ${validateOnClientImportPath ? `import validateOnClient from '${validateOnClientImportPath}';\n` : '\nconst validateOnClient = undefined;'}
50
+ type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
51
+ const prefix = '${apiEntryPoint}';
52
+ `;
53
+ js += `
54
+ const { default: validateOnClient = null } = ${validateOnClientImportPath ? `require('${validateOnClientImportPath}')` : '{}'};
55
+ const prefix = '${apiEntryPoint}';
56
+ `;
57
+ for (let i = 0; i < segments.length; i++) {
58
+ const { segmentName } = segments[i];
59
+ const schema = segmentsSchema[segmentName];
60
+ if (!schema) {
61
+ throw new Error(`Unable to generate client. No schema found for ${formatLoggedSegmentName(segmentName)}`);
62
+ }
63
+ if (!schema.emitSchema)
64
+ continue;
65
+ for (const key of Object.keys(schema.controllers)) {
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`;
69
+ }
70
+ for (const key of Object.keys(schema.workers)) {
71
+ dts += `export const ${key}: ReturnType<typeof promisifyWorker<Workers${i}["${key}"]>>;\n`;
72
+ js += `exports.${key} = promisifyWorker(null, schema['${segmentName}'].workers.${key});\n`;
73
+ ts += `export const ${key} = promisifyWorker<Workers${i}["${key}"]>(null, schema['${segmentName}'].workers.${key});\n`;
74
+ }
75
+ }
76
+ const localJsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'client.js');
77
+ const localDtsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'client.d.ts');
78
+ const localTsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'index.ts');
79
+ const existingJs = await fs.readFile(localJsAbsolutePath, 'utf-8').catch(() => '');
80
+ const existingDts = await fs.readFile(localDtsAbsolutePath, 'utf-8').catch(() => '');
81
+ const existingTs = await fs.readFile(localTsAbsolutePath, 'utf-8').catch(() => '');
82
+ if (config.prettifyClient) {
83
+ js = await prettify(js, localJsAbsolutePath);
84
+ dts = await prettify(dts, localDtsAbsolutePath);
85
+ ts = await prettify(ts, localTsAbsolutePath);
86
+ }
87
+ if (existingJs === js && existingDts === dts && existingTs === ts) {
88
+ log.debug(`Client is up to date and doesn't need to be regenerated (${Date.now() - now}ms)`);
89
+ return { written: false, path: clientoOutDirAbsolutePath };
90
+ }
91
+ await fs.mkdir(clientoOutDirAbsolutePath, { recursive: true });
92
+ await fs.writeFile(localJsAbsolutePath, js);
93
+ await fs.writeFile(localDtsAbsolutePath, dts);
94
+ await fs.writeFile(localTsAbsolutePath, ts);
95
+ log.info(`Client generated in ${Date.now() - now}ms`);
96
+ return { written: true, path: clientoOutDirAbsolutePath };
97
+ }
@@ -1,7 +1,9 @@
1
1
  import type { VovkConfig } from '../types.mjs';
2
- export default function getConfig({ clientOutDir }: {
2
+ export default function getConfig({ clientOutDir, cwd }: {
3
3
  clientOutDir?: string;
4
+ cwd: string;
4
5
  }): Promise<{
5
6
  config: Required<VovkConfig>;
6
7
  srcRoot: string;
8
+ configAbsolutePaths: string[];
7
9
  }>;
@@ -1,23 +1,27 @@
1
- import readConfig from './readConfig.mjs';
2
- import getCwdPath from './getCwdPath.mjs';
3
- import getSrcRoot from './getSrcRoot.mjs';
4
- import path from 'path';
5
- export default async function getConfig({ clientOutDir }) {
1
+ import getUserConfig from './getUserConfig.mjs';
2
+ import getRelativeSrcRoot from './getRelativeSrcRoot.mjs';
3
+ export default async function getConfig({ clientOutDir, cwd }) {
6
4
  const env = process.env;
7
- const userConfig = await readConfig();
8
- const srcRoot = await getSrcRoot();
9
- const cwd = process.cwd();
5
+ const { userConfig, configAbsolutePaths } = await getUserConfig({ cwd });
6
+ const srcRoot = await getRelativeSrcRoot({ cwd });
10
7
  const config = {
11
- modulesDir: path.join(cwd, env.VOVK_MODULES_DIR ?? userConfig.modulesDir ?? './' + [srcRoot, 'modules'].filter(Boolean).join('/')),
12
- validateOnClient: getCwdPath(env.VOVK_VALIDATE_ON_CLIENT ?? userConfig.validateOnClient ?? null),
13
- validationLibrary: getCwdPath(env.VOVK_VALIDATION_LIBRARY ?? userConfig.validationLibrary ?? null),
14
- fetcher: getCwdPath(env.VOVK_FETCHER ?? userConfig.fetcher ?? 'vovk/client/defaultFetcher'),
15
- metadataOutDir: env.VOVK_METADATA_OUT_DIR ?? userConfig.metadataOutDir ?? './.vovk-schema',
16
- clientOutDir: clientOutDir ?? env.VOVK_CLIENT_OUT_DIR ?? userConfig.clientOutDir ?? './node_modules/.vovk',
8
+ modulesDir: env.VOVK_MODULES_DIR ?? userConfig.modulesDir ?? './' + [srcRoot, 'modules'].filter(Boolean).join('/'),
9
+ validateOnClient: env.VOVK_VALIDATE_ON_CLIENT ?? userConfig.validateOnClient ?? null,
10
+ validationLibrary: env.VOVK_VALIDATION_LIBRARY ?? userConfig.validationLibrary ?? null,
11
+ fetcher: env.VOVK_FETCHER ?? userConfig.fetcher ?? 'vovk/client/defaultFetcher',
12
+ schemaOutDir: env.VOVK_SCHEMA_OUT_DIR ?? userConfig.schemaOutDir ?? './.vovk-schema',
13
+ clientOutDir: clientOutDir ?? env.VOVK_CLIENT_OUT_DIR ?? userConfig.clientOutDir ?? './node_modules/.vovk-client',
17
14
  origin: (env.VOVK_ORIGIN ?? userConfig.origin ?? '').replace(/\/$/, ''), // Remove trailing slash
18
15
  rootEntry: env.VOVK_ROOT_ENTRY ?? userConfig.rootEntry ?? 'api',
19
16
  rootSegmentModulesDirName: env.VOVK_ROOT_SEGMENT_MODULES_DIR_NAME ?? userConfig.rootSegmentModulesDirName ?? '',
20
17
  logLevel: env.VOVK_LOG_LEVEL ?? userConfig.logLevel ?? 'debug', // TODO: change to 'warn' when v3 is ready
18
+ prettifyClient: userConfig.prettifyClient ?? false,
19
+ templates: {
20
+ service: 'vovk-cli/templates/service.ejs',
21
+ controller: 'vovk-cli/templates/controller.ejs',
22
+ worker: 'vovk-cli/templates/worker.ejs',
23
+ ...userConfig.templates,
24
+ },
21
25
  };
22
- return { config, srcRoot };
26
+ return { config, srcRoot, configAbsolutePaths };
23
27
  }
@@ -0,0 +1,4 @@
1
+ export default function getConfigAbsolutePaths({ cwd, relativePath, }: {
2
+ cwd: string;
3
+ relativePath?: string;
4
+ }): Promise<string[]>;
@@ -0,0 +1,22 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ export default async function getConfigAbsolutePaths({ cwd, relativePath, }) {
4
+ const rootDir = path.resolve(cwd, relativePath || '');
5
+ const baseName = 'vovk.config';
6
+ const extensions = ['cjs', 'mjs', 'js'];
7
+ const dirs = [path.join(rootDir, '.config'), rootDir];
8
+ const configs = [];
9
+ for (const ext of extensions) {
10
+ for (const dir of dirs) {
11
+ const filePath = path.join(dir, `${baseName}.${ext}`);
12
+ try {
13
+ await fs.stat(filePath);
14
+ configs.push(filePath); // Return the path if the file exists
15
+ }
16
+ catch {
17
+ // Empty
18
+ }
19
+ }
20
+ }
21
+ return configs;
22
+ }
@@ -0,0 +1,3 @@
1
+ export default function getRelativeSrcRoot({ cwd }: {
2
+ cwd: string;
3
+ }): Promise<"." | "./src">;
@@ -1,13 +1,12 @@
1
1
  import path from 'path';
2
2
  import directoryExists from './directoryExists.mjs';
3
- export default async function getSrcRoot() {
4
- const cwd = process.cwd();
3
+ export default async function getRelativeSrcRoot({ cwd }) {
5
4
  // Next.js Docs: src/app or src/pages will be ignored if app or pages are present in the root directory.
6
5
  if (await directoryExists(path.join(cwd, 'app'))) {
7
- return cwd;
6
+ return '.';
8
7
  }
9
8
  else if (await directoryExists(path.join(cwd, 'src/app'))) {
10
- return path.join(cwd, 'src');
9
+ return './src';
11
10
  }
12
11
  throw new Error(`Could not find app router directory. Check Next.js docs for more info.`);
13
12
  }
@@ -0,0 +1,8 @@
1
+ import type { VovkConfig } from '../types.mjs';
2
+ declare function getUserConfig({ cwd, }: {
3
+ cwd: string;
4
+ }): Promise<{
5
+ userConfig: VovkConfig;
6
+ configAbsolutePaths: string[];
7
+ }>;
8
+ export default getUserConfig;
@@ -0,0 +1,19 @@
1
+ import getConfigAbsolutePaths from './getConfigAbsolutePaths.mjs';
2
+ async function getUserConfig({ cwd, }) {
3
+ const configAbsolutePaths = await getConfigAbsolutePaths({ cwd });
4
+ let userConfig = {};
5
+ if (!configAbsolutePaths.length) {
6
+ return { userConfig, configAbsolutePaths };
7
+ }
8
+ const configPath = configAbsolutePaths[0];
9
+ try {
10
+ const cacheBuster = Date.now();
11
+ ({ default: userConfig } = (await import(`${configPath}?cache=${cacheBuster}`)));
12
+ }
13
+ catch (e) {
14
+ // eslint-disable-next-line no-console
15
+ console.error('🐺 ❌ Error reading config file:', e.message);
16
+ }
17
+ return { userConfig, configAbsolutePaths };
18
+ }
19
+ export default getUserConfig;
@@ -1,25 +1,23 @@
1
- import loglevel from 'loglevel';
2
1
  export type ProjectInfo = Awaited<ReturnType<typeof getProjectInfo>>;
3
- export default function getProjectInfo({ port: givenPort, clientOutDir, }?: {
2
+ export default function getProjectInfo({ port: givenPort, clientOutDir, cwd, }?: {
4
3
  port?: number;
5
4
  clientOutDir?: string;
5
+ cwd?: string;
6
6
  }): Promise<{
7
7
  cwd: string;
8
8
  port: string;
9
- vovkPort: string;
10
9
  apiEntryPoint: string;
11
10
  apiDir: string;
12
11
  srcRoot: string;
13
- metadataOutFullPath: string;
14
- metadataOutImportPath: string;
15
- clientOutFullPath: string;
12
+ schemaOutImportPath: string;
16
13
  fetcherClientImportPath: string;
14
+ validateOnClientImportPath: string | null;
17
15
  config: Required<import("../types.mjs").VovkConfig>;
18
16
  log: {
19
17
  info: (msg: string) => void;
20
18
  warn: (msg: string) => void;
21
19
  error: (msg: string) => void;
22
20
  debug: (msg: string) => void;
23
- raw: loglevel.RootLogger;
21
+ raw: import("loglevel").RootLogger;
24
22
  };
25
23
  }>;
@@ -1,42 +1,33 @@
1
1
  import path from 'path';
2
- import loglevel from 'loglevel';
3
- import chalk from 'chalk';
4
2
  import getConfig from './getConfig.mjs';
5
- export default async function getProjectInfo({ port: givenPort, clientOutDir, } = {}) {
6
- const env = process.env;
3
+ import getLogger from '../utils/getLogger.mjs';
4
+ export default async function getProjectInfo({ port: givenPort, clientOutDir, cwd = process.cwd(), } = {}) {
7
5
  const port = givenPort?.toString() ?? process.env.PORT ?? '3000';
8
6
  // Make PORT available to the config file at getConfig
9
7
  process.env.PORT = port;
10
- const cwd = process.cwd();
11
- const { config, srcRoot } = await getConfig({ clientOutDir });
12
- const vovkPort = env.VOVK_PORT || (parseInt(port) + 6969).toString();
8
+ const { config, srcRoot, configAbsolutePaths } = await getConfig({ clientOutDir, cwd });
13
9
  const apiEntryPoint = `${config.origin ?? ''}/${config.rootEntry}`;
14
10
  const apiDir = path.join(srcRoot, 'app', config.rootEntry);
15
- const metadataOutFullPath = path.join(cwd, config.metadataOutDir);
16
- const metadataOutImportPath = path.relative(config.clientOutDir, metadataOutFullPath);
11
+ const schemaOutImportPath = path.relative(config.clientOutDir, config.schemaOutDir);
17
12
  const fetcherClientImportPath = config.fetcher.startsWith('.')
18
13
  ? path.relative(config.clientOutDir, config.fetcher)
19
14
  : config.fetcher;
20
- const clientOutFullPath = path.join(cwd, config.clientOutDir);
21
- const log = {
22
- info: (msg) => loglevel.info(chalk.blueBright(`🐺 ${msg}`)),
23
- warn: (msg) => loglevel.warn(chalk.yellowBright(`🐺 ${msg}`)),
24
- error: (msg) => loglevel.error(chalk.redBright(`🐺 ${msg}`)),
25
- debug: (msg) => loglevel.debug(chalk.gray(`🐺 ${msg}`)),
26
- raw: loglevel,
27
- };
28
- loglevel.setLevel(config.logLevel);
15
+ const validateOnClientImportPath = config.validateOnClient?.startsWith('.')
16
+ ? path.relative(config.clientOutDir, config.validateOnClient)
17
+ : config.validateOnClient;
18
+ const log = getLogger(config.logLevel);
19
+ if (configAbsolutePaths.length > 1) {
20
+ log.warn(`Multiple config files found. Using the first one: ${configAbsolutePaths[0]}`);
21
+ }
29
22
  return {
30
23
  cwd,
31
24
  port,
32
- vovkPort,
33
25
  apiEntryPoint,
34
26
  apiDir,
35
27
  srcRoot,
36
- metadataOutFullPath,
37
- metadataOutImportPath,
38
- clientOutFullPath,
28
+ schemaOutImportPath,
39
29
  fetcherClientImportPath,
30
+ validateOnClientImportPath,
40
31
  config,
41
32
  log,
42
33
  };
package/dist/index.d.mts CHANGED
@@ -1,3 +1,23 @@
1
1
  #!/usr/bin/env node
2
- import { VovkConfig, VovkEnv } from './types.mjs';
2
+ import { Command } from 'commander';
3
+ import type { LogLevelNames } from 'loglevel';
4
+ import type { VovkConfig, VovkEnv } from './types.mjs';
3
5
  export type { VovkConfig, VovkEnv };
6
+ export interface InitOptions {
7
+ yes?: boolean;
8
+ logLevel: LogLevelNames;
9
+ useNpm?: boolean;
10
+ useYarn?: boolean;
11
+ usePnpm?: boolean;
12
+ useBun?: boolean;
13
+ skipInstall?: boolean;
14
+ updateTsConfig?: boolean;
15
+ updateScripts?: 'implicit' | 'explicit';
16
+ validationLibrary?: string;
17
+ validateOnClient?: boolean;
18
+ }
19
+ export interface NewOptions {
20
+ dryRun: boolean;
21
+ }
22
+ declare const program: Command;
23
+ export declare function initProgram(p: typeof program, command: string): Command;
package/dist/index.mjs CHANGED
@@ -1,22 +1,23 @@
1
1
  #!/usr/bin/env node
2
+ import path from 'path';
2
3
  import { Command } from 'commander';
4
+ import { readFileSync } from 'fs';
3
5
  import concurrently from 'concurrently';
4
6
  import getAvailablePort from './utils/getAvailablePort.mjs';
5
- import { VovkCLIServer } from './server/index.mjs';
6
7
  import getProjectInfo from './getProjectInfo/index.mjs';
7
- import generateClient from './server/generateClient.mjs';
8
+ import generateClient from './generateClient.mjs';
8
9
  import locateSegments from './locateSegments.mjs';
9
- import path from 'path';
10
- import { readFileSync } from 'fs';
10
+ import { VovkCLIWatcher } from './watcher/index.mjs';
11
+ import { Init } from './init/index.mjs';
12
+ import newComponents from './new/index.mjs';
11
13
  const program = new Command();
12
14
  const packageJSON = JSON.parse(readFileSync(path.join(import.meta.dirname, '../package.json'), 'utf-8'));
13
15
  program.name('vovk').description('Vovk CLI').version(packageJSON.version);
14
16
  program
15
17
  .command('dev')
16
- .description('Start development server (optional flag --next-dev to start Vovk Server with Next.js)')
17
- .option('--project <path>', 'Path to Next.js project', process.cwd())
18
+ .description('Start schema watcher (optional flag --next-dev to start it with Next.js)')
18
19
  .option('--client-out <path>', 'Path to client output directory')
19
- .option('--next-dev', 'Start Vovk Server and Next.js with automatic port allocation', false)
20
+ .option('--next-dev', 'Start schema watcher and Next.js with automatic port allocation', false)
20
21
  .allowUnknownOption(true)
21
22
  .action(async (options, command) => {
22
23
  const portAttempts = 30;
@@ -34,12 +35,12 @@ program
34
35
  if (options.nextDev) {
35
36
  const { result } = concurrently([
36
37
  {
37
- command: `node ${import.meta.dirname}/server/index.mjs`,
38
- name: 'Vovk.ts Metadata Server',
39
- env: Object.assign({ PORT, __VOVK_START_SERVER_IN_STANDALONE_MODE__: 'true' }, options.clientOut ? { VOVK_CLIENT_OUT_DIR: options.clientOut } : {}),
38
+ command: `node ${import.meta.dirname}/watcher/index.mjs`,
39
+ name: 'Vovk.ts Schema Watcher',
40
+ env: Object.assign({ PORT, __VOVK_START_WATCHER_IN_STANDALONE_MODE__: 'true' }, options.clientOut ? { VOVK_CLIENT_OUT_DIR: options.clientOut } : {}),
40
41
  },
41
42
  {
42
- command: `cd ${options.project} && npx next dev ${command.args.join(' ')}`,
43
+ command: `npx next dev ${command.args.join(' ')}`,
43
44
  name: 'Next.js Development Server',
44
45
  env: { PORT },
45
46
  },
@@ -51,12 +52,11 @@ program
51
52
  await result;
52
53
  }
53
54
  finally {
54
- // eslint-disable-next-line no-console
55
- console.log('🐺 Exiting...');
55
+ // do nothing, all processes are killed
56
56
  }
57
57
  }
58
58
  else {
59
- void new VovkCLIServer().startServer({ clientOutDir: options.clientOut });
59
+ void new VovkCLIWatcher().start({ clientOutDir: options.clientOut });
60
60
  }
61
61
  });
62
62
  program
@@ -65,14 +65,49 @@ program
65
65
  .option('--client-out <path>', 'Path to output directory')
66
66
  .action(async (options) => {
67
67
  const projectInfo = await getProjectInfo({ clientOutDir: options.clientOut });
68
- const segments = await locateSegments(projectInfo.apiDir);
69
- const metadata = (await import(path.join(projectInfo.metadataOutFullPath, 'index.js')));
70
- await generateClient(projectInfo, segments, metadata.default);
68
+ const { cwd, config, apiDir } = projectInfo;
69
+ const segments = await locateSegments(apiDir);
70
+ const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
71
+ const schema = (await import(path.join(schemaOutAbsolutePath, 'index.js')));
72
+ await generateClient(projectInfo, segments, schema.default);
71
73
  });
74
+ // reused at vovk-init
75
+ export function initProgram(p, command) {
76
+ return p
77
+ .command(command + '[prefix]')
78
+ .description('Initialize Vovk project')
79
+ .option('-Y, --yes', 'Skip all prompts and use default values')
80
+ .option('--log-level <level>', 'Set log level', 'debug') // TODO change to 'info'
81
+ .option('--use-npm', 'Use npm as package manager')
82
+ .option('--use-yarn', 'Use yarn as package manager')
83
+ .option('--use-pnpm', 'Use pnpm as package manager')
84
+ .option('--use-bun', 'Use bun as package manager')
85
+ .option('--skip-install', 'Skip installing dependencies')
86
+ .option('--update-ts-config', 'Update tsconfig.json')
87
+ .option('--update-scripts <mode>', 'Update package.json scripts (implicit or explicit)')
88
+ .option('--validation-library <library>', 'Validation library to use')
89
+ .option('--validate-on-client', 'Validate on client')
90
+ .action((prefix = '.', options) => new Init().main(prefix, options));
91
+ }
92
+ initProgram(program, 'init ');
93
+ program
94
+ .command('new [components...]')
95
+ .alias('n')
96
+ .description('Create new components. "vovk new [...components] [segmentName/]moduleName" to create a new module or "vovk new segment [segmentName]" to create a new segment')
97
+ .option('--dry-run', 'Do not write files to disk')
98
+ .action((components, options) => newComponents(components, options));
72
99
  program
73
100
  .command('help')
74
101
  .description('Show help message')
75
102
  .action(() => program.help());
103
+ /*
104
+ vovk new segment [segmentName]
105
+ vovk new controller service [segmentName/]moduleName
106
+ vovk new c s w [segmentName/]moduleName
107
+
108
+ vovk c s w userApi/user
109
+ vovk new c s w user
110
+ */
76
111
  program.parse(process.argv);
77
112
  if (!process.argv.slice(2).length) {
78
113
  program.outputHelp();
@@ -0,0 +1 @@
1
+ export default function checkTSConfigForExperimentalDecorators(root: string): Promise<boolean>;
@@ -0,0 +1,15 @@
1
+ import path from 'path';
2
+ import fs from 'fs/promises';
3
+ import * as jsonc from 'jsonc-parser';
4
+ export default async function checkTSConfigForExperimentalDecorators(root) {
5
+ const tsconfigPath = path.resolve(root, 'tsconfig.json');
6
+ let tsconfigContent;
7
+ try {
8
+ tsconfigContent = await fs.readFile(tsconfigPath, 'utf8');
9
+ }
10
+ catch (error) {
11
+ throw new Error(`Failed to read tsconfig.json at ${tsconfigPath}. You can run "npx tsc --init" to create it. ${String(error)}`);
12
+ }
13
+ const tsconfig = jsonc.parse(tsconfigContent);
14
+ return !!tsconfig?.compilerOptions?.experimentalDecorators;
15
+ }
@@ -0,0 +1,7 @@
1
+ import type getLogger from '../utils/getLogger.mjs';
2
+ import type { InitOptions } from '../index.mjs';
3
+ export default function createConfig({ root, log, options: { validationLibrary, validateOnClient }, }: {
4
+ root: string;
5
+ log: ReturnType<typeof getLogger>;
6
+ options: Pick<InitOptions, 'validationLibrary' | 'validateOnClient'>;
7
+ }): Promise<void>;
@@ -0,0 +1,38 @@
1
+ import path from 'path';
2
+ import fs from 'fs/promises';
3
+ import getFileSystemEntryType, { FileSystemEntryType } from '../utils/getFileSystemEntryType.mjs';
4
+ import getTemplateFilesFromPackage from './getTemplateFilesFromPackage.mjs';
5
+ import prettify from '../utils/prettify.mjs';
6
+ export default async function createConfig({ root, log, options: { validationLibrary, validateOnClient }, }) {
7
+ const config = {};
8
+ const dotConfigPath = path.join(root, '.config');
9
+ const dir = (await getFileSystemEntryType(dotConfigPath)) === FileSystemEntryType.DIRECTORY ? dotConfigPath : root;
10
+ const isModule = await fs
11
+ .readFile(path.join(root, 'package.json'), 'utf-8')
12
+ .then((content) => JSON.parse(content).type === 'module');
13
+ const configAbsolutePath = path.join(dir, isModule ? 'vovk.config.mjs' : 'vovk.config.js');
14
+ config.validationLibrary = validationLibrary;
15
+ if (validateOnClient) {
16
+ config.validateOnClient = `${validationLibrary}/validateOnClient`;
17
+ }
18
+ const templates = {
19
+ controller: 'vovk-cli/templates/controller.ejs',
20
+ service: 'vovk-cli/templates/service.ejs',
21
+ worker: 'vovk-cli/templates/worker.ejs',
22
+ };
23
+ if (validationLibrary) {
24
+ try {
25
+ const validationTemplates = await getTemplateFilesFromPackage(validationLibrary);
26
+ Object.assign(templates, validationTemplates);
27
+ }
28
+ catch (error) {
29
+ log.error(`Failed to fetch validation library templates: ${error.message}`);
30
+ }
31
+ }
32
+ config.templates = templates;
33
+ const configStr = await prettify(`/** @type {import('vovk-cli').VovkConfig} */
34
+ const config = ${JSON.stringify(config, null, 2)};
35
+ ${isModule ? '\nexport default config;' : 'module.exports = config;'}`, configAbsolutePath);
36
+ await fs.writeFile(configAbsolutePath, configStr, 'utf-8');
37
+ log.info(`Config created at ${configAbsolutePath}`);
38
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Retrieves a list of files in the 'templates' folder of an NPM package.
3
+ * @param packageName - The name of the NPM package.
4
+ * @returns A promise that resolves to an array of file paths.
5
+ */
6
+ export default function getTemplatesFiles(packageName: string): Promise<Record<string, string>>;