vovk-cli 0.0.1-draft.39 → 0.0.1-draft.392

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 +1 -1
  2. package/README.md +31 -1
  3. package/client-templates/cjs/index.cjs.ejs +20 -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 +19 -0
  9. package/client-templates/openapiCjs/openapi.cjs.ejs +4 -0
  10. package/client-templates/openapiCjs/openapi.d.cts.ejs +4 -0
  11. package/client-templates/openapiJson/openapi.json.ejs +1 -0
  12. package/client-templates/openapiTs/openapi.ts.ejs +4 -0
  13. package/client-templates/packageJson/package.json.ejs +1 -0
  14. package/client-templates/readme/README.md.ejs +39 -0
  15. package/client-templates/schemaCjs/schema.cjs.ejs +24 -0
  16. package/client-templates/schemaCjs/schema.d.cts.ejs +10 -0
  17. package/client-templates/schemaJson/schema.json.ejs +1 -0
  18. package/client-templates/schemaTs/schema.ts.ejs +28 -0
  19. package/client-templates/ts/index.ts.ejs +33 -0
  20. package/dist/bundle/index.d.mts +8 -0
  21. package/dist/bundle/index.mjs +74 -0
  22. package/dist/dev/diffSegmentSchema.d.mts +36 -0
  23. package/dist/dev/{diffSchema.mjs → diffSegmentSchema.mjs} +3 -11
  24. package/dist/dev/ensureSchemaFiles.d.mts +4 -1
  25. package/dist/dev/ensureSchemaFiles.mjs +15 -31
  26. package/dist/dev/index.d.mts +5 -1
  27. package/dist/dev/index.mjs +141 -66
  28. package/dist/dev/logDiffResult.d.mts +1 -1
  29. package/dist/dev/logDiffResult.mjs +6 -43
  30. package/dist/dev/writeMetaJson.d.mts +2 -0
  31. package/dist/dev/writeMetaJson.mjs +20 -0
  32. package/dist/dev/writeOneSegmentSchemaFile.d.mts +12 -0
  33. package/dist/dev/{writeOneSchemaFile.mjs → writeOneSegmentSchemaFile.mjs} +10 -6
  34. package/dist/generate/ensureClient.d.mts +3 -0
  35. package/dist/generate/ensureClient.mjs +28 -0
  36. package/dist/generate/generate.d.mts +13 -0
  37. package/dist/generate/generate.mjs +300 -0
  38. package/dist/generate/getClientTemplateFiles.d.mts +20 -0
  39. package/dist/generate/getClientTemplateFiles.mjs +85 -0
  40. package/dist/generate/getProjectFullSchema.d.mts +8 -0
  41. package/dist/generate/getProjectFullSchema.mjs +66 -0
  42. package/dist/generate/getTemplateClientImports.d.mts +21 -0
  43. package/dist/generate/getTemplateClientImports.mjs +56 -0
  44. package/dist/generate/index.d.mts +33 -0
  45. package/dist/generate/index.mjs +186 -0
  46. package/dist/generate/writeOneClientFile.d.mts +42 -0
  47. package/dist/generate/writeOneClientFile.mjs +151 -0
  48. package/dist/getProjectInfo/getConfig/getConfigAbsolutePaths.d.mts +5 -0
  49. package/dist/getProjectInfo/{getConfigAbsolutePaths.mjs → getConfig/getConfigAbsolutePaths.mjs} +4 -1
  50. package/dist/getProjectInfo/{getRelativeSrcRoot.d.mts → getConfig/getRelativeSrcRoot.d.mts} +1 -1
  51. package/dist/getProjectInfo/{getRelativeSrcRoot.mjs → getConfig/getRelativeSrcRoot.mjs} +2 -2
  52. package/dist/getProjectInfo/getConfig/getTemplateDefs.d.mts +24 -0
  53. package/dist/getProjectInfo/getConfig/getTemplateDefs.mjs +165 -0
  54. package/dist/getProjectInfo/{getUserConfig.d.mts → getConfig/getUserConfig.d.mts} +3 -2
  55. package/dist/getProjectInfo/{getUserConfig.mjs → getConfig/getUserConfig.mjs} +3 -3
  56. package/dist/getProjectInfo/{importUncachedModule.mjs → getConfig/importUncachedModule.mjs} +1 -4
  57. package/dist/getProjectInfo/getConfig/index.d.mts +91 -0
  58. package/dist/getProjectInfo/getConfig/index.mjs +92 -0
  59. package/dist/getProjectInfo/getMetaSchema.d.mts +8 -0
  60. package/dist/getProjectInfo/getMetaSchema.mjs +27 -0
  61. package/dist/getProjectInfo/index.d.mts +14 -9
  62. package/dist/getProjectInfo/index.mjs +24 -22
  63. package/dist/index.d.mts +2 -2
  64. package/dist/index.mjs +116 -35
  65. package/dist/init/createConfig.d.mts +2 -2
  66. package/dist/init/createConfig.mjs +40 -13
  67. package/dist/init/createStandardSchemaValidatorFile.d.mts +4 -0
  68. package/dist/init/createStandardSchemaValidatorFile.mjs +52 -0
  69. package/dist/init/getTemplateFilesFromPackage.mjs +10 -5
  70. package/dist/init/index.d.mts +2 -2
  71. package/dist/init/index.mjs +121 -72
  72. package/dist/init/installDependencies.mjs +4 -2
  73. package/dist/init/logUpdateDependenciesError.d.mts +3 -1
  74. package/dist/init/logUpdateDependenciesError.mjs +7 -1
  75. package/dist/init/updateDependenciesWithoutInstalling.mjs +39 -9
  76. package/dist/init/updateNPMScripts.mjs +1 -1
  77. package/dist/init/updateTypeScriptConfig.d.mts +4 -1
  78. package/dist/init/updateTypeScriptConfig.mjs +11 -7
  79. package/dist/initProgram.d.mts +1 -1
  80. package/dist/initProgram.mjs +17 -17
  81. package/dist/locateSegments.d.mts +8 -1
  82. package/dist/locateSegments.mjs +13 -3
  83. package/dist/new/addClassToSegmentCode.d.mts +1 -2
  84. package/dist/new/addClassToSegmentCode.mjs +3 -3
  85. package/dist/new/index.d.mts +2 -1
  86. package/dist/new/index.mjs +5 -3
  87. package/dist/new/newModule.d.mts +5 -2
  88. package/dist/new/newModule.mjs +26 -24
  89. package/dist/new/newSegment.d.mts +4 -1
  90. package/dist/new/newSegment.mjs +20 -12
  91. package/dist/new/render.d.mts +7 -3
  92. package/dist/new/render.mjs +31 -10
  93. package/dist/types.d.mts +66 -44
  94. package/dist/utils/compileJSONSchemaToTypeScriptType.d.mts +5 -0
  95. package/dist/utils/compileJSONSchemaToTypeScriptType.mjs +9 -0
  96. package/dist/utils/compileTs.d.mts +12 -0
  97. package/dist/utils/compileTs.mjs +261 -0
  98. package/dist/utils/debounceWithArgs.d.mts +1 -1
  99. package/dist/utils/deepExtend.d.mts +54 -0
  100. package/dist/utils/deepExtend.mjs +129 -0
  101. package/dist/utils/formatLoggedSegmentName.d.mts +3 -1
  102. package/dist/utils/formatLoggedSegmentName.mjs +3 -2
  103. package/dist/utils/generateFnName.d.mts +23 -0
  104. package/dist/utils/generateFnName.mjs +76 -0
  105. package/dist/utils/getPackageJson.d.mts +3 -0
  106. package/dist/utils/getPackageJson.mjs +22 -0
  107. package/dist/utils/getPublicModuleNameFromPath.d.mts +4 -0
  108. package/dist/utils/getPublicModuleNameFromPath.mjs +9 -0
  109. package/dist/utils/normalizeOpenAPIMixin.d.mts +14 -0
  110. package/dist/utils/normalizeOpenAPIMixin.mjs +114 -0
  111. package/dist/utils/pickSegmentFullSchema.d.mts +3 -0
  112. package/dist/utils/pickSegmentFullSchema.mjs +15 -0
  113. package/dist/utils/removeUnlistedDirectories.d.mts +10 -0
  114. package/dist/utils/removeUnlistedDirectories.mjs +61 -0
  115. package/dist/utils/resolveAbsoluteModulePath.d.mts +2 -0
  116. package/dist/utils/resolveAbsoluteModulePath.mjs +32 -0
  117. package/module-templates/arktype/controller.ts.ejs +78 -0
  118. package/module-templates/type/controller.ts.ejs +64 -0
  119. package/module-templates/type/service.ts.ejs +37 -0
  120. package/module-templates/valibot/controller.ts.ejs +78 -0
  121. package/package.json +39 -22
  122. package/dist/dev/diffSchema.d.mts +0 -43
  123. package/dist/dev/ensureClient.d.mts +0 -5
  124. package/dist/dev/ensureClient.mjs +0 -31
  125. package/dist/dev/isMetadataEmpty.d.mts +0 -2
  126. package/dist/dev/isMetadataEmpty.mjs +0 -4
  127. package/dist/dev/writeOneSchemaFile.d.mts +0 -11
  128. package/dist/generateClient.d.mts +0 -7
  129. package/dist/generateClient.mjs +0 -97
  130. package/dist/getProjectInfo/getConfig.d.mts +0 -11
  131. package/dist/getProjectInfo/getConfig.mjs +0 -29
  132. package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +0 -4
  133. package/dist/postinstall.d.mts +0 -1
  134. package/dist/postinstall.mjs +0 -24
  135. package/templates/controller.ejs +0 -52
  136. package/templates/service.ejs +0 -27
  137. package/templates/worker.ejs +0 -24
  138. /package/dist/getProjectInfo/{importUncachedModule.d.mts → getConfig/importUncachedModule.d.mts} +0 -0
  139. /package/dist/getProjectInfo/{importUncachedModuleWorker.d.mts → getConfig/importUncachedModuleWorker.d.mts} +0 -0
  140. /package/dist/getProjectInfo/{importUncachedModuleWorker.mjs → getConfig/importUncachedModuleWorker.mjs} +0 -0
@@ -0,0 +1,91 @@
1
+ import { VovkSchemaIdEnum, type VovkStrictConfig } from 'vovk';
2
+ import type { LogLevelNames } from 'loglevel';
3
+ export default function getConfig({ configPath, cwd, logLevel, }: {
4
+ configPath?: string;
5
+ cwd: string;
6
+ logLevel?: LogLevelNames;
7
+ }): Promise<{
8
+ config: VovkStrictConfig;
9
+ srcRoot: string | null;
10
+ configAbsolutePaths: string[];
11
+ userConfig: {
12
+ $schema?: typeof VovkSchemaIdEnum.CONFIG | (string & {});
13
+ emitConfig?: boolean | (keyof VovkStrictConfig | (string & {}))[];
14
+ schemaOutDir?: string;
15
+ modulesDir?: string;
16
+ rootEntry?: string;
17
+ logLevel?: "error" | "trace" | "debug" | "info" | "warn" | (string & {});
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
+ prettifyClient?: boolean;
28
+ } & ({
29
+ excludeSegments?: never;
30
+ includeSegments?: string[];
31
+ } | {
32
+ excludeSegments?: string[];
33
+ includeSegments?: never;
34
+ });
35
+ segmentedClient?: {
36
+ enabled?: boolean;
37
+ outDir?: string;
38
+ fromTemplates?: string[];
39
+ prettifyClient?: boolean;
40
+ } & ({
41
+ excludeSegments?: never;
42
+ includeSegments?: string[];
43
+ } | {
44
+ excludeSegments?: string[];
45
+ includeSegments?: never;
46
+ });
47
+ bundle?: {
48
+ requires?: Record<string, string>;
49
+ prebundleOutDir?: string;
50
+ keepPrebundleDir?: boolean;
51
+ outDir?: string;
52
+ build: (options: {
53
+ outDir: string;
54
+ prebundleDir: string;
55
+ entry: string;
56
+ }) => Promise<void>;
57
+ outputConfig?: import("vovk").VovkOutputConfig<{
58
+ fetcher?: string | [string, string] | [string];
59
+ validateOnClient?: string | [string, string] | [string] | null;
60
+ createRPC?: string | [string, string] | [string];
61
+ }>;
62
+ } & ({
63
+ excludeSegments?: never;
64
+ includeSegments?: string[];
65
+ } | {
66
+ excludeSegments?: string[];
67
+ includeSegments?: never;
68
+ });
69
+ clientTemplateDefs?: Record<string, import("vovk/types").ClientTemplateDef>;
70
+ rootSegmentModulesDirName?: string;
71
+ moduleTemplates?: {
72
+ service?: string;
73
+ controller?: string;
74
+ [key: string]: string | undefined;
75
+ };
76
+ outputConfig?: import("vovk").VovkOutputConfig<{
77
+ fetcher?: string | [string, string] | [string];
78
+ validateOnClient?: string | [string, string] | [string] | null;
79
+ createRPC?: string | [string, string] | [string];
80
+ }> & {
81
+ segments?: Record<string, import("vovk/types").VovkSegmentConfig>;
82
+ };
83
+ } | null;
84
+ log: {
85
+ info: (msg: string) => void;
86
+ warn: (msg: string) => void;
87
+ error: (msg: string) => void;
88
+ debug: (msg: string) => void;
89
+ raw: import("loglevel").RootLogger;
90
+ };
91
+ }>;
@@ -0,0 +1,92 @@
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 { normalizeOpenAPIMixin } from '../../utils/normalizeOpenAPIMixin.mjs';
8
+ import chalkHighlightThing from '../../utils/chalkHighlightThing.mjs';
9
+ export default async function getConfig({ configPath, cwd, logLevel, }) {
10
+ const { configAbsolutePaths, error, userConfig } = await getUserConfig({
11
+ configPath,
12
+ cwd,
13
+ });
14
+ const conf = userConfig ?? {};
15
+ logLevel = logLevel ?? conf.logLevel ?? 'info';
16
+ const log = getLogger(logLevel);
17
+ const env = process.env;
18
+ const clientTemplateDefs = getTemplateDefs(conf.clientTemplateDefs);
19
+ const srcRoot = await getRelativeSrcRoot({ cwd });
20
+ const config = {
21
+ $schema: VovkSchemaIdEnum.CONFIG,
22
+ clientTemplateDefs,
23
+ emitConfig: [],
24
+ composedClient: {
25
+ ...conf.composedClient,
26
+ enabled: conf.composedClient?.enabled ?? true,
27
+ fromTemplates: conf.composedClient?.fromTemplates ?? ['mjs', 'cjs'],
28
+ outDir: conf.composedClient?.outDir ?? './node_modules/.vovk-client',
29
+ prettifyClient: conf.composedClient?.prettifyClient ?? false,
30
+ },
31
+ segmentedClient: {
32
+ ...conf.segmentedClient,
33
+ enabled: conf.segmentedClient?.enabled ?? false,
34
+ fromTemplates: conf.segmentedClient?.fromTemplates ?? ['ts'],
35
+ outDir: conf.segmentedClient?.outDir ?? path.join(srcRoot ?? '.', 'client'),
36
+ prettifyClient: conf.segmentedClient?.prettifyClient ?? true,
37
+ },
38
+ bundle: {
39
+ prebundleOutDir: conf.bundle?.prebundleOutDir ?? 'tmp_prebundle',
40
+ keepPrebundleDir: conf.bundle?.keepPrebundleDir ?? false,
41
+ outDir: conf.bundle?.outDir ?? 'dist',
42
+ requires: {
43
+ [BuiltInTemplateName.readme]: '.',
44
+ [BuiltInTemplateName.packageJson]: '.',
45
+ },
46
+ outputConfig: {},
47
+ build: conf.bundle?.build ??
48
+ (() => {
49
+ throw new Error('No bundle.build function specified');
50
+ }),
51
+ ...conf.bundle,
52
+ },
53
+ modulesDir: conf.modulesDir ?? path.join(srcRoot ?? '.', 'modules'),
54
+ schemaOutDir: env.VOVK_SCHEMA_OUT_DIR ?? conf.schemaOutDir ?? './.vovk-schema',
55
+ rootEntry: env.VOVK_ROOT_ENTRY ?? conf.rootEntry ?? 'api',
56
+ rootSegmentModulesDirName: conf.rootSegmentModulesDirName ?? '',
57
+ logLevel,
58
+ devHttps: conf.devHttps ?? false,
59
+ moduleTemplates: {
60
+ service: 'vovk-cli/module-templates/type/service.ts.ejs',
61
+ controller: 'vovk-cli/module-templates/type/controller.ts.ejs',
62
+ ...conf.moduleTemplates,
63
+ },
64
+ libs: conf.libs ?? {},
65
+ outputConfig: {
66
+ ...conf.outputConfig,
67
+ origin: (env.VOVK_ORIGIN ?? conf?.outputConfig?.origin ?? '').replace(/\/$/, ''), // Remove trailing slash
68
+ segments: Object.fromEntries(await Promise.all(Object.entries(conf.outputConfig?.segments ?? {}).map(async ([key, value]) => [
69
+ key,
70
+ {
71
+ ...value,
72
+ openAPIMixin: value.openAPIMixin
73
+ ? await normalizeOpenAPIMixin({ mixinModule: value.openAPIMixin, log, cwd })
74
+ : undefined,
75
+ },
76
+ ]))),
77
+ },
78
+ };
79
+ if (typeof conf.emitConfig === 'undefined') {
80
+ config.emitConfig = ['libs'];
81
+ }
82
+ else if (conf.emitConfig === true) {
83
+ config.emitConfig = Object.keys(config);
84
+ }
85
+ else if (Array.isArray(conf.emitConfig)) {
86
+ config.emitConfig = conf.emitConfig;
87
+ } // else it's false and emitConfig already is []
88
+ if (!userConfig) {
89
+ log.warn(`Unable to load config at ${chalkHighlightThing(cwd)}. Using default values. ${error ?? ''}`);
90
+ }
91
+ return { config, srcRoot, configAbsolutePaths, userConfig, log };
92
+ }
@@ -0,0 +1,8 @@
1
+ import { VovkSchemaIdEnum, type VovkStrictConfig } from 'vovk';
2
+ export default function getMetaSchema({ config, useEmitConfig }: {
3
+ config: VovkStrictConfig;
4
+ useEmitConfig: boolean;
5
+ }): {
6
+ config: VovkStrictConfig;
7
+ $schema: VovkSchemaIdEnum;
8
+ };
@@ -0,0 +1,27 @@
1
+ import { VovkSchemaIdEnum } from 'vovk';
2
+ import pick from 'lodash/pick.js';
3
+ import mapValues from 'lodash/mapValues.js';
4
+ import omit from 'lodash/omit.js';
5
+ export default function getMetaSchema({ config, useEmitConfig }) {
6
+ return {
7
+ $schema: VovkSchemaIdEnum.META,
8
+ ...{
9
+ config: useEmitConfig
10
+ ? (config
11
+ ? pick({
12
+ ...config,
13
+ outputConfig: config.outputConfig
14
+ ? {
15
+ ...config.outputConfig,
16
+ // TODO: Dirty fix! Need to think of a better way to avoid emission of mixins.
17
+ segments: config.outputConfig.segments
18
+ ? mapValues(config.outputConfig.segments, (segment) => omit(segment, ['openAPIMixin']))
19
+ : undefined,
20
+ }
21
+ : undefined,
22
+ }, [...config.emitConfig, '$schema'])
23
+ : {})
24
+ : config,
25
+ },
26
+ };
27
+ }
@@ -1,18 +1,23 @@
1
+ import { LogLevelNames } from 'loglevel';
1
2
  export type ProjectInfo = Awaited<ReturnType<typeof getProjectInfo>>;
2
- export default function getProjectInfo({ port: givenPort, clientOutDir, cwd, }?: {
3
+ export default function getProjectInfo({ port: givenPort, cwd, configPath, srcRootRequired, logLevel, }?: {
3
4
  port?: number;
4
- clientOutDir?: string;
5
5
  cwd?: string;
6
+ configPath?: string;
7
+ srcRootRequired?: boolean;
8
+ logLevel?: LogLevelNames;
6
9
  }): Promise<{
7
10
  cwd: string;
8
11
  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>;
12
+ apiRoot: string;
13
+ apiDirAbsolutePath: string | null;
14
+ srcRoot: string | null;
15
+ vovkCliPackage: {
16
+ version: string;
17
+ };
18
+ config: import("vovk/types").VovkStrictConfig;
19
+ packageJson: import("type-fest").PackageJson;
20
+ isNextInstalled: boolean;
16
21
  log: {
17
22
  info: (msg: string) => void;
18
23
  warn: (msg: string) => void;
@@ -1,37 +1,39 @@
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, logLevel, } = {
6
+ logLevel: 'info',
7
+ }) {
5
8
  const port = givenPort?.toString() ?? process.env.PORT ?? '3000';
6
9
  // Make PORT available to the config file at getConfig
7
10
  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).replace(/\\/g, '/'); // windows fix
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);
11
+ const { config, srcRoot, configAbsolutePaths, log } = await getConfig({
12
+ configPath,
13
+ cwd,
14
+ logLevel,
15
+ });
16
+ const packageJson = await getPackageJson(cwd, log);
17
+ const isNextInstalled = !!packageJson?.dependencies?.next || !!packageJson?.devDependencies?.next;
18
+ if (srcRootRequired && !srcRoot) {
19
+ throw new Error(`Could not find app router directory at ${cwd}. Check Next.js docs for more info.`);
20
+ }
21
+ const apiRoot = `${config.outputConfig.origin ?? ''}/${config.rootEntry}`;
22
+ const apiDirAbsolutePath = srcRoot ? path.resolve(cwd, srcRoot, 'app', config.rootEntry) : null;
19
23
  if (configAbsolutePaths.length > 1) {
20
24
  log.warn(`Multiple config files found. Using the first one: ${configAbsolutePaths[0]}`);
21
25
  }
22
- if (!userConfig && configAbsolutePaths.length > 0) {
23
- log.error(`Error reading config file at ${configAbsolutePaths[0]}: ${error?.message ?? 'Unknown Error'}`);
24
- }
26
+ const vovkCliPackage = JSON.parse(await readFile(path.join(import.meta.dirname, '../../package.json'), 'utf-8'));
25
27
  return {
26
28
  cwd,
27
29
  port,
28
- apiEntryPoint,
29
- apiDir,
30
+ apiRoot,
31
+ apiDirAbsolutePath,
30
32
  srcRoot,
31
- schemaOutImportPath,
32
- fetcherClientImportPath,
33
- validateOnClientImportPath,
33
+ vovkCliPackage,
34
34
  config,
35
+ packageJson,
36
+ isNextInstalled,
35
37
  log,
36
38
  };
37
39
  }
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
  import 'dotenv/config';
3
- import type { VovkConfig, VovkEnv } from './types.mjs';
4
- export type { VovkConfig, VovkEnv };
3
+ import type { VovkEnv } from './types.mjs';
4
+ export type { VovkEnv };
package/dist/index.mjs CHANGED
@@ -1,54 +1,69 @@
1
1
  #!/usr/bin/env node
2
2
  import path from 'node:path';
3
3
  import { readFileSync } from 'node:fs';
4
- import { pathToFileURL } from 'node:url';
5
4
  import 'dotenv/config';
6
5
  import { Command } from 'commander';
7
6
  import concurrently from 'concurrently';
8
7
  import getAvailablePort from './utils/getAvailablePort.mjs';
9
8
  import getProjectInfo from './getProjectInfo/index.mjs';
10
- import generateClient from './generateClient.mjs';
11
- import locateSegments from './locateSegments.mjs';
9
+ import { VovkGenerate } from './generate/index.mjs';
10
+ import { bundle } from './bundle/index.mjs';
12
11
  import { VovkDev } from './dev/index.mjs';
13
- import newComponents from './new/index.mjs';
14
- 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';
15
15
  const program = new Command();
16
- const packageJSON = JSON.parse(readFileSync(path.join(import.meta.dirname, '../package.json'), 'utf-8'));
17
- 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);
18
18
  initProgram(program.command('init'));
19
19
  program
20
20
  .command('dev')
21
+ .alias('d')
21
22
  .description('Start schema watcher (optional flag --next-dev to start it with Next.js)')
22
- .option('--next-dev', 'Start schema watcher and Next.js with automatic port allocation', false)
23
- .allowUnknownOption(true)
24
- .action(async (options, command) => {
23
+ .argument('[nextArgs...]', 'extra arguments for the implicit next dev command call')
24
+ .option('--next-dev', 'start schema watcher and Next.js with automatic port allocation')
25
+ .option('--exit', 'kill the processes 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
+ .option('--log-level <level>', 'set the log level')
29
+ .action(async (nextArgs, options) => {
30
+ const { nextDev, exit = false, schemaOut, devHttps, logLevel } = options;
25
31
  const portAttempts = 30;
26
- const PORT = !options.nextDev
32
+ const PORT = !nextDev
27
33
  ? process.env.PORT
28
34
  : process.env.PORT ||
29
35
  (await getAvailablePort(3000, portAttempts, 0, (failedPort, tryingPort) =>
30
36
  // 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`);
37
+ console.warn(`🐺 Port ${failedPort} is in use, trying ${tryingPort} instead.`)).catch(() => {
38
+ throw new Error(`🐺 ❌ Failed to find an available port after ${portAttempts} attempts`);
33
39
  }));
34
40
  if (!PORT) {
35
41
  throw new Error('🐺 ❌ PORT env variable is required');
36
42
  }
37
- if (options.nextDev) {
43
+ if (nextDev) {
38
44
  const { result } = concurrently([
39
45
  {
40
- command: `npx next dev ${command.args.join(' ')}`,
46
+ command: `npx next dev ${nextArgs.join(' ')}`,
41
47
  name: 'Next.js Development Server',
42
48
  env: { PORT },
43
49
  },
44
50
  {
45
51
  command: `node ${import.meta.dirname}/dev/index.mjs`,
46
- name: 'Vovk Dev Command',
47
- env: { PORT, __VOVK_START_WATCHER_IN_STANDALONE_MODE__: 'true' },
52
+ name: 'Vovk Dev Watcher',
53
+ env: {
54
+ PORT,
55
+ __VOVK_START_WATCHER_IN_STANDALONE_MODE__: 'true',
56
+ // TODO: Pass these as flags
57
+ __VOVK_SCHEMA_OUT_FLAG__: schemaOut ?? '',
58
+ __VOVK_DEV_HTTPS_FLAG__: devHttps ? 'true' : 'false',
59
+ __VOVK_EXIT__: exit ? 'true' : 'false',
60
+ __VOVK_LOG_LEVEL__: logLevel ?? undefined,
61
+ },
48
62
  },
49
63
  ], {
50
- killOthers: ['failure', 'success'],
64
+ killOthersOn: ['failure', 'success'],
51
65
  prefix: 'none',
66
+ successCondition: 'first',
52
67
  });
53
68
  try {
54
69
  await result;
@@ -58,32 +73,98 @@ program
58
73
  }
59
74
  }
60
75
  else {
61
- void new VovkDev().start();
76
+ void new VovkDev({ schemaOut, devHttps, logLevel }).start({ exit });
62
77
  }
63
78
  });
64
79
  program
65
80
  .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 schemaImportUrl = pathToFileURL(path.join(schemaOutAbsolutePath, 'index.js')).href;
74
- const schema = await import(schemaImportUrl);
75
- await generateClient(projectInfo, segments, schema.default);
81
+ .alias('g')
82
+ .description('Generate RPC client from schema')
83
+ .option('--composed-only', 'generate only composed client even if segmented client is enabled')
84
+ .option('--out, --composed-out <path>', 'path to output directory for composed client')
85
+ .option('--from, --composed-from <templates...>', 'client template names for composed client')
86
+ .option('--include, --composed-include-segments <segments...>', 'include segments in composed client')
87
+ .option('--exclude, --composed-exclude-segments <segments...>', 'exclude segments in composed client')
88
+ .option('--segmented-only', 'generate only segmented client even if composed client is enabled')
89
+ .option('--segmented-out <path>', 'path to output directory for segmented client')
90
+ .option('--segmented-from <templates...>', 'client template names for segmented client')
91
+ .option('--segmented-include-segments <segments...>', 'include segments in segmented client')
92
+ .option('--segmented-exclude-segments <segments...>', 'exclude segments in segmented client')
93
+ .option('--prettify', 'prettify output files')
94
+ .option('--schema, --schema-path <path>', 'path to schema folder (default: ./.vovk-schema)')
95
+ .option('--config, --config-path <config>', 'path to config file')
96
+ .option('--origin <url>', 'set the origin URL for the generated client')
97
+ .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')
98
+ .option('--openapi, --openapi-spec <openapi_path_or_urls...>', 'use OpenAPI schema for client generation')
99
+ .option('--openapi-module-name, --openapi-get-module-name <names...>', 'module names corresponding to the index of --openapi option')
100
+ .option('--openapi-method-name, --openapi-get-method-name <names...>', 'method names corresponding to the index of --openapi option')
101
+ .option('--openapi-root-url <urls...>', 'root URLs corresponding to the index of --openapi option')
102
+ .option('--openapi-mixin-name <names...>', 'mixin names corresponding to the index of --openapi option')
103
+ .option('--openapi-fallback <paths...>', 'save OpenAPI spec and use it as a fallback if URL is not available')
104
+ .option('--log-level <level>', 'set the log level')
105
+ .action(async (cliGenerateOptions) => {
106
+ const projectInfo = await getProjectInfo({
107
+ configPath: cliGenerateOptions.configPath,
108
+ srcRootRequired: false,
109
+ logLevel: cliGenerateOptions.logLevel,
110
+ });
111
+ await new VovkGenerate({
112
+ projectInfo,
113
+ forceNothingWrittenLog: true,
114
+ cliGenerateOptions,
115
+ }).start();
116
+ });
117
+ program
118
+ .command('bundle')
119
+ .alias('b')
120
+ .description('Generate TypeScript RPC and bundle it')
121
+ .option('--out, --out-dir <path>', 'path to output directory for bundle')
122
+ .option('--include, --include-segments <segments...>', 'include segments')
123
+ .option('--exclude, --exclude-segments <segments...>', 'exclude segments')
124
+ .option('--prebundle-out-dir, --prebundle-out <path>', 'path to output directory for prebundle')
125
+ .option('--keep-prebundle-dir', 'do not delete prebundle directory after bundling')
126
+ .option('--schema, --schema-path <path>', 'path to schema folder (default: .vovk-schema)')
127
+ .option('--config, --config-path <config>', 'path to config file')
128
+ .option('--origin <url>', 'set the origin URL for the generated client')
129
+ .option('--openapi, --openapi-spec <openapi_path_or_urls...>', 'use OpenAPI schema instead of Vovk schema')
130
+ .option('--openapi-get-module-name <names...>', 'module names corresponding to the index of --openapi option')
131
+ .option('--openapi-get-method-name <names...>', 'method names corresponding to the index of --openapi option')
132
+ .option('--openapi-root-url <urls...>', 'root URLs corresponding to the index of --openapi option')
133
+ .option('--log-level <level>', 'set the log level')
134
+ .action(async (cliBundleOptions) => {
135
+ const projectInfo = await getProjectInfo({
136
+ configPath: cliBundleOptions.configPath,
137
+ srcRootRequired: false,
138
+ logLevel: cliBundleOptions.logLevel,
139
+ });
140
+ const { cwd, config, log, isNextInstalled } = projectInfo;
141
+ const fullSchema = await getProjectFullSchema({
142
+ schemaOutAbsolutePath: path.resolve(cwd, cliBundleOptions?.schema ?? config.schemaOutDir),
143
+ log,
144
+ isNextInstalled,
145
+ config,
146
+ });
147
+ await bundle({
148
+ projectInfo,
149
+ fullSchema,
150
+ cliBundleOptions,
151
+ });
76
152
  });
77
153
  program
78
154
  .command('new [components...]')
79
155
  .alias('n')
80
156
  .description('Create new components. "vovk new [...components] [segmentName/]moduleName" to create a new module or "vovk new segment [segmentName]" to create a new segment')
81
- .option('-o, --overwrite', 'Overwrite existing files')
82
- .option('--template, --templates <templates...>', 'Override config template. Accepts an array of strings that correspond the order of the components')
83
- .option('--dir <dirname>', 'Override dirName in template file. Relative to the root of the project')
84
- .option('--no-segment-update', 'Do not update segment files when creating a new module')
85
- .option('--dry-run', 'Do not write files to disk')
86
- .action((components, options) => newComponents(components, options));
157
+ .option('-o, --overwrite', 'overwrite existing files')
158
+ .option('--static', '(new segment only) if the segment is static')
159
+ .option('--template, --templates <templates...>', '(new module only) override config template; accepts an array of strings that correspond the order of the components')
160
+ .option('--out, --out-dir <dirname>', '(new module only) override outDir in template file; relative to the root of the project')
161
+ .option('--no-segment-update', '(new module only) do not update segment files when creating a new module')
162
+ .option('--empty', '(new module only) create an empty module')
163
+ .option('--dry-run', 'do not write files to disk')
164
+ .option('--log-level <level>', 'set the log level')
165
+ .action(async (components, newOptions) => newComponents(components, await getProjectInfo({
166
+ logLevel: newOptions.logLevel,
167
+ }), newOptions));
87
168
  program
88
169
  .command('help')
89
170
  .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,34 +3,61 @@ 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;
10
10
  const isModule = await fs
11
11
  .readFile(path.join(root, 'package.json'), 'utf-8')
12
+ .catch(() => '{}')
12
13
  .then((content) => JSON.parse(content).type === 'module');
13
14
  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',
15
+ const typeTemplates = {
16
+ controller: 'vovk-cli/module-templates/type/controller.ts.ejs',
17
+ service: 'vovk-cli/module-templates/type/service.ts.ejs',
18
18
  };
19
- if (validationLibrary) {
20
- config.validationLibrary = validationLibrary;
21
- if (validateOnClient) {
22
- config.validateOnClient = `${validationLibrary}/validateOnClient`;
23
- }
19
+ const moduleTemplates = {
20
+ ...typeTemplates,
21
+ ...{
22
+ type: typeTemplates,
23
+ zod: {
24
+ controller: 'vovk-zod/module-templates/controller.ts.ejs',
25
+ },
26
+ yup: {
27
+ controller: 'vovk-yup/module-templates/controller.ts.ejs',
28
+ },
29
+ 'class-validator': {
30
+ controller: 'vovk-dto/module-templates/controller.ts.ejs',
31
+ },
32
+ valibot: {
33
+ controller: 'vovk-cli/module-templates/valibot/controller.ts.ejs',
34
+ },
35
+ arktype: {
36
+ controller: 'vovk-cli/module-templates/arktype/controller.ts.ejs',
37
+ },
38
+ }[validationLibrary ?? 'type'],
39
+ };
40
+ config.outputConfig ??= {};
41
+ config.outputConfig.imports ??= {};
42
+ config.outputConfig.imports.validateOnClient =
43
+ validationLibrary === 'class-validator' ? 'vovk-dto/validateOnClient' : 'vovk-ajv';
44
+ if (validationLibrary && !moduleTemplates) {
24
45
  try {
46
+ // TODO: Legacy, is it useful to keep it?
25
47
  const validationTemplates = await getTemplateFilesFromPackage(validationLibrary, channel);
26
- Object.assign(templates, validationTemplates);
48
+ Object.assign(moduleTemplates, validationTemplates);
27
49
  }
28
50
  catch (error) {
29
51
  log.warn(`Failed to fetch validation library templates: ${error.message}`);
30
52
  }
31
53
  }
32
- config.templates = templates;
33
- const configStr = await prettify(`/** @type {import('vovk-cli').VovkConfig} */
54
+ if (lang?.length) {
55
+ config.composedClient ??= {};
56
+ config.composedClient.fromTemplates = ['mjs', 'cjs', ...lang];
57
+ }
58
+ config.moduleTemplates = moduleTemplates;
59
+ const configStr = await prettify(`// @ts-check
60
+ /** @type {import('vovk').VovkConfig} */
34
61
  const config = ${JSON.stringify(config, null, 2)};
35
62
  ${isModule ? '\nexport default config;' : 'module.exports = config;'}`, configAbsolutePath);
36
63
  if (!dryRun)
@@ -0,0 +1,4 @@
1
+ export declare function createStandardSchemaValidatorFile({ root, validationLibrary, }: {
2
+ root: string;
3
+ validationLibrary: 'arktype' | 'valibot';
4
+ }): Promise<void>;