vovk-cli 0.0.1-draft.32 → 0.0.1-draft.322

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 (130) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +29 -1
  3. package/client-templates/cjs/index.cjs.ejs +19 -0
  4. package/client-templates/cjs/index.d.cts.ejs +25 -0
  5. package/client-templates/mixins/mixins.d.ts.ejs +64 -0
  6. package/client-templates/mixins/mixins.json.ejs +1 -0
  7. package/client-templates/mjs/index.d.mts.ejs +25 -0
  8. package/client-templates/mjs/index.mjs.ejs +23 -0
  9. package/client-templates/packageJson/package.json.ejs +1 -0
  10. package/client-templates/readme/README.md.ejs +38 -0
  11. package/client-templates/schemaCjs/schema.cjs.ejs +26 -0
  12. package/client-templates/schemaCjs/schema.d.cts.ejs +10 -0
  13. package/client-templates/schemaJson/schema.json.ejs +1 -0
  14. package/client-templates/schemaTs/schema.ts.ejs +35 -0
  15. package/client-templates/ts/index.ts.ejs +33 -0
  16. package/dist/bundle/index.d.mts +8 -0
  17. package/dist/bundle/index.mjs +88 -0
  18. package/dist/dev/diffSegmentSchema.d.mts +36 -0
  19. package/dist/dev/{diffSchema.mjs → diffSegmentSchema.mjs} +3 -11
  20. package/dist/dev/ensureSchemaFiles.d.mts +3 -0
  21. package/dist/dev/ensureSchemaFiles.mjs +15 -31
  22. package/dist/dev/index.d.mts +5 -1
  23. package/dist/dev/index.mjs +181 -78
  24. package/dist/dev/logDiffResult.d.mts +1 -1
  25. package/dist/dev/logDiffResult.mjs +6 -43
  26. package/dist/dev/writeMetaJson.d.mts +2 -0
  27. package/dist/dev/writeMetaJson.mjs +17 -0
  28. package/dist/dev/writeOneSegmentSchemaFile.d.mts +12 -0
  29. package/dist/dev/{writeOneSchemaFile.mjs → writeOneSegmentSchemaFile.mjs} +10 -6
  30. package/dist/generate/ensureClient.d.mts +3 -0
  31. package/dist/generate/ensureClient.mjs +32 -0
  32. package/dist/generate/generate.d.mts +16 -0
  33. package/dist/generate/generate.mjs +293 -0
  34. package/dist/generate/getClientTemplateFiles.d.mts +20 -0
  35. package/dist/generate/getClientTemplateFiles.mjs +94 -0
  36. package/dist/generate/getProjectFullSchema.d.mts +7 -0
  37. package/dist/generate/getProjectFullSchema.mjs +65 -0
  38. package/dist/generate/getTemplateClientImports.d.mts +18 -0
  39. package/dist/generate/getTemplateClientImports.mjs +38 -0
  40. package/dist/generate/index.d.mts +33 -0
  41. package/dist/generate/index.mjs +189 -0
  42. package/dist/generate/mergePackages.d.mts +7 -0
  43. package/dist/generate/mergePackages.mjs +55 -0
  44. package/dist/generate/writeOneClientFile.d.mts +36 -0
  45. package/dist/generate/writeOneClientFile.mjs +123 -0
  46. package/dist/getProjectInfo/getConfig/getConfigAbsolutePaths.d.mts +5 -0
  47. package/dist/getProjectInfo/{getConfigAbsolutePaths.mjs → getConfig/getConfigAbsolutePaths.mjs} +4 -1
  48. package/dist/getProjectInfo/{getRelativeSrcRoot.d.mts → getConfig/getRelativeSrcRoot.d.mts} +1 -1
  49. package/dist/getProjectInfo/{getRelativeSrcRoot.mjs → getConfig/getRelativeSrcRoot.mjs} +2 -2
  50. package/dist/getProjectInfo/getConfig/getTemplateDefs.d.mts +22 -0
  51. package/dist/getProjectInfo/getConfig/getTemplateDefs.mjs +145 -0
  52. package/dist/getProjectInfo/{getUserConfig.d.mts → getConfig/getUserConfig.d.mts} +3 -2
  53. package/dist/getProjectInfo/{getUserConfig.mjs → getConfig/getUserConfig.mjs} +6 -4
  54. package/dist/getProjectInfo/{importUncachedModule.mjs → getConfig/importUncachedModule.mjs} +1 -4
  55. package/dist/getProjectInfo/getConfig/index.d.mts +120 -0
  56. package/dist/getProjectInfo/getConfig/index.mjs +97 -0
  57. package/dist/getProjectInfo/index.d.mts +12 -9
  58. package/dist/getProjectInfo/index.mjs +21 -22
  59. package/dist/index.d.mts +2 -2
  60. package/dist/index.mjs +101 -36
  61. package/dist/init/createConfig.d.mts +2 -2
  62. package/dist/init/createConfig.mjs +14 -12
  63. package/dist/init/getTemplateFilesFromPackage.mjs +10 -5
  64. package/dist/init/index.d.mts +2 -2
  65. package/dist/init/index.mjs +81 -60
  66. package/dist/init/installDependencies.mjs +4 -2
  67. package/dist/init/logUpdateDependenciesError.d.mts +3 -1
  68. package/dist/init/logUpdateDependenciesError.mjs +7 -1
  69. package/dist/init/updateDependenciesWithoutInstalling.mjs +39 -9
  70. package/dist/init/updateNPMScripts.d.mts +3 -1
  71. package/dist/init/updateNPMScripts.mjs +9 -7
  72. package/dist/init/updateTypeScriptConfig.d.mts +4 -1
  73. package/dist/init/updateTypeScriptConfig.mjs +11 -7
  74. package/dist/initProgram.d.mts +1 -1
  75. package/dist/initProgram.mjs +16 -16
  76. package/dist/locateSegments.d.mts +8 -1
  77. package/dist/locateSegments.mjs +14 -4
  78. package/dist/new/addClassToSegmentCode.d.mts +1 -2
  79. package/dist/new/addClassToSegmentCode.mjs +3 -3
  80. package/dist/new/index.d.mts +1 -1
  81. package/dist/new/index.mjs +3 -2
  82. package/dist/new/newModule.d.mts +2 -1
  83. package/dist/new/newModule.mjs +18 -16
  84. package/dist/new/newSegment.d.mts +2 -1
  85. package/dist/new/newSegment.mjs +19 -10
  86. package/dist/new/render.d.mts +7 -3
  87. package/dist/new/render.mjs +29 -8
  88. package/dist/types.d.mts +51 -40
  89. package/dist/utils/compileJSONSchemaToTypeScriptType.d.mts +5 -0
  90. package/dist/utils/compileJSONSchemaToTypeScriptType.mjs +9 -0
  91. package/dist/utils/compileTs.d.mts +12 -0
  92. package/dist/utils/compileTs.mjs +261 -0
  93. package/dist/utils/debounceWithArgs.d.mts +2 -2
  94. package/dist/utils/debounceWithArgs.mjs +24 -6
  95. package/dist/utils/formatLoggedSegmentName.d.mts +3 -1
  96. package/dist/utils/formatLoggedSegmentName.mjs +3 -2
  97. package/dist/utils/getPackageJson.d.mts +3 -0
  98. package/dist/utils/getPackageJson.mjs +22 -0
  99. package/dist/utils/getPublicModuleNameFromPath.d.mts +4 -0
  100. package/dist/utils/getPublicModuleNameFromPath.mjs +9 -0
  101. package/dist/utils/normalizeOpenAPIMixins.d.mts +7 -0
  102. package/dist/utils/normalizeOpenAPIMixins.mjs +67 -0
  103. package/dist/utils/pickSegmentFullSchema.d.mts +3 -0
  104. package/dist/utils/pickSegmentFullSchema.mjs +15 -0
  105. package/dist/utils/removeUnlistedDirectories.d.mts +10 -0
  106. package/dist/utils/removeUnlistedDirectories.mjs +61 -0
  107. package/dist/utils/resolveAbsoluteModulePath.d.mts +2 -0
  108. package/dist/utils/resolveAbsoluteModulePath.mjs +32 -0
  109. package/module-templates/controller.ts.ejs +56 -0
  110. package/module-templates/service.ts.ejs +28 -0
  111. package/package.json +36 -22
  112. package/dist/dev/diffSchema.d.mts +0 -43
  113. package/dist/dev/ensureClient.d.mts +0 -5
  114. package/dist/dev/ensureClient.mjs +0 -31
  115. package/dist/dev/isMetadataEmpty.d.mts +0 -2
  116. package/dist/dev/isMetadataEmpty.mjs +0 -4
  117. package/dist/dev/writeOneSchemaFile.d.mts +0 -11
  118. package/dist/generateClient.d.mts +0 -7
  119. package/dist/generateClient.mjs +0 -97
  120. package/dist/getProjectInfo/getConfig.d.mts +0 -11
  121. package/dist/getProjectInfo/getConfig.mjs +0 -29
  122. package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +0 -4
  123. package/dist/postinstall.d.mts +0 -1
  124. package/dist/postinstall.mjs +0 -24
  125. package/templates/controller.ejs +0 -51
  126. package/templates/service.ejs +0 -27
  127. package/templates/worker.ejs +0 -24
  128. /package/dist/getProjectInfo/{importUncachedModule.d.mts → getConfig/importUncachedModule.d.mts} +0 -0
  129. /package/dist/getProjectInfo/{importUncachedModuleWorker.d.mts → getConfig/importUncachedModuleWorker.d.mts} +0 -0
  130. /package/dist/getProjectInfo/{importUncachedModuleWorker.mjs → getConfig/importUncachedModuleWorker.mjs} +0 -0
@@ -1,14 +1,11 @@
1
1
  // importUncachedModule.js
2
2
  import { Worker } from 'node:worker_threads';
3
3
  import path from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
4
  import './importUncachedModuleWorker.mjs'; // required for TS compilation
6
5
  function importUncachedModule(modulePath) {
7
6
  return new Promise((resolve, reject) => {
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
10
7
  // Resolve the path to the worker script
11
- const workerPath = path.resolve(__dirname, 'importUncachedModuleWorker.mjs');
8
+ const workerPath = path.resolve(import.meta.dirname, 'importUncachedModuleWorker.mjs');
12
9
  // Initialize the worker thread
13
10
  const worker = new Worker(workerPath, {
14
11
  workerData: { modulePath },
@@ -0,0 +1,120 @@
1
+ import { VovkSchemaIdEnum, type VovkStrictConfig } from 'vovk';
2
+ export default function getConfig({ configPath, cwd }: {
3
+ configPath?: string;
4
+ cwd: string;
5
+ }): Promise<{
6
+ config: VovkStrictConfig;
7
+ srcRoot: string | null;
8
+ configAbsolutePaths: string[];
9
+ userConfig: {
10
+ $schema?: typeof VovkSchemaIdEnum.CONFIG | (string & {});
11
+ emitConfig?: boolean | (keyof VovkStrictConfig | (string & {}))[];
12
+ schemaOutDir?: string;
13
+ modulesDir?: string;
14
+ rootEntry?: string;
15
+ origin?: string;
16
+ logLevel?: "error" | "trace" | "debug" | "info" | "warn" | (string & {});
17
+ prettifyClient?: boolean;
18
+ libs?: {
19
+ ajv: import("vovk").KnownAny;
20
+ [key: string]: import("vovk").KnownAny;
21
+ };
22
+ devHttps?: boolean;
23
+ composedClient?: ({
24
+ enabled?: boolean;
25
+ outDir?: string;
26
+ fromTemplates?: string[];
27
+ } & ({
28
+ excludeSegments?: never;
29
+ includeSegments?: string[];
30
+ } | {
31
+ excludeSegments?: string[];
32
+ includeSegments?: never;
33
+ })) & {
34
+ package?: import("type-fest").PackageJson;
35
+ readme?: {
36
+ banner?: string;
37
+ };
38
+ };
39
+ segmentedClient?: ({
40
+ enabled?: boolean;
41
+ outDir?: string;
42
+ fromTemplates?: string[];
43
+ } & ({
44
+ excludeSegments?: never;
45
+ includeSegments?: string[];
46
+ } | {
47
+ excludeSegments?: string[];
48
+ includeSegments?: never;
49
+ })) & {
50
+ packages?: Record<string, import("type-fest").PackageJson>;
51
+ readmes?: Record<string, {
52
+ banner?: string;
53
+ }>;
54
+ };
55
+ bundle?: {
56
+ requires?: Record<string, string>;
57
+ tsClientOutDir?: string;
58
+ dontDeleteTsClientOutDirAfter?: boolean;
59
+ package?: import("type-fest").PackageJson;
60
+ readme?: {
61
+ banner?: string;
62
+ };
63
+ reExports?: Record<string, string>;
64
+ tsdownBuildOptions?: Parameters<typeof import("tsdown/config-9hj-APNF.mjs").build>[0];
65
+ } & ({
66
+ excludeSegments?: never;
67
+ includeSegments?: string[];
68
+ } | {
69
+ excludeSegments?: string[];
70
+ includeSegments?: never;
71
+ });
72
+ clientTemplateDefs?: Record<string, import("vovk/mjs/types").ClientTemplateDef>;
73
+ imports?: {
74
+ fetcher?: string | [string, string] | [string];
75
+ validateOnClient?: string | [string, string] | [string];
76
+ createRPC?: string | [string, string] | [string];
77
+ };
78
+ rootSegmentModulesDirName?: string;
79
+ moduleTemplates?: {
80
+ service?: string;
81
+ controller?: string;
82
+ [key: string]: string | undefined;
83
+ };
84
+ segmentConfig?: false | {
85
+ [x: string]: {
86
+ origin?: string;
87
+ rootEntry?: string;
88
+ segmentNameOverride?: string;
89
+ reExports?: Record<string, string>;
90
+ };
91
+ };
92
+ openApiMixins?: {
93
+ [mixinName: string]: {
94
+ source: {
95
+ file: string;
96
+ } | {
97
+ url: string;
98
+ fallback?: string;
99
+ } | {
100
+ object: import("openapi3-ts/oas31").OpenAPIObject;
101
+ };
102
+ package?: import("type-fest").PackageJson;
103
+ readme?: {
104
+ banner?: string;
105
+ };
106
+ apiRoot?: string;
107
+ getModuleName?: "nestjs-operation-id" | (string & {}) | "api" | import("vovk/mjs/types").GetOpenAPINameFn;
108
+ getMethodName?: "nestjs-operation-id" | "camel-case-operation-id" | "auto" | import("vovk/mjs/types").GetOpenAPINameFn;
109
+ errorMessageKey?: string;
110
+ };
111
+ };
112
+ } | null;
113
+ log: {
114
+ info: (msg: string) => void;
115
+ warn: (msg: string) => void;
116
+ error: (msg: string) => void;
117
+ debug: (msg: string) => void;
118
+ raw: import("loglevel").RootLogger;
119
+ };
120
+ }>;
@@ -0,0 +1,97 @@
1
+ import path from 'node:path';
2
+ import { VovkSchemaIdEnum } from 'vovk';
3
+ import getLogger from '../../utils/getLogger.mjs';
4
+ import getUserConfig from './getUserConfig.mjs';
5
+ import getRelativeSrcRoot from './getRelativeSrcRoot.mjs';
6
+ import getTemplateDefs, { BuiltInTemplateName } from './getTemplateDefs.mjs';
7
+ import { normalizeOpenAPIMixins } from '../../utils/normalizeOpenAPIMixins.mjs';
8
+ import chalkHighlightThing from '../../utils/chalkHighlightThing.mjs';
9
+ export default async function getConfig({ configPath, cwd }) {
10
+ const { configAbsolutePaths, error, userConfig } = await getUserConfig({
11
+ configPath,
12
+ cwd,
13
+ });
14
+ const conf = userConfig ?? {};
15
+ const env = process.env;
16
+ const clientTemplateDefs = getTemplateDefs(conf.clientTemplateDefs);
17
+ const srcRoot = await getRelativeSrcRoot({ cwd });
18
+ const validateOnClientImport = conf.imports?.validateOnClient ?? null;
19
+ const fetcherImport = conf.imports?.fetcher ?? 'vovk';
20
+ const createRPCImport = conf.imports?.createRPC ?? 'vovk';
21
+ const imports = {
22
+ fetcher: typeof fetcherImport === 'string' ? [fetcherImport] : fetcherImport,
23
+ validateOnClient: typeof validateOnClientImport === 'string'
24
+ ? [validateOnClientImport]
25
+ : (validateOnClientImport ?? null),
26
+ createRPC: typeof createRPCImport === 'string' ? [createRPCImport] : createRPCImport,
27
+ };
28
+ const config = {
29
+ $schema: VovkSchemaIdEnum.CONFIG,
30
+ clientTemplateDefs,
31
+ imports,
32
+ emitConfig: [],
33
+ composedClient: {
34
+ ...conf.composedClient,
35
+ enabled: conf.composedClient?.enabled ?? true,
36
+ fromTemplates: conf.composedClient?.fromTemplates ?? ['mjs', 'cjs'],
37
+ outDir: conf.composedClient?.outDir ?? './node_modules/.vovk-client',
38
+ },
39
+ segmentedClient: {
40
+ ...conf.segmentedClient,
41
+ enabled: conf.segmentedClient?.enabled ?? false,
42
+ fromTemplates: conf.segmentedClient?.fromTemplates ?? ['ts'],
43
+ outDir: conf.segmentedClient?.outDir ?? path.join(srcRoot ?? '.', 'client'),
44
+ },
45
+ bundle: {
46
+ tsClientOutDir: conf.bundle?.tsClientOutDir ?? 'tmp_prebundle',
47
+ dontDeleteTsClientOutDirAfter: conf.bundle?.dontDeleteTsClientOutDirAfter ?? false,
48
+ requires: {
49
+ [BuiltInTemplateName.readme]: '.',
50
+ [BuiltInTemplateName.packageJson]: '.',
51
+ },
52
+ package: {},
53
+ readme: {},
54
+ reExports: {},
55
+ ...conf.bundle,
56
+ tsdownBuildOptions: {
57
+ outDir: conf.bundle?.tsdownBuildOptions?.outDir ?? 'dist',
58
+ ...conf.bundle?.tsdownBuildOptions,
59
+ },
60
+ },
61
+ modulesDir: conf.modulesDir ?? path.join(srcRoot ?? '.', 'modules'),
62
+ schemaOutDir: env.VOVK_SCHEMA_OUT_DIR ?? conf.schemaOutDir ?? './.vovk-schema',
63
+ origin: (env.VOVK_ORIGIN ?? conf.origin ?? '').replace(/\/$/, ''), // Remove trailing slash
64
+ rootEntry: env.VOVK_ROOT_ENTRY ?? conf.rootEntry ?? 'api',
65
+ rootSegmentModulesDirName: conf.rootSegmentModulesDirName ?? '',
66
+ logLevel: env.VOVK_LOG_LEVEL ?? conf.logLevel ?? 'info',
67
+ prettifyClient: (env.VOVK_PRETTIFY_CLIENT ? !!env.VOVK_PRETTIFY_CLIENT : null) ?? conf.prettifyClient ?? false,
68
+ devHttps: (env.VOVK_DEV_HTTPS ? !!env.VOVK_DEV_HTTPS : null) ?? conf.devHttps ?? false,
69
+ moduleTemplates: {
70
+ service: 'vovk-cli/module-templates/service.ts.ejs',
71
+ controller: 'vovk-cli/module-templates/controller.ts.ejs',
72
+ ...conf.moduleTemplates,
73
+ },
74
+ libs: conf.libs ?? {},
75
+ segmentConfig: conf.segmentConfig ?? {},
76
+ openApiMixins: {},
77
+ };
78
+ if (typeof conf.emitConfig === 'undefined') {
79
+ config.emitConfig = ['libs'];
80
+ }
81
+ else if (conf.emitConfig === true) {
82
+ config.emitConfig = Object.keys(config);
83
+ }
84
+ else if (Array.isArray(conf.emitConfig)) {
85
+ config.emitConfig = conf.emitConfig;
86
+ } // else it's false and emitConfig already is []
87
+ const log = getLogger(config.logLevel);
88
+ config.openApiMixins = await normalizeOpenAPIMixins({
89
+ mixinModules: conf.openApiMixins ?? {},
90
+ cwd,
91
+ log,
92
+ });
93
+ if (!userConfig) {
94
+ log.warn(`Unable to load config at ${chalkHighlightThing(cwd)}. Using default values. ${error ?? ''}`);
95
+ }
96
+ return { config, srcRoot, configAbsolutePaths, userConfig, log };
97
+ }
@@ -1,18 +1,21 @@
1
1
  export type ProjectInfo = Awaited<ReturnType<typeof getProjectInfo>>;
2
- export default function getProjectInfo({ port: givenPort, clientOutDir, cwd, }?: {
2
+ export default function getProjectInfo({ port: givenPort, cwd, configPath, srcRootRequired, }?: {
3
3
  port?: number;
4
- clientOutDir?: string;
5
4
  cwd?: string;
5
+ configPath?: string;
6
+ srcRootRequired?: boolean;
6
7
  }): Promise<{
7
8
  cwd: string;
8
9
  port: string;
9
- apiEntryPoint: string;
10
- apiDir: string;
11
- srcRoot: string;
12
- schemaOutImportPath: string;
13
- fetcherClientImportPath: string;
14
- validateOnClientImportPath: string | null;
15
- config: Required<import("../types.mjs").VovkConfig>;
10
+ apiRoot: string;
11
+ apiDirAbsolutePath: string | null;
12
+ srcRoot: string | null;
13
+ vovkCliPackage: {
14
+ version: string;
15
+ };
16
+ config: import("vovk").VovkStrictConfig;
17
+ packageJson: import("type-fest").PackageJson;
18
+ isNextInstalled: boolean;
16
19
  log: {
17
20
  info: (msg: string) => void;
18
21
  warn: (msg: string) => void;
@@ -1,37 +1,36 @@
1
1
  import path from 'node:path';
2
- import getConfig from './getConfig.mjs';
3
- import getLogger from '../utils/getLogger.mjs';
4
- export default async function getProjectInfo({ port: givenPort, clientOutDir, cwd = process.cwd(), } = {}) {
2
+ import getConfig from './getConfig/index.mjs';
3
+ import { getPackageJson } from '../utils/getPackageJson.mjs';
4
+ import { readFile } from 'node:fs/promises';
5
+ export default async function getProjectInfo({ port: givenPort, cwd = process.cwd(), configPath, srcRootRequired = true, } = {}) {
5
6
  const port = givenPort?.toString() ?? process.env.PORT ?? '3000';
6
7
  // Make PORT available to the config file at getConfig
7
8
  process.env.PORT = port;
8
- const { config, srcRoot, configAbsolutePaths, userConfig, error } = await getConfig({ clientOutDir, cwd });
9
- const apiEntryPoint = `${config.origin ?? ''}/${config.rootEntry}`;
10
- const apiDir = path.join(srcRoot, 'app', config.rootEntry);
11
- const schemaOutImportPath = path.relative(config.clientOutDir, config.schemaOutDir);
12
- const fetcherClientImportPath = config.fetcher.startsWith('.')
13
- ? path.relative(config.clientOutDir, config.fetcher)
14
- : config.fetcher;
15
- const validateOnClientImportPath = config.validateOnClient?.startsWith('.')
16
- ? path.relative(config.clientOutDir, config.validateOnClient)
17
- : config.validateOnClient;
18
- const log = getLogger(config.logLevel);
9
+ const { config, srcRoot, configAbsolutePaths, log } = await getConfig({
10
+ configPath,
11
+ cwd,
12
+ });
13
+ const packageJson = await getPackageJson(cwd, log);
14
+ const isNextInstalled = !!packageJson?.dependencies?.next || !!packageJson?.devDependencies?.next;
15
+ if (srcRootRequired && !srcRoot) {
16
+ throw new Error(`Could not find app router directory at ${cwd}. Check Next.js docs for more info.`);
17
+ }
18
+ const apiRoot = `${config.origin ?? ''}/${config.rootEntry}`;
19
+ const apiDirAbsolutePath = srcRoot ? path.resolve(cwd, srcRoot, 'app', config.rootEntry) : null;
19
20
  if (configAbsolutePaths.length > 1) {
20
21
  log.warn(`Multiple config files found. Using the first one: ${configAbsolutePaths[0]}`);
21
22
  }
22
- if (!userConfig && configAbsolutePaths.length > 0) {
23
- log.error(`Error reading config file at ${configAbsolutePaths[0]}: ${error?.message ?? 'Unknown Error'}`);
24
- }
23
+ const vovkCliPackage = JSON.parse(await readFile(path.join(import.meta.dirname, '../../package.json'), 'utf-8'));
25
24
  return {
26
25
  cwd,
27
26
  port,
28
- apiEntryPoint,
29
- apiDir,
27
+ apiRoot,
28
+ apiDirAbsolutePath,
30
29
  srcRoot,
31
- schemaOutImportPath,
32
- fetcherClientImportPath,
33
- validateOnClientImportPath,
30
+ vovkCliPackage,
34
31
  config,
32
+ packageJson,
33
+ isNextInstalled,
35
34
  log,
36
35
  };
37
36
  }
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import type { VovkConfig, VovkDevEnv } from './types.mjs';
3
2
  import 'dotenv/config';
4
- export type { VovkConfig, VovkDevEnv };
3
+ import type { VovkEnv } from './types.mjs';
4
+ export type { VovkEnv };
package/dist/index.mjs CHANGED
@@ -1,53 +1,66 @@
1
1
  #!/usr/bin/env node
2
2
  import path from 'node:path';
3
3
  import { readFileSync } from 'node:fs';
4
+ import 'dotenv/config';
4
5
  import { Command } from 'commander';
5
6
  import concurrently from 'concurrently';
6
7
  import getAvailablePort from './utils/getAvailablePort.mjs';
7
8
  import getProjectInfo from './getProjectInfo/index.mjs';
8
- import generateClient from './generateClient.mjs';
9
- import locateSegments from './locateSegments.mjs';
9
+ import { VovkGenerate } from './generate/index.mjs';
10
+ import { bundle } from './bundle/index.mjs';
10
11
  import { VovkDev } from './dev/index.mjs';
11
- import newComponents from './new/index.mjs';
12
- import 'dotenv/config';
13
- import initProgram from './initProgram.mjs';
12
+ import { newComponents } from './new/index.mjs';
13
+ import { initProgram } from './initProgram.mjs';
14
+ import { getProjectFullSchema } from './generate/getProjectFullSchema.mjs';
14
15
  const program = new Command();
15
- const packageJSON = JSON.parse(readFileSync(path.join(import.meta.dirname, '../package.json'), 'utf-8'));
16
- program.name('vovk').description('Vovk CLI').version(packageJSON.version);
16
+ const vovkCliPackage = JSON.parse(readFileSync(path.join(import.meta.dirname, '../package.json'), 'utf-8'));
17
+ program.name('vovk').description('Vovk CLI').version(vovkCliPackage.version);
17
18
  initProgram(program.command('init'));
18
19
  program
19
20
  .command('dev')
20
- .description('Start schema watcher (optional flag --next-dev to start it with Next.js)')
21
- .option('--next-dev', 'Start schema watcher and Next.js with automatic port allocation', false)
22
- .allowUnknownOption(true)
23
- .action(async (options, command) => {
21
+ .alias('d')
22
+ .description('start schema watcher (optional flag --next-dev to start it with Next.js)')
23
+ .argument('[nextArgs...]', 'extra arguments for the dev command')
24
+ .option('--next-dev', 'start schema watcher and Next.js with automatic port allocation')
25
+ .option('--exit', 'kill the processe when schema and client is generated')
26
+ .option('--schema-out <path>', 'path to schema output directory (default: .vovk-schema)')
27
+ .option('--https, --dev-https', 'use HTTPS for the dev server (default: false)')
28
+ .action(async (nextArgs, options) => {
29
+ const { nextDev, exit = false, schemaOut, devHttps } = options;
24
30
  const portAttempts = 30;
25
- const PORT = !options.nextDev
31
+ const PORT = !nextDev
26
32
  ? process.env.PORT
27
33
  : process.env.PORT ||
28
34
  (await getAvailablePort(3000, portAttempts, 0, (failedPort, tryingPort) =>
29
35
  // eslint-disable-next-line no-console
30
- console.warn(`🐺 Next.js Port ${failedPort} is in use, trying ${tryingPort} instead.`)).catch(() => {
31
- throw new Error(`🐺 ❌ Failed to find available Next port after ${portAttempts} attempts`);
36
+ console.warn(`🐺 Port ${failedPort} is in use, trying ${tryingPort} instead.`)).catch(() => {
37
+ throw new Error(`🐺 ❌ Failed to find an available port after ${portAttempts} attempts`);
32
38
  }));
33
39
  if (!PORT) {
34
40
  throw new Error('🐺 ❌ PORT env variable is required');
35
41
  }
36
- if (options.nextDev) {
42
+ if (nextDev) {
37
43
  const { result } = concurrently([
38
44
  {
39
- command: `npx next dev ${command.args.join(' ')}`,
45
+ command: `npx next dev ${nextArgs.join(' ')}`,
40
46
  name: 'Next.js Development Server',
41
47
  env: { PORT },
42
48
  },
43
49
  {
44
50
  command: `node ${import.meta.dirname}/dev/index.mjs`,
45
- name: 'Vovk Dev Command',
46
- env: { PORT, __VOVK_START_WATCHER_IN_STANDALONE_MODE__: 'true' },
51
+ name: 'Vovk Dev Watcher',
52
+ env: {
53
+ PORT,
54
+ __VOVK_START_WATCHER_IN_STANDALONE_MODE__: 'true',
55
+ __VOVK_SCHEMA_OUT_FLAG__: schemaOut ?? '',
56
+ __VOVK_DEV_HTTPS_FLAG__: devHttps ? 'true' : 'false',
57
+ __VOVK_EXIT__: exit ? 'true' : 'false',
58
+ },
47
59
  },
48
60
  ], {
49
- killOthers: ['failure', 'success'],
61
+ killOthersOn: ['failure', 'success'],
50
62
  prefix: 'none',
63
+ successCondition: 'first',
51
64
  });
52
65
  try {
53
66
  await result;
@@ -57,31 +70,83 @@ program
57
70
  }
58
71
  }
59
72
  else {
60
- void new VovkDev().start();
73
+ void new VovkDev({ schemaOut, devHttps }).start({ exit });
61
74
  }
62
75
  });
63
76
  program
64
77
  .command('generate')
65
- .description('Generate client')
66
- .option('--client-out <path>', 'Path to output directory')
67
- .action(async (options) => {
68
- const projectInfo = await getProjectInfo({ clientOutDir: options.clientOut });
69
- const { cwd, config, apiDir } = projectInfo;
70
- const segments = await locateSegments(apiDir);
71
- const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
72
- const schema = (await import(path.join(schemaOutAbsolutePath, 'index.js')));
73
- await generateClient(projectInfo, segments, schema.default);
78
+ .alias('g')
79
+ .description('generate RPC client from schema')
80
+ .option('--composed-only', 'generate only composed client even if segmented client is enabled')
81
+ .option('--out, --composed-out <path>', 'path to output directory for composed client')
82
+ .option('--from, --composed-from <templates...>', 'client template names for composed client')
83
+ .option('--include, --composed-include-segments <segments...>', 'include segments in composed client')
84
+ .option('--exclude, --composed-exclude-segments <segments...>', 'exclude segments in composed client')
85
+ .option('--segmented-only', 'generate only segmented client even if composed client is enabled')
86
+ .option('--segmented-out <path>', 'path to output directory for segmented client')
87
+ .option('--segmented-from <templates...>', 'client template names for segmented client')
88
+ .option('--segmented-include-segments <segments...>', 'include segments in segmented client')
89
+ .option('--segmented-exclude-segments <segments...>', 'exclude segments in segmented client')
90
+ .option('--prettify', 'prettify output files')
91
+ .option('--schema, --schema-path <path>', 'path to schema folder (default: ./.vovk-schema)')
92
+ .option('--config, --config-path <config>', 'path to config file')
93
+ .option('--force-ts-standalone', 'force TypeScript standalone mode (Next.js environment will be ignored, by default it\'s "true" for non-Next.js directories)')
94
+ .option('--watch <s>', 'watch for changes in schema or openapi spec and regenerate client; accepts a number in seconds to throttle the watcher or make an HTTP request to the OpenAPI spec URL')
95
+ .option('--openapi, --openapi-spec <openapi_path_or_urls...>', 'use OpenAPI schema for client generation')
96
+ .option('--openapi-get-module-name <names...>', 'module names corresponding to the index of --openapi option')
97
+ .option('--openapi-get-method-name <names...>', 'method names corresponding to the index of --openapi option')
98
+ .option('--openapi-root-url <urls...>', 'root URLs corresponding to the index of --openapi option')
99
+ .option('--openapi-mixin-name <names...>', 'mixin names corresponding to the index of --openapi option')
100
+ .option('--openapi-fallback <paths...>', 'save OpenAPI spec and use it as a fallback if URL is not available')
101
+ .action(async (cliGenerateOptions) => {
102
+ const projectInfo = await getProjectInfo({ configPath: cliGenerateOptions.configPath, srcRootRequired: false });
103
+ await new VovkGenerate({
104
+ projectInfo,
105
+ forceNothingWrittenLog: true,
106
+ cliGenerateOptions,
107
+ }).start();
108
+ });
109
+ program
110
+ .command('bundle')
111
+ .alias('b')
112
+ .description('generate TypeScrtipt RPC and bundle it')
113
+ .option('--out, --out-dir <path>', 'path to output directory for bundle')
114
+ .option('--include, --include-segments <segments...>', 'include segments')
115
+ .option('--exclude, --exclude-segments <segments...>', 'exclude segments')
116
+ .option('--ts-client-out-dir <path>', 'path to output directory for TypeScript client')
117
+ .option('--dont-delete-ts-client-out-dir-after', 'do not delete TypeScript client output directory after bundling')
118
+ .option('--config <config>', 'path to config file')
119
+ .option('--schema <path>', 'path to schema folder (default: .vovk-schema)')
120
+ .option('--openapi, --openapi-spec <openapi_path_or_urls...>', 'use OpenAPI schema instead of Vovk schema')
121
+ .option('--openapi-get-module-name <names...>', 'module names corresponding to the index of --openapi option')
122
+ .option('--openapi-get-method-name <names...>', 'method names corresponding to the index of --openapi option')
123
+ .option('--openapi-root-url <urls...>', 'root URLs corresponding to the index of --openapi option')
124
+ .action(async (cliBundleOptions) => {
125
+ const projectInfo = await getProjectInfo({ configPath: cliBundleOptions.config, srcRootRequired: false });
126
+ const { cwd, config, log, isNextInstalled } = projectInfo;
127
+ const fullSchema = await getProjectFullSchema({
128
+ schemaOutAbsolutePath: path.resolve(cwd, cliBundleOptions?.schema ?? config.schemaOutDir),
129
+ log,
130
+ isNextInstalled,
131
+ });
132
+ await bundle({
133
+ projectInfo,
134
+ fullSchema,
135
+ cliBundleOptions,
136
+ });
74
137
  });
75
138
  program
76
139
  .command('new [components...]')
77
140
  .alias('n')
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')
83
- .option('--dry-run', 'Do not write files to disk')
84
- .action((components, options) => newComponents(components, options));
141
+ .description('create new components. "vovk new [...components] [segmentName/]moduleName" to create a new module or "vovk new segment [segmentName]" to create a new segment')
142
+ .option('-o, --overwrite', 'overwrite existing files')
143
+ .option('--template, --templates <templates...>', 'override config template; accepts an array of strings that correspond the order of the components')
144
+ .option('--dir <dirname>', 'override dirName in template file; relative to the root of the project')
145
+ .option('--empty', 'create an empty module')
146
+ .option('--no-segment-update', 'do not update segment files when creating a new module')
147
+ .option('--dry-run', 'do not write files to disk')
148
+ .option('--static', 'if the segment is static')
149
+ .action((components, newOptions) => newComponents(components, newOptions));
85
150
  program
86
151
  .command('help')
87
152
  .description('Show help message')
@@ -1,9 +1,9 @@
1
1
  import type getLogger from '../utils/getLogger.mjs';
2
2
  import type { InitOptions } from '../types.mjs';
3
- export default function createConfig({ root, log, options: { validationLibrary, validateOnClient, channel, dryRun }, }: {
3
+ export default function createConfig({ root, log, options: { validationLibrary, lang, channel, dryRun }, }: {
4
4
  root: string;
5
5
  log: ReturnType<typeof getLogger>;
6
- options: Pick<InitOptions, 'validationLibrary' | 'validateOnClient' | 'channel' | 'dryRun'>;
6
+ options: Pick<InitOptions, 'validationLibrary' | 'lang' | 'channel' | 'dryRun'>;
7
7
  }): Promise<{
8
8
  configAbsolutePath: string;
9
9
  }>;
@@ -3,7 +3,7 @@ import fs from 'node:fs/promises';
3
3
  import getTemplateFilesFromPackage from './getTemplateFilesFromPackage.mjs';
4
4
  import prettify from '../utils/prettify.mjs';
5
5
  import getFileSystemEntryType, { FileSystemEntryType } from '../utils/getFileSystemEntryType.mjs';
6
- export default async function createConfig({ root, log, options: { validationLibrary, validateOnClient, channel, dryRun }, }) {
6
+ export default async function createConfig({ root, log, options: { validationLibrary, lang, channel, dryRun }, }) {
7
7
  const config = {};
8
8
  const dotConfigPath = path.join(root, '.config');
9
9
  const dir = (await getFileSystemEntryType(dotConfigPath)) === FileSystemEntryType.DIRECTORY ? dotConfigPath : root;
@@ -11,26 +11,28 @@ export default async function createConfig({ root, log, options: { validationLib
11
11
  .readFile(path.join(root, 'package.json'), 'utf-8')
12
12
  .then((content) => JSON.parse(content).type === 'module');
13
13
  const configAbsolutePath = path.join(dir, isModule ? 'vovk.config.mjs' : 'vovk.config.js');
14
- const templates = {
15
- controller: 'vovk-cli/templates/controller.ejs',
16
- service: 'vovk-cli/templates/service.ejs',
17
- worker: 'vovk-cli/templates/worker.ejs',
14
+ const moduleTemplates = {
15
+ controller: 'vovk-cli/module-templates/controller.ts.ejs',
16
+ service: 'vovk-cli/module-templates/service.ts.ejs',
18
17
  };
18
+ config.imports ??= {};
19
+ config.imports.validateOnClient = validationLibrary === 'vovk-dto' ? 'vovk-dto/validateOnClient' : 'vovk-ajv';
19
20
  if (validationLibrary) {
20
- config.validationLibrary = validationLibrary;
21
- if (validateOnClient) {
22
- config.validateOnClient = `${validationLibrary}/validateOnClient`;
23
- }
24
21
  try {
25
22
  const validationTemplates = await getTemplateFilesFromPackage(validationLibrary, channel);
26
- Object.assign(templates, validationTemplates);
23
+ Object.assign(moduleTemplates, validationTemplates);
27
24
  }
28
25
  catch (error) {
29
26
  log.warn(`Failed to fetch validation library templates: ${error.message}`);
30
27
  }
31
28
  }
32
- config.templates = templates;
33
- const configStr = await prettify(`/** @type {import('vovk-cli').VovkConfig} */
29
+ if (lang?.length) {
30
+ config.composedClient ??= {};
31
+ config.composedClient.fromTemplates = ['mjs', 'cjs', ...lang];
32
+ }
33
+ config.moduleTemplates = moduleTemplates;
34
+ const configStr = await prettify(`// @ts-check
35
+ /** @type {import('vovk').VovkConfig} */
34
36
  const config = ${JSON.stringify(config, null, 2)};
35
37
  ${isModule ? '\nexport default config;' : 'module.exports = config;'}`, configAbsolutePath);
36
38
  if (!dryRun)
@@ -2,7 +2,6 @@ import { Readable } from 'node:stream';
2
2
  import { createGunzip } from 'node:zlib';
3
3
  import tar from 'tar-stream';
4
4
  import getNPMPackageMetadata from '../utils/getNPMPackageMetadata.mjs';
5
- // Crereated with AI
6
5
  /**
7
6
  * Retrieves a list of files in the 'templates' folder of an NPM package.
8
7
  * @param packageName - The name of the NPM package.
@@ -22,8 +21,14 @@ export default async function getTemplateFilesFromPackage(packageName, channel =
22
21
  // Extract the tarball in memory and collect template files
23
22
  const templateFiles = await extractTemplatesFromTarball(tarballBuffer);
24
23
  const entries = templateFiles
25
- .filter((fileName) => fileName.startsWith('templates/') && !fileName.endsWith('/') && fileName.endsWith('.ejs'))
26
- .map((fileName) => [fileName.substring('templates/'.length).replace(/\.ejs$/, ''), `${packageName}/${fileName}`]);
24
+ .filter((fileName) => fileName.startsWith('module-templates/') && !fileName.endsWith('/') && fileName.endsWith('.ts.ejs'))
25
+ .map((fileName) => [
26
+ fileName
27
+ .substring('module-templates/'.length)
28
+ .replace(/\.ts\.ejs$/, '')
29
+ .toLowerCase(),
30
+ `${packageName}/${fileName}`,
31
+ ]);
27
32
  return Object.fromEntries(entries);
28
33
  }
29
34
  /**
@@ -37,8 +42,8 @@ function extractTemplatesFromTarball(tarballBuffer) {
37
42
  const files = [];
38
43
  extract.on('entry', (header, stream, next) => {
39
44
  const filePath = header.name;
40
- // Check if the file is in the 'templates' folder
41
- if (filePath.startsWith('package/templates/')) {
45
+ // Check if the file is in the 'module-templates' folder
46
+ if (filePath.startsWith('package/module-templates/')) {
42
47
  files.push(filePath.replace('package/', ''));
43
48
  }
44
49
  stream.on('end', () => next());
@@ -1,8 +1,8 @@
1
- import type { InitOptions } from '../types.mjs';
2
1
  import getLogger from '../utils/getLogger.mjs';
2
+ import type { InitOptions } from '../types.mjs';
3
3
  export declare class Init {
4
4
  #private;
5
5
  root: string;
6
6
  log: ReturnType<typeof getLogger>;
7
- main(prefix: string, { yes, logLevel, useNpm, useYarn, usePnpm, useBun, skipInstall, updateTsConfig, updateScripts, validationLibrary, validateOnClient, dryRun, channel, }: InitOptions): Promise<void>;
7
+ main(prefix: string, { yes, logLevel, useNpm, useYarn, usePnpm, useBun, skipInstall, updateTsConfig, updateScripts, validationLibrary, lang, dryRun, channel, }: InitOptions): Promise<void>;
8
8
  }