vovk-cli 0.0.1-draft.2 → 0.0.1-draft.200

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 (142) 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 +15 -0
  5. package/client-templates/mjs/index.d.mts.ejs +15 -0
  6. package/client-templates/mjs/index.mjs.ejs +20 -0
  7. package/client-templates/packageJson/package.json.ejs +1 -0
  8. package/client-templates/readme/README.md.ejs +35 -0
  9. package/client-templates/schemaCjs/schema.cjs.ejs +15 -0
  10. package/client-templates/schemaCjs/schema.d.cts.ejs +10 -0
  11. package/client-templates/schemaJson/schema.json.ejs +1 -0
  12. package/client-templates/schemaTs/schema.ts.ejs +17 -0
  13. package/client-templates/ts/index.ts.ejs +22 -0
  14. package/dist/bundle/index.d.mts +8 -0
  15. package/dist/bundle/index.mjs +82 -0
  16. package/dist/dev/diffSegmentSchema.d.mts +36 -0
  17. package/dist/{watcher/diffSchema.mjs → dev/diffSegmentSchema.mjs} +4 -12
  18. package/dist/{watcher → dev}/ensureSchemaFiles.d.mts +3 -0
  19. package/dist/{watcher → dev}/ensureSchemaFiles.mjs +16 -21
  20. package/dist/dev/index.d.mts +9 -0
  21. package/dist/dev/index.mjs +386 -0
  22. package/dist/dev/logDiffResult.d.mts +3 -0
  23. package/dist/dev/logDiffResult.mjs +57 -0
  24. package/dist/dev/writeMetaJson.d.mts +2 -0
  25. package/dist/dev/writeMetaJson.mjs +17 -0
  26. package/dist/dev/writeOneSegmentSchemaFile.d.mts +12 -0
  27. package/dist/dev/writeOneSegmentSchemaFile.mjs +32 -0
  28. package/dist/generate/ensureClient.d.mts +3 -0
  29. package/dist/generate/ensureClient.mjs +31 -0
  30. package/dist/generate/generate.d.mts +12 -0
  31. package/dist/generate/generate.mjs +214 -0
  32. package/dist/generate/getClientTemplateFiles.d.mts +20 -0
  33. package/dist/generate/getClientTemplateFiles.mjs +77 -0
  34. package/dist/generate/getProjectFullSchema.d.mts +3 -0
  35. package/dist/generate/getProjectFullSchema.mjs +64 -0
  36. package/dist/generate/getTemplateClientImports.d.mts +18 -0
  37. package/dist/generate/getTemplateClientImports.mjs +38 -0
  38. package/dist/generate/index.d.mts +40 -0
  39. package/dist/generate/index.mjs +212 -0
  40. package/dist/generate/mergePackages.d.mts +9 -0
  41. package/dist/generate/mergePackages.mjs +78 -0
  42. package/dist/generate/writeOneClientFile.d.mts +31 -0
  43. package/dist/generate/writeOneClientFile.mjs +95 -0
  44. package/dist/getProjectInfo/getConfig/getConfigAbsolutePaths.d.mts +5 -0
  45. package/dist/getProjectInfo/{getConfigAbsolutePaths.mjs → getConfig/getConfigAbsolutePaths.mjs} +6 -3
  46. package/dist/getProjectInfo/{getRelativeSrcRoot.d.mts → getConfig/getRelativeSrcRoot.d.mts} +1 -1
  47. package/dist/getProjectInfo/getConfig/getRelativeSrcRoot.mjs +12 -0
  48. package/dist/getProjectInfo/getConfig/getTemplateDefs.d.mts +14 -0
  49. package/dist/getProjectInfo/getConfig/getTemplateDefs.mjs +85 -0
  50. package/dist/getProjectInfo/{getUserConfig.d.mts → getConfig/getUserConfig.d.mts} +3 -2
  51. package/dist/getProjectInfo/{getUserConfig.mjs → getConfig/getUserConfig.mjs} +7 -5
  52. package/dist/getProjectInfo/{importUncachedModule.mjs → getConfig/importUncachedModule.mjs} +1 -5
  53. package/dist/getProjectInfo/{importUncachedModuleWorker.mjs → getConfig/importUncachedModuleWorker.mjs} +0 -1
  54. package/dist/getProjectInfo/getConfig/index.d.mts +17 -0
  55. package/dist/getProjectInfo/getConfig/index.mjs +83 -0
  56. package/dist/getProjectInfo/index.d.mts +6 -8
  57. package/dist/getProjectInfo/index.mjs +13 -22
  58. package/dist/index.d.mts +2 -24
  59. package/dist/index.mjs +91 -66
  60. package/dist/init/checkTSConfigForExperimentalDecorators.mjs +2 -2
  61. package/dist/init/createConfig.d.mts +3 -4
  62. package/dist/init/createConfig.mjs +22 -16
  63. package/dist/init/getTemplateFilesFromPackage.d.mts +2 -1
  64. package/dist/init/getTemplateFilesFromPackage.mjs +10 -9
  65. package/dist/init/index.d.mts +2 -3
  66. package/dist/init/index.mjs +118 -131
  67. package/dist/init/installDependencies.d.mts +4 -1
  68. package/dist/init/installDependencies.mjs +6 -4
  69. package/dist/init/logUpdateDependenciesError.d.mts +13 -0
  70. package/dist/init/logUpdateDependenciesError.mjs +51 -0
  71. package/dist/init/updateDependenciesWithoutInstalling.d.mts +3 -2
  72. package/dist/init/updateDependenciesWithoutInstalling.mjs +50 -15
  73. package/dist/init/updateNPMScripts.d.mts +3 -1
  74. package/dist/init/updateNPMScripts.mjs +10 -7
  75. package/dist/init/updateTypeScriptConfig.d.mts +4 -1
  76. package/dist/init/updateTypeScriptConfig.mjs +13 -9
  77. package/dist/initProgram.d.mts +2 -0
  78. package/dist/initProgram.mjs +22 -0
  79. package/dist/locateSegments.d.mts +8 -1
  80. package/dist/locateSegments.mjs +14 -6
  81. package/dist/new/addClassToSegmentCode.d.mts +1 -2
  82. package/dist/new/addClassToSegmentCode.mjs +9 -5
  83. package/dist/new/addCommonTerms.mjs +1 -0
  84. package/dist/new/index.d.mts +2 -2
  85. package/dist/new/index.mjs +14 -3
  86. package/dist/new/newModule.d.mts +7 -2
  87. package/dist/new/newModule.mjs +61 -35
  88. package/dist/new/newSegment.d.mts +4 -2
  89. package/dist/new/newSegment.mjs +20 -12
  90. package/dist/new/render.d.mts +9 -9
  91. package/dist/new/render.mjs +38 -13
  92. package/dist/types.d.mts +63 -28
  93. package/dist/utils/debounceWithArgs.d.mts +2 -2
  94. package/dist/utils/debounceWithArgs.mjs +24 -9
  95. package/dist/utils/formatLoggedSegmentName.d.mts +2 -1
  96. package/dist/utils/formatLoggedSegmentName.mjs +3 -3
  97. package/dist/utils/getAvailablePort.mjs +3 -2
  98. package/dist/utils/getFileSystemEntryType.mjs +1 -1
  99. package/dist/utils/getPublicModuleNameFromPath.d.mts +4 -0
  100. package/dist/utils/getPublicModuleNameFromPath.mjs +9 -0
  101. package/dist/utils/pickSegmentFullSchema.d.mts +3 -0
  102. package/dist/utils/pickSegmentFullSchema.mjs +15 -0
  103. package/dist/utils/removeUnlistedDirectories.d.mts +10 -0
  104. package/dist/utils/removeUnlistedDirectories.mjs +61 -0
  105. package/dist/utils/resolveAbsoluteModulePath.d.mts +2 -0
  106. package/dist/utils/resolveAbsoluteModulePath.mjs +32 -0
  107. package/module-templates/Controller.ts.ejs +50 -0
  108. package/module-templates/Service.ts.ejs +28 -0
  109. package/package.json +38 -20
  110. package/dist/generateClient.d.mts +0 -7
  111. package/dist/generateClient.mjs +0 -97
  112. package/dist/getProjectInfo/directoryExists.d.mts +0 -1
  113. package/dist/getProjectInfo/directoryExists.mjs +0 -10
  114. package/dist/getProjectInfo/getConfig.d.mts +0 -11
  115. package/dist/getProjectInfo/getConfig.mjs +0 -29
  116. package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +0 -4
  117. package/dist/getProjectInfo/getRelativeSrcRoot.mjs +0 -12
  118. package/dist/postinstall.d.mts +0 -1
  119. package/dist/postinstall.mjs +0 -22
  120. package/dist/watcher/diffSchema.d.mts +0 -43
  121. package/dist/watcher/index.d.mts +0 -6
  122. package/dist/watcher/index.mjs +0 -290
  123. package/dist/watcher/isMetadataEmpty.d.mts +0 -2
  124. package/dist/watcher/isMetadataEmpty.mjs +0 -4
  125. package/dist/watcher/logDiffResult.d.mts +0 -3
  126. package/dist/watcher/logDiffResult.mjs +0 -90
  127. package/dist/watcher/writeOneSchemaFile.d.mts +0 -11
  128. package/dist/watcher/writeOneSchemaFile.mjs +0 -27
  129. package/templates/controller.ejs +0 -50
  130. package/templates/service.ejs +0 -7
  131. package/templates/worker.ejs +0 -1
  132. package/templates_old/MyThingController.c.only.template.ts +0 -32
  133. package/templates_old/MyThingController.c.template.ts +0 -34
  134. package/templates_old/MyThingService.s.template.ts +0 -18
  135. package/templates_old/controller.ejs +0 -85
  136. package/templates_old/service.ejs +0 -9
  137. package/templates_old/worker.ejs +0 -9
  138. package/templates_old/zod/MyThingController.c.only.template.ts +0 -32
  139. package/templates_old/zod/MyThingController.c.template.ts +0 -39
  140. package/templates_old/zod/MyThingService.s.template.ts +0 -18
  141. /package/dist/getProjectInfo/{importUncachedModule.d.mts → getConfig/importUncachedModule.d.mts} +0 -0
  142. /package/dist/getProjectInfo/{importUncachedModuleWorker.d.mts → getConfig/importUncachedModuleWorker.d.mts} +0 -0
@@ -0,0 +1,83 @@
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
+ export default async function getConfig({ configPath, cwd }) {
8
+ const { configAbsolutePaths, error, userConfig } = await getUserConfig({
9
+ configPath,
10
+ cwd,
11
+ });
12
+ const conf = userConfig ?? {};
13
+ const env = process.env;
14
+ const clientTemplateDefs = getTemplateDefs(conf.clientTemplateDefs);
15
+ const srcRoot = await getRelativeSrcRoot({ cwd });
16
+ const validateOnClientImport = conf.imports?.validateOnClient ?? null;
17
+ const fetcherImport = conf.imports?.fetcher ?? 'vovk';
18
+ const createRPCImport = conf.imports?.createRPC ?? 'vovk';
19
+ const imports = {
20
+ fetcher: typeof fetcherImport === 'string' ? [fetcherImport] : fetcherImport,
21
+ validateOnClient: typeof validateOnClientImport === 'string'
22
+ ? [validateOnClientImport]
23
+ : (validateOnClientImport ?? null),
24
+ createRPC: typeof createRPCImport === 'string' ? [createRPCImport] : createRPCImport,
25
+ };
26
+ const config = {
27
+ $schema: VovkSchemaIdEnum.CONFIG,
28
+ clientTemplateDefs,
29
+ imports,
30
+ emitConfig: [],
31
+ composedClient: {
32
+ ...conf.composedClient,
33
+ enabled: conf.composedClient?.enabled ?? true,
34
+ fromTemplates: conf.composedClient?.fromTemplates ?? ['mjs', 'cjs'],
35
+ outDir: conf.composedClient?.outDir ?? './node_modules/.vovk-client',
36
+ },
37
+ segmentedClient: {
38
+ ...conf.segmentedClient,
39
+ enabled: conf.segmentedClient?.enabled ?? false,
40
+ fromTemplates: conf.segmentedClient?.fromTemplates ?? ['ts'],
41
+ outDir: conf.segmentedClient?.outDir ?? path.join(srcRoot ?? '.', 'client'),
42
+ },
43
+ bundle: {
44
+ ...conf.bundle,
45
+ outDir: conf.bundle?.outDir ?? 'dist',
46
+ tsClientOutDir: conf.bundle?.tsClientOutDir ?? 'tmp_ts_rpc',
47
+ dontDeleteTsClientOutDirAfter: conf.bundle?.dontDeleteTsClientOutDirAfter ?? false,
48
+ sourcemap: conf.bundle?.sourcemap ?? false,
49
+ requires: {
50
+ [BuiltInTemplateName.readme]: '.',
51
+ [BuiltInTemplateName.packageJson]: '.',
52
+ },
53
+ },
54
+ modulesDir: conf.modulesDir ?? path.join(srcRoot ?? '.', 'modules'),
55
+ schemaOutDir: env.VOVK_SCHEMA_OUT_DIR ?? conf.schemaOutDir ?? './.vovk-schema',
56
+ origin: (env.VOVK_ORIGIN ?? conf.origin ?? '').replace(/\/$/, ''), // Remove trailing slash
57
+ rootEntry: env.VOVK_ROOT_ENTRY ?? conf.rootEntry ?? 'api',
58
+ rootSegmentModulesDirName: conf.rootSegmentModulesDirName ?? '',
59
+ logLevel: env.VOVK_LOG_LEVEL ?? conf.logLevel ?? 'info',
60
+ prettifyClient: (env.VOVK_PRETTIFY_CLIENT ? !!env.VOVK_PRETTIFY_CLIENT : null) ?? conf.prettifyClient ?? false,
61
+ devHttps: (env.VOVK_DEV_HTTPS ? !!env.VOVK_DEV_HTTPS : null) ?? conf.devHttps ?? false,
62
+ moduleTemplates: {
63
+ service: 'vovk-cli/module-templates/Service.ts.ejs',
64
+ controller: 'vovk-cli/module-templates/Controller.ts.ejs',
65
+ ...conf.moduleTemplates,
66
+ },
67
+ libs: conf.libs ?? {},
68
+ segmentConfig: conf.segmentConfig ?? {},
69
+ };
70
+ if (typeof conf.emitConfig === 'undefined') {
71
+ config.emitConfig = ['$schema', 'libs'];
72
+ }
73
+ else if (conf.emitConfig === true) {
74
+ config.emitConfig = Object.keys(config);
75
+ }
76
+ else if (Array.isArray(conf.emitConfig)) {
77
+ config.emitConfig = conf.emitConfig;
78
+ } // else it's false and emitConfig already is []
79
+ const log = getLogger(config.logLevel);
80
+ if (error)
81
+ log.warn(`Unable to load config. Using default values.`);
82
+ return { config, srcRoot, configAbsolutePaths, userConfig, log };
83
+ }
@@ -1,18 +1,16 @@
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
+ apiRoot: string;
10
11
  apiDir: string;
11
- srcRoot: string;
12
- schemaOutImportPath: string;
13
- fetcherClientImportPath: string;
14
- validateOnClientImportPath: string | null;
15
- config: Required<import("../types.mjs").VovkConfig>;
12
+ srcRoot: string | null;
13
+ config: import("vovk").VovkStrictConfig;
16
14
  log: {
17
15
  info: (msg: string) => void;
18
16
  warn: (msg: string) => void;
@@ -1,36 +1,27 @@
1
- import path from '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(), } = {}) {
1
+ import path from 'node:path';
2
+ import getConfig from './getConfig/index.mjs';
3
+ export default async function getProjectInfo({ port: givenPort, cwd = process.cwd(), configPath, srcRootRequired = true, } = {}) {
5
4
  const port = givenPort?.toString() ?? process.env.PORT ?? '3000';
6
5
  // Make PORT available to the config file at getConfig
7
6
  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);
7
+ const { config, srcRoot, configAbsolutePaths, log } = await getConfig({
8
+ configPath,
9
+ cwd,
10
+ });
11
+ if (srcRootRequired && !srcRoot) {
12
+ throw new Error(`Could not find app router directory at ${cwd}. Check Next.js docs for more info.`);
13
+ }
14
+ const apiRoot = `${config.origin ?? ''}/${config.rootEntry}`;
15
+ const apiDir = path.join(srcRoot ?? '.', 'app', config.rootEntry);
19
16
  if (configAbsolutePaths.length > 1) {
20
17
  log.warn(`Multiple config files found. Using the first one: ${configAbsolutePaths[0]}`);
21
18
  }
22
- if (!userConfig && configAbsolutePaths.length > 0) {
23
- log.error(`Error reading config file at ${configAbsolutePaths[0]}: ${error?.message ?? 'Unknown Error'}`);
24
- }
25
19
  return {
26
20
  cwd,
27
21
  port,
28
- apiEntryPoint,
22
+ apiRoot,
29
23
  apiDir,
30
24
  srcRoot,
31
- schemaOutImportPath,
32
- fetcherClientImportPath,
33
- validateOnClientImportPath,
34
25
  config,
35
26
  log,
36
27
  };
package/dist/index.d.mts CHANGED
@@ -1,26 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import type { LogLevelNames } from 'loglevel';
4
- import type { VovkConfig, VovkEnv } from './types.mjs';
5
2
  import 'dotenv/config';
6
- export type { VovkConfig, VovkEnv };
7
- export interface InitOptions {
8
- yes?: boolean;
9
- logLevel: LogLevelNames;
10
- useNpm?: boolean;
11
- useYarn?: boolean;
12
- usePnpm?: boolean;
13
- useBun?: boolean;
14
- skipInstall?: boolean;
15
- updateTsConfig?: boolean;
16
- updateScripts?: 'implicit' | 'explicit';
17
- validationLibrary?: string | null;
18
- validateOnClient?: boolean;
19
- dryRun?: boolean;
20
- channel?: 'latest' | 'beta' | 'dev';
21
- }
22
- export interface NewOptions {
23
- dryRun: boolean;
24
- }
25
- declare const program: Command;
26
- export declare function initProgram(p: typeof program, command: string): Command;
3
+ import type { VovkEnv } from './types.mjs';
4
+ export type { VovkEnv };
package/dist/index.mjs CHANGED
@@ -1,53 +1,64 @@
1
1
  #!/usr/bin/env node
2
- import path from 'path';
2
+ import path from 'node:path';
3
+ import { readFileSync } from 'node:fs';
4
+ import 'dotenv/config';
3
5
  import { Command } from 'commander';
4
- import { readFileSync } from 'fs';
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';
10
- import { VovkCLIWatcher } from './watcher/index.mjs';
11
- import { Init } from './init/index.mjs';
12
- import newComponents from './new/index.mjs';
13
- import 'dotenv/config';
9
+ import { VovkGenerate } from './generate/index.mjs';
10
+ import { bundle } from './bundle/index.mjs';
11
+ import { VovkDev } from './dev/index.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
16
  const packageJSON = JSON.parse(readFileSync(path.join(import.meta.dirname, '../package.json'), 'utf-8'));
16
17
  program.name('vovk').description('Vovk CLI').version(packageJSON.version);
18
+ initProgram(program.command('init'));
17
19
  program
18
20
  .command('dev')
19
- .description('Start schema watcher (optional flag --next-dev to start it with Next.js)')
20
- .option('--client-out <path>', 'Path to client output directory')
21
- .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
+ .action(async (nextArgs, options) => {
28
+ const { nextDev, exit = false, schemaOut } = options;
24
29
  const portAttempts = 30;
25
- const PORT = !options.nextDev
30
+ const PORT = !nextDev
26
31
  ? process.env.PORT
27
32
  : process.env.PORT ||
28
33
  (await getAvailablePort(3000, portAttempts, 0, (failedPort, tryingPort) =>
29
34
  // 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`);
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`);
32
37
  }));
33
38
  if (!PORT) {
34
39
  throw new Error('🐺 ❌ PORT env variable is required');
35
40
  }
36
- if (options.nextDev) {
41
+ if (nextDev) {
37
42
  const { result } = concurrently([
38
43
  {
39
- command: `node ${import.meta.dirname}/watcher/index.mjs`,
40
- name: 'Vovk.ts Schema Watcher',
41
- env: Object.assign({ PORT, __VOVK_START_WATCHER_IN_STANDALONE_MODE__: 'true' }, options.clientOut ? { VOVK_CLIENT_OUT_DIR: options.clientOut } : {}),
42
- },
43
- {
44
- command: `npx next dev ${command.args.join(' ')}`,
44
+ command: `npx next dev ${nextArgs.join(' ')}`,
45
45
  name: 'Next.js Development Server',
46
46
  env: { PORT },
47
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
+ },
48
58
  ], {
49
59
  killOthers: ['failure', 'success'],
50
60
  prefix: 'none',
61
+ successCondition: 'first',
51
62
  });
52
63
  try {
53
64
  await result;
@@ -57,61 +68,75 @@ program
57
68
  }
58
69
  }
59
70
  else {
60
- void new VovkCLIWatcher().start({ clientOutDir: options.clientOut });
71
+ void new VovkDev({ schemaOut }).start({ exit });
61
72
  }
62
73
  });
63
74
  program
64
75
  .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);
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('--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')
92
+ .option('--openapi, --openapi-spec <openapi_path_or_url>', 'use OpenAPI schema instead of Vovk schema')
93
+ .action(async (cliGenerateOptions) => {
94
+ const projectInfo = await getProjectInfo({ configPath: cliGenerateOptions.configPath, srcRootRequired: false });
95
+ await new VovkGenerate({
96
+ projectInfo,
97
+ forceNothingWrittenLog: true,
98
+ cliGenerateOptions,
99
+ }).start();
100
+ });
101
+ program
102
+ .command('bundle')
103
+ .alias('b')
104
+ .description('generate TypeScrtipt RPC and bundle it')
105
+ .option('--out, --out-dir <path>', 'path to output directory for bundle')
106
+ .option('--include, --include-segments <segments...>', 'include segments')
107
+ .option('--exclude, --exclude-segments <segments...>', 'exclude segments')
108
+ .option('--ts-client-out-dir <path>', 'path to output directory for TypeScript client')
109
+ .option('--dont-delete-ts-client-out-dir-after', 'do not delete TypeScript client output directory after bundling')
110
+ .option('--config <config>', 'path to config file')
111
+ .option('--schema <path>', 'path to schema folder (default: .vovk-schema)')
112
+ .option('--sourcemap', 'generate sourcemaps')
113
+ .option('--openapi, --openapi-spec <openapi_path_or_url>', 'use OpenAPI schema instead of Vovk schema')
114
+ .action(async (cliBundleOptions) => {
115
+ const projectInfo = await getProjectInfo({ configPath: cliBundleOptions.config, srcRootRequired: false });
116
+ const { cwd, config, log } = projectInfo;
117
+ const fullSchema = await getProjectFullSchema(path.resolve(cwd, cliBundleOptions?.schema ?? config.schemaOutDir), log);
118
+ await bundle({
119
+ projectInfo,
120
+ fullSchema,
121
+ cliBundleOptions,
122
+ });
74
123
  });
75
- // reused at vovk-init
76
- export function initProgram(p, command) {
77
- return p
78
- .command(command + '[prefix]')
79
- .description('Initialize Vovk project')
80
- .option('-Y, --yes', 'Skip all prompts and use default values')
81
- .option('--log-level <level>', 'Set log level', 'info')
82
- .option('--use-npm', 'Use npm as package manager')
83
- .option('--use-yarn', 'Use yarn as package manager')
84
- .option('--use-pnpm', 'Use pnpm as package manager')
85
- .option('--use-bun', 'Use bun as package manager')
86
- .option('--skip-install', 'Skip installing dependencies')
87
- .option('--update-ts-config', 'Update tsconfig.json')
88
- .option('--update-scripts <mode>', 'Update package.json scripts (implicit or explicit)')
89
- .option('--validation-library <library>', 'Validation library to use ("vovk-zod", "vovk-yup", "vovk-dto" or another). Set to "none" to skip validation')
90
- .option('--validate-on-client', 'Validate on client')
91
- .option('--dry-run', 'Do not write files to disk')
92
- .option('--channel <channel>', 'Channel to use for fetching packages', 'latest')
93
- .action((prefix = '.', options) => new Init().main(prefix, options));
94
- }
95
- initProgram(program, 'init ');
96
124
  program
97
125
  .command('new [components...]')
98
126
  .alias('n')
99
- .description('Create new components. "vovk new [...components] [segmentName/]moduleName" to create a new module or "vovk new segment [segmentName]" to create a new segment')
100
- .option('--dry-run', 'Do not write files to disk')
101
- .action((components, options) => newComponents(components, options));
127
+ .description('create new components. "vovk new [...components] [segmentName/]moduleName" to create a new module or "vovk new segment [segmentName]" to create a new segment')
128
+ .option('-o, --overwrite', 'overwrite existing files')
129
+ .option('--template, --templates <templates...>', 'override config template; accepts an array of strings that correspond the order of the components')
130
+ .option('--dir <dirname>', 'override dirName in template file; relative to the root of the project')
131
+ .option('--empty', 'create an empty module')
132
+ .option('--no-segment-update', 'do not update segment files when creating a new module')
133
+ .option('--dry-run', 'do not write files to disk')
134
+ .option('--static', 'if the segment is static')
135
+ .action((components, newOptions) => newComponents(components, newOptions));
102
136
  program
103
137
  .command('help')
104
138
  .description('Show help message')
105
139
  .action(() => program.help());
106
- /*
107
- TODO
108
- vovk new segment [segmentName]
109
- vovk new controller service [segmentName/]moduleName
110
- vovk new c s w [segmentName/]moduleName
111
-
112
- vovk c s w userApi/user
113
- vovk new c s w user
114
- */
115
140
  program.parse(process.argv);
116
141
  if (!process.argv.slice(2).length) {
117
142
  program.outputHelp();
@@ -1,5 +1,5 @@
1
- import path from 'path';
2
- import fs from 'fs/promises';
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
3
  import * as jsonc from 'jsonc-parser';
4
4
  export default async function checkTSConfigForExperimentalDecorators(root) {
5
5
  const tsconfigPath = path.resolve(root, 'tsconfig.json');
@@ -1,10 +1,9 @@
1
1
  import type getLogger from '../utils/getLogger.mjs';
2
- import type { InitOptions } from '../index.mjs';
3
- export default function createConfig({ root, log, dryRun, options: { validationLibrary, validateOnClient }, }: {
2
+ import type { InitOptions } from '../types.mjs';
3
+ export default function createConfig({ root, log, options: { validationLibrary, reactQuery, lang, channel, dryRun }, }: {
4
4
  root: string;
5
5
  log: ReturnType<typeof getLogger>;
6
- dryRun?: boolean;
7
- options: Pick<InitOptions, 'validationLibrary' | 'validateOnClient'>;
6
+ options: Pick<InitOptions, 'validationLibrary' | 'reactQuery' | 'lang' | 'channel' | 'dryRun'>;
8
7
  }): Promise<{
9
8
  configAbsolutePath: string;
10
9
  }>;
@@ -1,9 +1,9 @@
1
- import path from 'path';
2
- import fs from 'fs/promises';
3
- import getFileSystemEntryType, { FileSystemEntryType } from '../utils/getFileSystemEntryType.mjs';
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
4
3
  import getTemplateFilesFromPackage from './getTemplateFilesFromPackage.mjs';
5
4
  import prettify from '../utils/prettify.mjs';
6
- export default async function createConfig({ root, log, dryRun, options: { validationLibrary, validateOnClient }, }) {
5
+ import getFileSystemEntryType, { FileSystemEntryType } from '../utils/getFileSystemEntryType.mjs';
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, dryRun, options: { valid
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
  };
19
18
  if (validationLibrary) {
20
- config.validationLibrary = validationLibrary;
21
- if (validateOnClient) {
22
- config.validateOnClient = `${validationLibrary}/validateOnClient`;
23
- }
19
+ config.imports ??= {};
20
+ config.imports.validateOnClient = 'vovk-ajv';
24
21
  try {
25
- const validationTemplates = await getTemplateFilesFromPackage(validationLibrary);
26
- Object.assign(templates, validationTemplates);
22
+ const validationTemplates = await getTemplateFilesFromPackage(validationLibrary, channel);
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)
@@ -1,6 +1,7 @@
1
+ import { InitOptions } from '../types.mjs';
1
2
  /**
2
3
  * Retrieves a list of files in the 'templates' folder of an NPM package.
3
4
  * @param packageName - The name of the NPM package.
4
5
  * @returns A promise that resolves to an array of file paths.
5
6
  */
6
- export default function getTemplatesFiles(packageName: string, channel?: string): Promise<Record<string, string>>;
7
+ export default function getTemplateFilesFromPackage(packageName: string, channel?: InitOptions['channel']): Promise<Record<string, string>>;
@@ -1,14 +1,13 @@
1
- import { createGunzip } from 'zlib';
1
+ import { Readable } from 'node:stream';
2
+ import { createGunzip } from 'node:zlib';
2
3
  import tar from 'tar-stream';
3
- import { Readable } from 'stream';
4
4
  import getNPMPackageMetadata from '../utils/getNPMPackageMetadata.mjs';
5
5
  /**
6
6
  * Retrieves a list of files in the 'templates' folder of an NPM package.
7
7
  * @param packageName - The name of the NPM package.
8
8
  * @returns A promise that resolves to an array of file paths.
9
9
  */
10
- export default async function getTemplatesFiles(packageName, channel = 'beta' // TODO change to latest
11
- ) {
10
+ export default async function getTemplateFilesFromPackage(packageName, channel = 'latest') {
12
11
  const metadata = await getNPMPackageMetadata(packageName);
13
12
  const latestVersion = metadata['dist-tags'][channel];
14
13
  const tarballUrl = metadata.versions[latestVersion].dist.tarball;
@@ -22,8 +21,11 @@ export default async function getTemplatesFiles(packageName, channel = 'beta' //
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.substring('module-templates/'.length).replace(/\.ts\.ejs$/, ''),
27
+ `${packageName}/${fileName}`,
28
+ ]);
27
29
  return Object.fromEntries(entries);
28
30
  }
29
31
  /**
@@ -37,9 +39,8 @@ function extractTemplatesFromTarball(tarballBuffer) {
37
39
  const files = [];
38
40
  extract.on('entry', (header, stream, next) => {
39
41
  const filePath = header.name;
40
- // TODO revisit comments
41
- // Check if the file is in the 'templates' folder
42
- if (filePath.startsWith('package/templates/')) {
42
+ // Check if the file is in the 'module-templates' folder
43
+ if (filePath.startsWith('package/module-templates/')) {
43
44
  files.push(filePath.replace('package/', ''));
44
45
  }
45
46
  stream.on('end', () => next());
@@ -1,9 +1,8 @@
1
- #!/usr/bin/env node
2
- import type { InitOptions } from '../index.mjs';
3
1
  import getLogger from '../utils/getLogger.mjs';
2
+ import type { InitOptions } from '../types.mjs';
4
3
  export declare class Init {
5
4
  #private;
6
5
  root: string;
7
6
  log: ReturnType<typeof getLogger>;
8
- 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>;
9
8
  }