vovk-cli 0.0.1-draft.29 → 0.0.1-draft.290

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 (132) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +29 -1
  3. package/client-templates/cjs/index.cjs.ejs +14 -0
  4. package/client-templates/cjs/index.d.cts.ejs +22 -0
  5. package/client-templates/mixins/mixins.d.ts.ejs +64 -0
  6. package/client-templates/mixins/mixins.json.ejs +1 -0
  7. package/client-templates/mjs/index.d.mts.ejs +22 -0
  8. package/client-templates/mjs/index.mjs.ejs +20 -0
  9. package/client-templates/packageJson/package.json.ejs +1 -0
  10. package/client-templates/readme/README.md.ejs +37 -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 +30 -0
  16. package/dist/bundle/index.d.mts +8 -0
  17. package/dist/bundle/index.mjs +90 -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 -2
  23. package/dist/dev/index.mjs +173 -77
  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 +15 -0
  33. package/dist/generate/generate.mjs +285 -0
  34. package/dist/generate/getClientTemplateFiles.d.mts +20 -0
  35. package/dist/generate/getClientTemplateFiles.mjs +89 -0
  36. package/dist/generate/getProjectFullSchema.d.mts +3 -0
  37. package/dist/generate/getProjectFullSchema.mjs +64 -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 +185 -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 +35 -0
  45. package/dist/generate/writeOneClientFile.mjs +114 -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 +16 -0
  51. package/dist/getProjectInfo/getConfig/getTemplateDefs.mjs +98 -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 -5
  55. package/dist/getProjectInfo/{importUncachedModuleWorker.mjs → getConfig/importUncachedModuleWorker.mjs} +0 -1
  56. package/dist/getProjectInfo/getConfig/index.d.mts +119 -0
  57. package/dist/getProjectInfo/getConfig/index.mjs +94 -0
  58. package/dist/getProjectInfo/index.d.mts +11 -9
  59. package/dist/getProjectInfo/index.mjs +19 -22
  60. package/dist/index.d.mts +2 -2
  61. package/dist/index.mjs +98 -40
  62. package/dist/init/createConfig.d.mts +2 -2
  63. package/dist/init/createConfig.mjs +18 -12
  64. package/dist/init/getTemplateFilesFromPackage.mjs +10 -5
  65. package/dist/init/index.d.mts +2 -2
  66. package/dist/init/index.mjs +90 -60
  67. package/dist/init/installDependencies.mjs +4 -2
  68. package/dist/init/logUpdateDependenciesError.d.mts +3 -1
  69. package/dist/init/logUpdateDependenciesError.mjs +7 -1
  70. package/dist/init/updateDependenciesWithoutInstalling.mjs +39 -9
  71. package/dist/init/updateNPMScripts.d.mts +3 -1
  72. package/dist/init/updateNPMScripts.mjs +10 -7
  73. package/dist/init/updateTypeScriptConfig.d.mts +4 -1
  74. package/dist/init/updateTypeScriptConfig.mjs +11 -7
  75. package/dist/initProgram.d.mts +1 -1
  76. package/dist/initProgram.mjs +17 -16
  77. package/dist/locateSegments.d.mts +8 -1
  78. package/dist/locateSegments.mjs +14 -4
  79. package/dist/new/addClassToSegmentCode.d.mts +1 -2
  80. package/dist/new/addClassToSegmentCode.mjs +3 -3
  81. package/dist/new/addCommonTerms.mjs +1 -0
  82. package/dist/new/index.d.mts +1 -1
  83. package/dist/new/index.mjs +3 -2
  84. package/dist/new/newModule.d.mts +2 -1
  85. package/dist/new/newModule.mjs +18 -16
  86. package/dist/new/newSegment.d.mts +2 -1
  87. package/dist/new/newSegment.mjs +19 -10
  88. package/dist/new/render.d.mts +7 -3
  89. package/dist/new/render.mjs +29 -8
  90. package/dist/types.d.mts +50 -41
  91. package/dist/utils/compileJSONSchemaToTypeScriptType.d.mts +5 -0
  92. package/dist/utils/compileJSONSchemaToTypeScriptType.mjs +9 -0
  93. package/dist/utils/compileTs.d.mts +12 -0
  94. package/dist/utils/compileTs.mjs +261 -0
  95. package/dist/utils/debounceWithArgs.d.mts +2 -2
  96. package/dist/utils/debounceWithArgs.mjs +24 -9
  97. package/dist/utils/formatLoggedSegmentName.d.mts +3 -1
  98. package/dist/utils/formatLoggedSegmentName.mjs +3 -2
  99. package/dist/utils/getAvailablePort.mjs +1 -1
  100. package/dist/utils/getPackageJson.d.mts +3 -0
  101. package/dist/utils/getPackageJson.mjs +22 -0
  102. package/dist/utils/getPublicModuleNameFromPath.d.mts +4 -0
  103. package/dist/utils/getPublicModuleNameFromPath.mjs +9 -0
  104. package/dist/utils/normalizeOpenAPIMixins.d.mts +7 -0
  105. package/dist/utils/normalizeOpenAPIMixins.mjs +66 -0
  106. package/dist/utils/pickSegmentFullSchema.d.mts +3 -0
  107. package/dist/utils/pickSegmentFullSchema.mjs +15 -0
  108. package/dist/utils/removeUnlistedDirectories.d.mts +10 -0
  109. package/dist/utils/removeUnlistedDirectories.mjs +61 -0
  110. package/dist/utils/resolveAbsoluteModulePath.d.mts +2 -0
  111. package/dist/utils/resolveAbsoluteModulePath.mjs +32 -0
  112. package/module-templates/controller.ts.ejs +56 -0
  113. package/module-templates/service.ts.ejs +28 -0
  114. package/package.json +41 -22
  115. package/dist/dev/diffSchema.d.mts +0 -43
  116. package/dist/dev/ensureClient.d.mts +0 -5
  117. package/dist/dev/ensureClient.mjs +0 -31
  118. package/dist/dev/isMetadataEmpty.d.mts +0 -2
  119. package/dist/dev/isMetadataEmpty.mjs +0 -4
  120. package/dist/dev/writeOneSchemaFile.d.mts +0 -11
  121. package/dist/generateClient.d.mts +0 -7
  122. package/dist/generateClient.mjs +0 -97
  123. package/dist/getProjectInfo/getConfig.d.mts +0 -11
  124. package/dist/getProjectInfo/getConfig.mjs +0 -29
  125. package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +0 -4
  126. package/dist/postinstall.d.mts +0 -1
  127. package/dist/postinstall.mjs +0 -24
  128. package/templates/controller.ejs +0 -51
  129. package/templates/service.ejs +0 -27
  130. package/templates/worker.ejs +0 -24
  131. /package/dist/getProjectInfo/{importUncachedModule.d.mts → getConfig/importUncachedModule.d.mts} +0 -0
  132. /package/dist/getProjectInfo/{importUncachedModuleWorker.d.mts → getConfig/importUncachedModuleWorker.d.mts} +0 -0
@@ -0,0 +1,119 @@
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
+ composedClient?: ({
14
+ enabled?: boolean;
15
+ outDir?: string;
16
+ fromTemplates?: string[];
17
+ } & ({
18
+ excludeSegments?: never;
19
+ includeSegments?: string[];
20
+ } | {
21
+ excludeSegments?: string[];
22
+ includeSegments?: never;
23
+ })) & {
24
+ package?: import("type-fest").PackageJson;
25
+ readme?: {
26
+ banner?: string;
27
+ };
28
+ };
29
+ segmentedClient?: ({
30
+ enabled?: boolean;
31
+ outDir?: string;
32
+ fromTemplates?: string[];
33
+ } & ({
34
+ excludeSegments?: never;
35
+ includeSegments?: string[];
36
+ } | {
37
+ excludeSegments?: string[];
38
+ includeSegments?: never;
39
+ })) & {
40
+ packages?: Record<string, import("type-fest").PackageJson>;
41
+ readmes?: Record<string, {
42
+ banner?: string;
43
+ }>;
44
+ };
45
+ bundle?: {
46
+ outDir?: string;
47
+ requires?: Record<string, string>;
48
+ tsClientOutDir?: string;
49
+ dontDeleteTsClientOutDirAfter?: boolean;
50
+ sourcemap?: boolean;
51
+ package?: import("type-fest").PackageJson;
52
+ readme?: {
53
+ banner?: string;
54
+ };
55
+ } & ({
56
+ excludeSegments?: never;
57
+ includeSegments?: string[];
58
+ } | {
59
+ excludeSegments?: string[];
60
+ includeSegments?: never;
61
+ });
62
+ imports?: {
63
+ fetcher?: string | [string, string] | [string];
64
+ validateOnClient?: string | [string, string] | [string];
65
+ createRPC?: string | [string, string] | [string];
66
+ };
67
+ modulesDir?: string;
68
+ rootEntry?: string;
69
+ origin?: string;
70
+ rootSegmentModulesDirName?: string;
71
+ logLevel?: "error" | "trace" | "debug" | "info" | "warn";
72
+ prettifyClient?: boolean;
73
+ devHttps?: boolean;
74
+ clientTemplateDefs?: Record<string, import("vovk/mjs/types").ClientTemplateDef>;
75
+ moduleTemplates?: {
76
+ service?: string;
77
+ controller?: string;
78
+ [key: string]: string | undefined;
79
+ };
80
+ libs?: {
81
+ ajv: import("vovk").KnownAny;
82
+ [key: string]: import("vovk").KnownAny;
83
+ };
84
+ segmentConfig?: false | {
85
+ [x: string]: {
86
+ origin?: string;
87
+ rootEntry?: string;
88
+ segmentNameOverride?: string;
89
+ };
90
+ };
91
+ openApiMixins?: {
92
+ [mixinName: string]: {
93
+ source: {
94
+ file: string;
95
+ } | {
96
+ url: string;
97
+ fallback?: string;
98
+ } | {
99
+ object: import("openapi3-ts/oas31").OpenAPIObject;
100
+ };
101
+ package?: import("type-fest").PackageJson;
102
+ readme?: {
103
+ banner?: string;
104
+ };
105
+ apiRoot?: string;
106
+ getModuleName?: "nestjs-operation-id" | (string & {}) | "api" | import("vovk/mjs/types").GetOpenAPINameFn;
107
+ getMethodName?: "nestjs-operation-id" | "camel-case-operation-id" | "auto" | import("vovk/mjs/types").GetOpenAPINameFn;
108
+ errorMessageKey?: string;
109
+ };
110
+ };
111
+ } | null;
112
+ log: {
113
+ info: (msg: string) => void;
114
+ warn: (msg: string) => void;
115
+ error: (msg: string) => void;
116
+ debug: (msg: string) => void;
117
+ raw: import("loglevel").RootLogger;
118
+ };
119
+ }>;
@@ -0,0 +1,94 @@
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
+ outDir: conf.bundle?.outDir ?? 'dist',
47
+ tsClientOutDir: conf.bundle?.tsClientOutDir ?? 'tmp_ts_rpc',
48
+ dontDeleteTsClientOutDirAfter: conf.bundle?.dontDeleteTsClientOutDirAfter ?? false,
49
+ sourcemap: conf.bundle?.sourcemap ?? false,
50
+ requires: {
51
+ [BuiltInTemplateName.readme]: '.',
52
+ [BuiltInTemplateName.packageJson]: '.',
53
+ },
54
+ package: {},
55
+ readme: {},
56
+ ...conf.bundle,
57
+ },
58
+ modulesDir: conf.modulesDir ?? path.join(srcRoot ?? '.', 'modules'),
59
+ schemaOutDir: env.VOVK_SCHEMA_OUT_DIR ?? conf.schemaOutDir ?? './.vovk-schema',
60
+ origin: (env.VOVK_ORIGIN ?? conf.origin ?? '').replace(/\/$/, ''), // Remove trailing slash
61
+ rootEntry: env.VOVK_ROOT_ENTRY ?? conf.rootEntry ?? 'api',
62
+ rootSegmentModulesDirName: conf.rootSegmentModulesDirName ?? '',
63
+ logLevel: env.VOVK_LOG_LEVEL ?? conf.logLevel ?? 'info',
64
+ prettifyClient: (env.VOVK_PRETTIFY_CLIENT ? !!env.VOVK_PRETTIFY_CLIENT : null) ?? conf.prettifyClient ?? false,
65
+ devHttps: (env.VOVK_DEV_HTTPS ? !!env.VOVK_DEV_HTTPS : null) ?? conf.devHttps ?? false,
66
+ moduleTemplates: {
67
+ service: 'vovk-cli/module-templates/service.ts.ejs',
68
+ controller: 'vovk-cli/module-templates/controller.ts.ejs',
69
+ ...conf.moduleTemplates,
70
+ },
71
+ libs: conf.libs ?? {},
72
+ segmentConfig: conf.segmentConfig ?? {},
73
+ openApiMixins: {},
74
+ };
75
+ if (typeof conf.emitConfig === 'undefined') {
76
+ config.emitConfig = ['$schema', 'libs'];
77
+ }
78
+ else if (conf.emitConfig === true) {
79
+ config.emitConfig = Object.keys(config);
80
+ }
81
+ else if (Array.isArray(conf.emitConfig)) {
82
+ config.emitConfig = conf.emitConfig;
83
+ } // else it's false and emitConfig already is []
84
+ const log = getLogger(config.logLevel);
85
+ config.openApiMixins = await normalizeOpenAPIMixins({
86
+ mixinModules: conf.openApiMixins ?? {},
87
+ cwd,
88
+ log,
89
+ });
90
+ if (!userConfig) {
91
+ log.warn(`Unable to load config at ${chalkHighlightThing(cwd)}. Using default values. ${error ?? ''}`);
92
+ }
93
+ return { config, srcRoot, configAbsolutePaths, userConfig, log };
94
+ }
@@ -1,18 +1,20 @@
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;
16
18
  log: {
17
19
  info: (msg: string) => void;
18
20
  warn: (msg: string) => void;
@@ -1,37 +1,34 @@
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
+ if (srcRootRequired && !srcRoot) {
15
+ throw new Error(`Could not find app router directory at ${cwd}. Check Next.js docs for more info.`);
16
+ }
17
+ const apiRoot = `${config.origin ?? ''}/${config.rootEntry}`;
18
+ const apiDirAbsolutePath = srcRoot ? path.resolve(cwd, srcRoot, 'app', config.rootEntry) : null;
19
19
  if (configAbsolutePaths.length > 1) {
20
20
  log.warn(`Multiple config files found. Using the first one: ${configAbsolutePaths[0]}`);
21
21
  }
22
- if (!userConfig && configAbsolutePaths.length > 0) {
23
- log.error(`Error reading config file at ${configAbsolutePaths[0]}: ${error?.message ?? 'Unknown Error'}`);
24
- }
22
+ const vovkCliPackage = JSON.parse(await readFile(path.join(import.meta.dirname, '../../package.json'), 'utf-8'));
25
23
  return {
26
24
  cwd,
27
25
  port,
28
- apiEntryPoint,
29
- apiDir,
26
+ apiRoot,
27
+ apiDirAbsolutePath,
30
28
  srcRoot,
31
- schemaOutImportPath,
32
- fetcherClientImportPath,
33
- validateOnClientImportPath,
29
+ vovkCliPackage,
34
30
  config,
31
+ packageJson,
35
32
  log,
36
33
  };
37
34
  }
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,54 +1,64 @@
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('--client-out <path>', 'Path to client output directory')
22
- .option('--next-dev', 'Start schema watcher and Next.js with automatic port allocation', false)
23
- .allowUnknownOption(true)
24
- .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
+ .action(async (nextArgs, options) => {
28
+ const { nextDev, exit = false, schemaOut } = options;
25
29
  const portAttempts = 30;
26
- const PORT = !options.nextDev
30
+ const PORT = !nextDev
27
31
  ? process.env.PORT
28
32
  : process.env.PORT ||
29
33
  (await getAvailablePort(3000, portAttempts, 0, (failedPort, tryingPort) =>
30
34
  // eslint-disable-next-line no-console
31
- console.warn(`🐺 Next.js Port ${failedPort} is in use, trying ${tryingPort} instead.`)).catch(() => {
32
- throw new Error(`🐺 ❌ Failed to find available Next port after ${portAttempts} attempts`);
35
+ console.warn(`🐺 Port ${failedPort} is in use, trying ${tryingPort} instead.`)).catch(() => {
36
+ throw new Error(`🐺 ❌ Failed to find an available port after ${portAttempts} attempts`);
33
37
  }));
34
38
  if (!PORT) {
35
39
  throw new Error('🐺 ❌ PORT env variable is required');
36
40
  }
37
- if (options.nextDev) {
41
+ if (nextDev) {
38
42
  const { result } = concurrently([
39
43
  {
40
- command: `node ${import.meta.dirname}/dev/index.mjs`,
41
- name: 'Vovk Dev Command',
42
- env: Object.assign({ PORT, __VOVK_START_WATCHER_IN_STANDALONE_MODE__: 'true' }, options.clientOut ? { VOVK_CLIENT_OUT_DIR: options.clientOut } : {}),
43
- },
44
- {
45
- command: `npx next dev ${command.args.join(' ')}`,
44
+ command: `npx next dev ${nextArgs.join(' ')}`,
46
45
  name: 'Next.js Development Server',
47
46
  env: { PORT },
48
47
  },
48
+ {
49
+ command: `node ${import.meta.dirname}/dev/index.mjs`,
50
+ name: 'Vovk Dev Watcher',
51
+ env: {
52
+ PORT,
53
+ __VOVK_START_WATCHER_IN_STANDALONE_MODE__: 'true',
54
+ __VOVK_SCHEMA_OUT_FLAG__: schemaOut ?? '',
55
+ __VOVK_EXIT__: exit ? 'true' : 'false',
56
+ },
57
+ },
49
58
  ], {
50
- killOthers: ['failure', 'success'],
59
+ killOthersOn: ['failure', 'success'],
51
60
  prefix: 'none',
61
+ successCondition: 'first',
52
62
  });
53
63
  try {
54
64
  await result;
@@ -58,31 +68,79 @@ program
58
68
  }
59
69
  }
60
70
  else {
61
- void new VovkDev().start({ clientOutDir: options.clientOut });
71
+ void new VovkDev({ schemaOut }).start({ exit });
62
72
  }
63
73
  });
64
74
  program
65
75
  .command('generate')
66
- .description('Generate client')
67
- .option('--client-out <path>', 'Path to output directory')
68
- .action(async (options) => {
69
- const projectInfo = await getProjectInfo({ clientOutDir: options.clientOut });
70
- const { cwd, config, apiDir } = projectInfo;
71
- const segments = await locateSegments(apiDir);
72
- const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
73
- const schema = (await import(path.join(schemaOutAbsolutePath, 'index.js')));
74
- await generateClient(projectInfo, segments, schema.default);
76
+ .alias('g')
77
+ .description('generate RPC client from schema')
78
+ .option('--out, --composed-out <path>', 'path to output directory for composed client')
79
+ .option('--from, --composed-from <templates...>', 'client template names for composed client')
80
+ .option('--include, --composed-include-segments <segments...>', 'include segments in composed client')
81
+ .option('--exclude, --composed-exclude-segments <segments...>', 'exclude segments in composed client')
82
+ .option('--composed-only', 'generate only composed client even if segmented client is enabled')
83
+ .option('--segmented-only', 'generate only segmented client even if composed client is enabled')
84
+ .option('--segmented-out <path>', 'path to output directory for segmented client')
85
+ .option('--segmented-from <templates...>', 'client template names for segmented client')
86
+ .option('--segmented-include-segments <segments...>', 'include segments in segmented client')
87
+ .option('--segmented-exclude-segments <segments...>', 'exclude segments in segmented client')
88
+ .option('--prettify', 'prettify output files')
89
+ .option('--schema, --schema-path <path>', 'path to schema folder (default: ./.vovk-schema)')
90
+ .option('--config, --config-path <config>', 'path to config file')
91
+ .option('--force-ts-standalone', 'force TypeScript standalone mode (Next.js environment will be ignored, by default it\'s "true" for non-Next.js directories)')
92
+ .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')
93
+ .option('--openapi, --openapi-spec <openapi_path_or_urls...>', 'use OpenAPI schema instead of Vovk schema')
94
+ .option('--openapi-get-module-name <names...>', 'module names corresponding to the index of --openapi option')
95
+ .option('--openapi-get-method-name <names...>', 'method names corresponding to the index of --openapi option')
96
+ .option('--openapi-root-url <urls...>', 'root URLs corresponding to the index of --openapi option')
97
+ .action(async (cliGenerateOptions) => {
98
+ const projectInfo = await getProjectInfo({ configPath: cliGenerateOptions.configPath, srcRootRequired: false });
99
+ await new VovkGenerate({
100
+ projectInfo,
101
+ forceNothingWrittenLog: true,
102
+ cliGenerateOptions,
103
+ }).start();
104
+ });
105
+ program
106
+ .command('bundle')
107
+ .alias('b')
108
+ .description('generate TypeScrtipt RPC and bundle it')
109
+ .option('--out, --out-dir <path>', 'path to output directory for bundle')
110
+ .option('--include, --include-segments <segments...>', 'include segments')
111
+ .option('--exclude, --exclude-segments <segments...>', 'exclude segments')
112
+ .option('--ts-client-out-dir <path>', 'path to output directory for TypeScript client')
113
+ .option('--dont-delete-ts-client-out-dir-after', 'do not delete TypeScript client output directory after bundling')
114
+ .option('--config <config>', 'path to config file')
115
+ .option('--schema <path>', 'path to schema folder (default: .vovk-schema)')
116
+ .option('--sourcemap', 'generate sourcemaps')
117
+ .option('--force-ts-standalone', 'force TypeScript standalone mode (Next.js environment will be ignored, by default it\'s "true" for non-Next.js directories)')
118
+ .option('--openapi, --openapi-spec <openapi_path_or_urls...>', 'use OpenAPI schema instead of Vovk schema')
119
+ .option('--openapi-get-module-name <names...>', 'module names corresponding to the index of --openapi option')
120
+ .option('--openapi-get-method-name <names...>', 'method names corresponding to the index of --openapi option')
121
+ .option('--openapi-root-url <urls...>', 'root URLs corresponding to the index of --openapi option')
122
+ .action(async (cliBundleOptions) => {
123
+ const projectInfo = await getProjectInfo({ configPath: cliBundleOptions.config, srcRootRequired: false });
124
+ const { cwd, config, log } = projectInfo;
125
+ const fullSchema = await getProjectFullSchema(path.resolve(cwd, cliBundleOptions?.schema ?? config.schemaOutDir), log);
126
+ await bundle({
127
+ projectInfo,
128
+ fullSchema,
129
+ cliBundleOptions,
130
+ });
75
131
  });
76
132
  program
77
133
  .command('new [components...]')
78
134
  .alias('n')
79
- .description('Create new components. "vovk new [...components] [segmentName/]moduleName" to create a new module or "vovk new segment [segmentName]" to create a new segment')
80
- .option('-o, --overwrite', 'Overwrite existing files')
81
- .option('--template, --templates <templates...>', 'Override config template. Accepts an array of strings that correspond the order of the components')
82
- .option('--dir <dirname>', 'Override dirName in template file. Relative to the root of the project')
83
- .option('--no-segment-update', 'Do not update segment files when creating a new module')
84
- .option('--dry-run', 'Do not write files to disk')
85
- .action((components, options) => newComponents(components, options));
135
+ .description('create new components. "vovk new [...components] [segmentName/]moduleName" to create a new module or "vovk new segment [segmentName]" to create a new segment')
136
+ .option('-o, --overwrite', 'overwrite existing files')
137
+ .option('--template, --templates <templates...>', 'override config template; accepts an array of strings that correspond the order of the components')
138
+ .option('--dir <dirname>', 'override dirName in template file; relative to the root of the project')
139
+ .option('--empty', 'create an empty module')
140
+ .option('--no-segment-update', 'do not update segment files when creating a new module')
141
+ .option('--dry-run', 'do not write files to disk')
142
+ .option('--static', 'if the segment is static')
143
+ .action((components, newOptions) => newComponents(components, newOptions));
86
144
  program
87
145
  .command('help')
88
146
  .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, reactQuery, 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' | 'reactQuery' | '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, reactQuery, 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,32 @@ 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
+ if (reactQuery) {
34
+ config.imports ??= {};
35
+ config.imports.createRPC = 'vovk-react-query';
36
+ }
37
+ config.moduleTemplates = moduleTemplates;
38
+ const configStr = await prettify(`// @ts-check
39
+ /** @type {import('vovk').VovkConfig} */
34
40
  const config = ${JSON.stringify(config, null, 2)};
35
41
  ${isModule ? '\nexport default config;' : 'module.exports = config;'}`, configAbsolutePath);
36
42
  if (!dryRun)
@@ -21,8 +21,14 @@ export default async function getTemplateFilesFromPackage(packageName, channel =
21
21
  // Extract the tarball in memory and collect template files
22
22
  const templateFiles = await extractTemplatesFromTarball(tarballBuffer);
23
23
  const entries = templateFiles
24
- .filter((fileName) => fileName.startsWith('templates/') && !fileName.endsWith('/') && fileName.endsWith('.ejs'))
25
- .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
+ ]);
26
32
  return Object.fromEntries(entries);
27
33
  }
28
34
  /**
@@ -36,9 +42,8 @@ function extractTemplatesFromTarball(tarballBuffer) {
36
42
  const files = [];
37
43
  extract.on('entry', (header, stream, next) => {
38
44
  const filePath = header.name;
39
- // TODO revisit comments
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, reactQuery, lang, dryRun, channel, }: InitOptions): Promise<void>;
8
8
  }