vovk-cli 0.0.1-draft.13 → 0.0.1-draft.131

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 (92) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +29 -1
  3. package/client-templates/fullSchema/fullSchema.cjs.ejs +13 -0
  4. package/client-templates/fullSchema/fullSchema.d.cts.ejs +11 -0
  5. package/client-templates/main/main.cjs.ejs +15 -0
  6. package/client-templates/main/main.d.cts.ejs +15 -0
  7. package/client-templates/module/module.d.mts.ejs +15 -0
  8. package/client-templates/module/module.mjs.ejs +21 -0
  9. package/client-templates/ts/index.ts.ejs +24 -0
  10. package/dist/dev/diffSegmentSchema.d.mts +36 -0
  11. package/dist/{watcher/diffSchema.mjs → dev/diffSegmentSchema.mjs} +4 -12
  12. package/dist/{watcher → dev}/ensureSchemaFiles.d.mts +3 -0
  13. package/dist/{watcher → dev}/ensureSchemaFiles.mjs +12 -30
  14. package/dist/dev/index.d.mts +6 -0
  15. package/dist/{watcher → dev}/index.mjs +157 -89
  16. package/dist/dev/isSegmentSchemaEmpty.d.mts +2 -0
  17. package/dist/dev/isSegmentSchemaEmpty.mjs +4 -0
  18. package/dist/{watcher → dev}/logDiffResult.d.mts +1 -1
  19. package/dist/dev/logDiffResult.mjs +57 -0
  20. package/dist/dev/writeConfigJson.d.mts +2 -0
  21. package/dist/dev/writeConfigJson.mjs +15 -0
  22. package/dist/dev/writeOneSegmentSchemaFile.d.mts +12 -0
  23. package/dist/{watcher/writeOneSchemaFile.mjs → dev/writeOneSegmentSchemaFile.mjs} +10 -6
  24. package/dist/generate/ensureClient.d.mts +4 -0
  25. package/dist/generate/ensureClient.mjs +66 -0
  26. package/dist/generate/getClientTemplates.d.mts +23 -0
  27. package/dist/generate/getClientTemplates.mjs +87 -0
  28. package/dist/generate/getFullSchemaFromJSON.d.mts +3 -0
  29. package/dist/generate/getFullSchemaFromJSON.mjs +53 -0
  30. package/dist/generate/index.d.mts +13 -0
  31. package/dist/generate/index.mjs +182 -0
  32. package/dist/getProjectInfo/getConfig.d.mts +5 -4
  33. package/dist/getProjectInfo/getConfig.mjs +28 -7
  34. package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +2 -1
  35. package/dist/getProjectInfo/getConfigAbsolutePaths.mjs +4 -1
  36. package/dist/getProjectInfo/getUserConfig.d.mts +3 -2
  37. package/dist/getProjectInfo/getUserConfig.mjs +5 -3
  38. package/dist/getProjectInfo/importUncachedModule.mjs +0 -1
  39. package/dist/getProjectInfo/importUncachedModuleWorker.mjs +0 -1
  40. package/dist/getProjectInfo/index.d.mts +14 -6
  41. package/dist/getProjectInfo/index.mjs +47 -14
  42. package/dist/index.d.mts +0 -5
  43. package/dist/index.mjs +55 -65
  44. package/dist/init/createConfig.d.mts +2 -3
  45. package/dist/init/createConfig.mjs +11 -7
  46. package/dist/init/getTemplateFilesFromPackage.d.mts +2 -1
  47. package/dist/init/getTemplateFilesFromPackage.mjs +2 -3
  48. package/dist/init/index.d.mts +1 -1
  49. package/dist/init/index.mjs +29 -28
  50. package/dist/init/updateDependenciesWithoutInstalling.mjs +3 -5
  51. package/dist/init/updateNPMScripts.d.mts +3 -1
  52. package/dist/init/updateNPMScripts.mjs +10 -7
  53. package/dist/initProgram.d.mts +2 -0
  54. package/dist/initProgram.mjs +21 -0
  55. package/dist/locateSegments.d.mts +7 -1
  56. package/dist/locateSegments.mjs +6 -4
  57. package/dist/new/addClassToSegmentCode.d.mts +1 -2
  58. package/dist/new/addClassToSegmentCode.mjs +3 -3
  59. package/dist/new/addCommonTerms.mjs +1 -0
  60. package/dist/new/index.d.mts +1 -1
  61. package/dist/new/index.mjs +2 -1
  62. package/dist/new/newModule.d.mts +2 -1
  63. package/dist/new/newModule.mjs +14 -16
  64. package/dist/new/newSegment.mjs +6 -4
  65. package/dist/new/render.d.mts +6 -3
  66. package/dist/new/render.mjs +25 -13
  67. package/dist/types.d.mts +9 -42
  68. package/dist/utils/debounceWithArgs.d.mts +2 -2
  69. package/dist/utils/debounceWithArgs.mjs +24 -9
  70. package/dist/utils/getAvailablePort.mjs +1 -1
  71. package/dist/utils/pickSegmentFullSchema.d.mts +2 -0
  72. package/dist/utils/pickSegmentFullSchema.mjs +8 -0
  73. package/dist/utils/removeUnlistedDirectories.d.mts +10 -0
  74. package/dist/utils/removeUnlistedDirectories.mjs +54 -0
  75. package/dist/utils/resolveAbsoluteModulePath.d.mts +1 -0
  76. package/dist/utils/resolveAbsoluteModulePath.mjs +6 -0
  77. package/package.json +25 -21
  78. package/templates/controller.ejs +20 -21
  79. package/templates/service.ejs +12 -12
  80. package/dist/generateClient.d.mts +0 -7
  81. package/dist/generateClient.mjs +0 -97
  82. package/dist/postinstall.d.mts +0 -1
  83. package/dist/postinstall.mjs +0 -24
  84. package/dist/watcher/diffSchema.d.mts +0 -43
  85. package/dist/watcher/ensureClient.d.mts +0 -5
  86. package/dist/watcher/ensureClient.mjs +0 -31
  87. package/dist/watcher/index.d.mts +0 -6
  88. package/dist/watcher/isMetadataEmpty.d.mts +0 -2
  89. package/dist/watcher/isMetadataEmpty.mjs +0 -4
  90. package/dist/watcher/logDiffResult.mjs +0 -94
  91. package/dist/watcher/writeOneSchemaFile.d.mts +0 -11
  92. package/templates/worker.ejs +0 -24
package/dist/index.mjs CHANGED
@@ -1,54 +1,61 @@
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';
10
- import { VovkCLIWatcher } from './watcher/index.mjs';
11
- import { Init } from './init/index.mjs';
9
+ import generate from './generate/index.mjs';
10
+ import { VovkDev } from './dev/index.mjs';
12
11
  import newComponents from './new/index.mjs';
13
- import 'dotenv/config';
12
+ import initProgram from './initProgram.mjs';
13
+ import { getFullSchemaFromJSON } from './generate/getFullSchemaFromJSON.mjs';
14
14
  const program = new Command();
15
15
  const packageJSON = JSON.parse(readFileSync(path.join(import.meta.dirname, '../package.json'), 'utf-8'));
16
16
  program.name('vovk').description('Vovk CLI').version(packageJSON.version);
17
+ initProgram(program.command('init'));
17
18
  program
18
19
  .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) => {
20
+ .alias('d')
21
+ .description('start schema watcher (optional flag --next-dev to start it with Next.js)')
22
+ .argument('[nextArgs...]', 'extra arguments for the dev command')
23
+ .option('--next-dev', 'start schema watcher and Next.js with automatic port allocation')
24
+ .option('--exit', 'kill the processe when schema and client is generated')
25
+ .action(async (nextArgs, options) => {
26
+ const { nextDev, exit = false } = options;
24
27
  const portAttempts = 30;
25
- const PORT = !options.nextDev
28
+ const PORT = !nextDev
26
29
  ? process.env.PORT
27
30
  : process.env.PORT ||
28
31
  (await getAvailablePort(3000, portAttempts, 0, (failedPort, tryingPort) =>
29
32
  // 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`);
33
+ console.warn(`🐺 Port ${failedPort} is in use, trying ${tryingPort} instead.`)).catch(() => {
34
+ throw new Error(`🐺 ❌ Failed to find an available port after ${portAttempts} attempts`);
32
35
  }));
33
36
  if (!PORT) {
34
37
  throw new Error('🐺 ❌ PORT env variable is required');
35
38
  }
36
- console.log(`TODO:`, command.args);
37
- if (options.nextDev) {
39
+ if (nextDev) {
38
40
  const { result } = concurrently([
39
41
  {
40
- command: `node ${import.meta.dirname}/watcher/index.mjs`,
41
- name: 'Vovk.ts Schema Watcher',
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(' ')}`,
42
+ command: `npx next dev ${nextArgs.join(' ')}`,
46
43
  name: 'Next.js Development Server',
47
44
  env: { PORT },
48
45
  },
46
+ {
47
+ command: `node ${import.meta.dirname}/dev/index.mjs`,
48
+ name: 'Vovk Dev Watcher',
49
+ env: {
50
+ PORT,
51
+ __VOVK_START_WATCHER_IN_STANDALONE_MODE__: 'true',
52
+ __VOVK_EXIT__: exit ? 'true' : 'false',
53
+ },
54
+ },
49
55
  ], {
50
56
  killOthers: ['failure', 'success'],
51
57
  prefix: 'none',
58
+ successCondition: 'first',
52
59
  });
53
60
  try {
54
61
  await result;
@@ -58,65 +65,48 @@ program
58
65
  }
59
66
  }
60
67
  else {
61
- void new VovkCLIWatcher().start({ clientOutDir: options.clientOut });
68
+ void new VovkDev().start({ exit });
62
69
  }
63
70
  });
64
71
  program
65
72
  .command('generate')
66
- .description('Generate client')
67
- .option('--client-out <path>', 'Path to output directory')
73
+ .alias('g')
74
+ .description('Generate RPC client from schema')
75
+ .option('--out, --client-out-dir <path>', 'path to output directory')
76
+ .option('--template, --templates <templates...>', 'client code templates ("ts", "compiled", "python", "none", a custom path)')
77
+ .option('--emit-full-schema, --full-schema-json [fileName]', 'generate client with full schema')
78
+ .option('--prettify', 'prettify output files')
79
+ .option('--config <config>', 'path to config file')
68
80
  .action(async (options) => {
69
- const projectInfo = await getProjectInfo({ clientOutDir: options.clientOut });
70
- const { cwd, config, apiDir } = projectInfo;
71
- const segments = await locateSegments(apiDir);
81
+ const { clientOutDir, templates, prettify, emitFullSchema, config: configPath } = options;
82
+ const projectInfo = await getProjectInfo({ clientOutDir, configPath });
83
+ const { cwd, config } = projectInfo;
72
84
  const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
73
- const schema = (await import(path.join(schemaOutAbsolutePath, 'index.js')));
74
- await generateClient(projectInfo, segments, schema.default);
85
+ const fullSchema = await getFullSchemaFromJSON(schemaOutAbsolutePath, projectInfo);
86
+ await generate({
87
+ projectInfo,
88
+ fullSchema,
89
+ templates,
90
+ prettify,
91
+ forceNothingWrittenLog: true,
92
+ emitFullSchema,
93
+ });
75
94
  });
76
- // reused at vovk-init
77
- export function initProgram(p, command) {
78
- return p
79
- .command(command + '[prefix]')
80
- .description('Initialize Vovk project')
81
- .option('-y, --yes', 'Skip all prompts and use default values')
82
- .option('--log-level <level>', 'Set log level', 'info')
83
- .option('--use-npm', 'Use npm as package manager')
84
- .option('--use-yarn', 'Use yarn as package manager')
85
- .option('--use-pnpm', 'Use pnpm as package manager')
86
- .option('--use-bun', 'Use bun as package manager')
87
- .option('--skip-install', 'Skip installing dependencies')
88
- .option('--update-ts-config', 'Update tsconfig.json')
89
- .option('--update-scripts <mode>', 'Update package.json scripts (implicit or explicit)')
90
- .option('--validation-library <library>', 'Validation library to use ("vovk-zod", "vovk-yup", "vovk-dto" or another). Set to "none" to skip validation')
91
- .option('--validate-on-client', 'Validate on client')
92
- .option('--channel <channel>', 'Channel to use for fetching packages', 'latest')
93
- .option('--dry-run', 'Do not write files to disk')
94
- .action((prefix = '.', options) => new Init().main(prefix, options));
95
- }
96
- initProgram(program, 'init ');
97
95
  program
98
96
  .command('new [components...]')
99
97
  .alias('n')
100
- .description('Create new components. "vovk new [...components] [segmentName/]moduleName" to create a new module or "vovk new segment [segmentName]" to create a new segment')
101
- .option('-o, --overwrite', 'Overwrite existing files')
102
- .option('--template, --templates <templates...>', 'Override config template. Accepts an array of strings that correspond the order of the components')
103
- .option('--dir <dirname>', 'Override dirName in template file. Relative to the root of the project')
104
- .option('--no-segment-update', 'Do not update segment files when creating a new module')
105
- .option('--dry-run', 'Do not write files to disk')
98
+ .description('create new components. "vovk new [...components] [segmentName/]moduleName" to create a new module or "vovk new segment [segmentName]" to create a new segment')
99
+ .option('-o, --overwrite', 'overwrite existing files')
100
+ .option('--template, --templates <templates...>', 'override config template; accepts an array of strings that correspond the order of the components')
101
+ .option('--dir <dirname>', 'override dirName in template file; relative to the root of the project')
102
+ .option('--empty', 'create an empty module')
103
+ .option('--no-segment-update', 'do not update segment files when creating a new module')
104
+ .option('--dry-run', 'do not write files to disk')
106
105
  .action((components, options) => newComponents(components, options));
107
106
  program
108
107
  .command('help')
109
108
  .description('Show help message')
110
109
  .action(() => program.help());
111
- /*
112
- TODO
113
- vovk new segment [segmentName]
114
- vovk new controller service [segmentName/]moduleName
115
- vovk new c s w [segmentName/]moduleName
116
-
117
- vovk c s w userApi/user
118
- vovk new c s w user
119
- */
120
110
  program.parse(process.argv);
121
111
  if (!process.argv.slice(2).length) {
122
112
  program.outputHelp();
@@ -1,10 +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, dryRun, options: { validationLibrary, validateOnClient }, }: {
3
+ export default function createConfig({ root, log, options: { validationLibrary, reactQuery, 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' | 'channel' | 'dryRun'>;
8
7
  }): Promise<{
9
8
  configAbsolutePath: string;
10
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, dryRun, options: { validationLibrary, validateOnClient }, }) {
6
+ export default async function createConfig({ root, log, options: { validationLibrary, reactQuery, 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;
@@ -14,21 +14,25 @@ export default async function createConfig({ root, log, dryRun, options: { valid
14
14
  const templates = {
15
15
  controller: 'vovk-cli/templates/controller.ejs',
16
16
  service: 'vovk-cli/templates/service.ejs',
17
- worker: 'vovk-cli/templates/worker.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 =
21
+ {
22
+ 'vovk-dto': `vovk-dto/validateOnClient.js`,
23
+ }[validationLibrary] ?? 'vovk-ajv';
24
24
  try {
25
- const validationTemplates = await getTemplateFilesFromPackage(validationLibrary);
25
+ const validationTemplates = await getTemplateFilesFromPackage(validationLibrary, channel);
26
26
  Object.assign(templates, validationTemplates);
27
27
  }
28
28
  catch (error) {
29
29
  log.warn(`Failed to fetch validation library templates: ${error.message}`);
30
30
  }
31
31
  }
32
+ if (reactQuery) {
33
+ config.imports ??= {};
34
+ config.imports.createRPC = 'vovk-react-query';
35
+ }
32
36
  config.templates = templates;
33
37
  const configStr = await prettify(`/** @type {import('vovk-cli').VovkConfig} */
34
38
  const config = ${JSON.stringify(config, null, 2)};
@@ -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>>;
@@ -2,13 +2,13 @@ import { Readable } from 'node:stream';
2
2
  import { createGunzip } from 'node:zlib';
3
3
  import tar from 'tar-stream';
4
4
  import getNPMPackageMetadata from '../utils/getNPMPackageMetadata.mjs';
5
+ // Crereated with AI
5
6
  /**
6
7
  * Retrieves a list of files in the 'templates' folder of an NPM package.
7
8
  * @param packageName - The name of the NPM package.
8
9
  * @returns A promise that resolves to an array of file paths.
9
10
  */
10
- export default async function getTemplatesFiles(packageName, channel = 'beta' // TODO change to latest
11
- ) {
11
+ export default async function getTemplateFilesFromPackage(packageName, channel = 'latest') {
12
12
  const metadata = await getNPMPackageMetadata(packageName);
13
13
  const latestVersion = metadata['dist-tags'][channel];
14
14
  const tarballUrl = metadata.versions[latestVersion].dist.tarball;
@@ -37,7 +37,6 @@ function extractTemplatesFromTarball(tarballBuffer) {
37
37
  const files = [];
38
38
  extract.on('entry', (header, stream, next) => {
39
39
  const filePath = header.name;
40
- // TODO revisit comments
41
40
  // Check if the file is in the 'templates' folder
42
41
  if (filePath.startsWith('package/templates/')) {
43
42
  files.push(filePath.replace('package/', ''));
@@ -4,5 +4,5 @@ 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, dryRun, channel, }: InitOptions): Promise<void>;
8
8
  }
@@ -7,18 +7,19 @@ import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
7
7
  import installDependencies, { getPackageManager } from './installDependencies.mjs';
8
8
  import getLogger from '../utils/getLogger.mjs';
9
9
  import createConfig from './createConfig.mjs';
10
- import updateNPMScripts from './updateNPMScripts.mjs';
10
+ import updateNPMScripts, { getDevScript } from './updateNPMScripts.mjs';
11
11
  import checkTSConfigForExperimentalDecorators from './checkTSConfigForExperimentalDecorators.mjs';
12
12
  import updateTypeScriptConfig from './updateTypeScriptConfig.mjs';
13
13
  import updateDependenciesWithoutInstalling from './updateDependenciesWithoutInstalling.mjs';
14
14
  import logUpdateDependenciesError from './logUpdateDependenciesError.mjs';
15
15
  import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
16
+ import NPMCliPackageJson from '@npmcli/package-json';
16
17
  export class Init {
17
18
  root;
18
19
  log;
19
- async #init({ configPaths, }, { useNpm, useYarn, usePnpm, useBun, skipInstall, updateTsConfig, updateScripts, validationLibrary, validateOnClient, dryRun, channel, }) {
20
+ async #init({ configPaths, pkgJson, }, { useNpm, useYarn, usePnpm, useBun, skipInstall, updateTsConfig, updateScripts, validationLibrary, reactQuery, dryRun, channel, }) {
20
21
  const { log, root } = this;
21
- const dependencies = ['vovk'];
22
+ const dependencies = ['vovk', 'vovk-client', 'vovk-openapi'];
22
23
  const devDependencies = ['vovk-cli'];
23
24
  // delete older config files
24
25
  if (configPaths.length) {
@@ -26,17 +27,19 @@ export class Init {
26
27
  log.debug(`Deleted existing config file${configPaths.length > 1 ? 's' : ''} at ${configPaths.join(', ')}`);
27
28
  }
28
29
  if (validationLibrary) {
29
- dependencies.push(validationLibrary);
30
- dependencies.push(...({
30
+ dependencies.push(validationLibrary, 'vovk-ajv', ...({
31
31
  'vovk-zod': ['zod'],
32
32
  'vovk-yup': ['yup'],
33
- 'vovk-dto': ['class-validator', 'class-transformer'],
33
+ 'vovk-dto': ['class-validator', 'class-transformer', 'vovk-mapped-types', 'reflect-metadata'],
34
34
  }[validationLibrary] ?? []));
35
35
  }
36
+ if (reactQuery) {
37
+ dependencies.push('vovk-react-query', '@tanstack/react-query');
38
+ }
36
39
  if (updateScripts) {
37
40
  try {
38
41
  if (!dryRun)
39
- await updateNPMScripts(root, updateScripts);
42
+ await updateNPMScripts(pkgJson, root, updateScripts);
40
43
  log.info('Updated scripts at package.json');
41
44
  }
42
45
  catch (error) {
@@ -101,8 +104,7 @@ export class Init {
101
104
  const { configAbsolutePath } = await createConfig({
102
105
  root,
103
106
  log,
104
- options: { validationLibrary, validateOnClient },
105
- dryRun,
107
+ options: { validationLibrary, reactQuery, channel, dryRun },
106
108
  });
107
109
  log.info('Config created successfully at ' + configAbsolutePath);
108
110
  }
@@ -110,15 +112,16 @@ export class Init {
110
112
  log.error(`Failed to create config: ${error.message}`);
111
113
  }
112
114
  }
113
- async main(prefix, { yes, logLevel, useNpm, useYarn, usePnpm, useBun, skipInstall, updateTsConfig, updateScripts, validationLibrary, validateOnClient, dryRun, channel, }) {
115
+ async main(prefix, { yes, logLevel, useNpm, useYarn, usePnpm, useBun, skipInstall, updateTsConfig, updateScripts, validationLibrary, reactQuery, dryRun, channel, }) {
114
116
  const cwd = process.cwd();
115
117
  const root = path.resolve(cwd, prefix);
116
118
  const log = getLogger(logLevel);
119
+ const pkgJson = await NPMCliPackageJson.load(root);
117
120
  this.root = root;
118
121
  this.log = log;
119
122
  const configPaths = await getConfigPaths({ cwd, relativePath: prefix });
120
123
  if (yes) {
121
- return this.#init({ configPaths }, {
124
+ return this.#init({ configPaths, pkgJson }, {
122
125
  useNpm: useNpm ?? (!useYarn && !usePnpm && !useBun),
123
126
  useYarn: useYarn ?? false,
124
127
  usePnpm: usePnpm ?? false,
@@ -127,7 +130,7 @@ export class Init {
127
130
  updateTsConfig: updateTsConfig ?? true,
128
131
  updateScripts: updateScripts ?? 'implicit',
129
132
  validationLibrary: validationLibrary?.toLocaleLowerCase() === 'none' ? null : (validationLibrary ?? 'vovk-zod'),
130
- validateOnClient: validateOnClient ?? true,
133
+ reactQuery: reactQuery ?? true,
131
134
  dryRun: dryRun ?? false,
132
135
  channel: channel ?? 'latest',
133
136
  });
@@ -165,56 +168,54 @@ export class Init {
165
168
  {
166
169
  name: 'vovk-dto',
167
170
  value: 'vovk-dto',
168
- description: 'Use class-validator and class-transformer for data validation',
171
+ description: 'Use class-validator for data validation. Also installs class-transformer, vovk-mapped-types and reflect-metadata',
169
172
  },
170
173
  { name: 'None', value: null, description: 'Install validation library later' },
171
174
  ],
172
175
  })));
173
- if (validationLibrary) {
174
- validateOnClient =
175
- validateOnClient ??
176
- (await confirm({
177
- message: 'Do you want to enable client validation?',
178
- }));
179
- }
180
176
  updateScripts =
181
177
  updateScripts ??
182
178
  (await select({
183
- message: 'Do you want to update package.json by adding "generate" and "dev" scripts?',
179
+ message: 'Do you want to update "dev" NPM script at package.json?',
184
180
  default: 'implicit',
185
181
  choices: [
186
182
  {
187
183
  name: 'Yes, use "concurrently" implicitly',
188
- description: `The "dev" script will use "concurrently" API internally in order to run "next dev" and "vovk dev" together and automatically look for an available port ${chalk.whiteBright.bold(`"vovk dev --next-dev"`)}`,
189
184
  value: 'implicit',
185
+ description: `The script will use "concurrently" API to run "next dev" and "vovk dev" commands together and automatically find an available port ${chalk.whiteBright.bold(`"${getDevScript(pkgJson, 'implicit')}"`)}`,
190
186
  },
191
187
  {
192
188
  name: 'Yes, use "concurrently" explicitly',
193
189
  value: 'explicit',
194
- description: `The "dev" script will use pre-defined PORT variable and run "next dev" and "vovk dev" as "concurrently" CLI arguments ${chalk.whiteBright.bold(`"PORT=3000 concurrently 'vovk dev' 'next dev' --kill-others"`)}`,
190
+ description: `The script will use pre-defined PORT variable and run "next dev" and "vovk dev" as "concurrently" CLI arguments ${chalk.whiteBright.bold(`"${getDevScript(pkgJson, 'explicit')}"`)}`,
195
191
  },
196
192
  {
197
193
  name: 'No',
198
194
  value: undefined,
199
- description: 'Add the scripts manually',
195
+ description: 'Add the NPM scripts manually',
200
196
  },
201
197
  ],
202
198
  }));
199
+ reactQuery =
200
+ reactQuery ??
201
+ (await confirm({
202
+ message: 'Do you want to use @tanstack/react-query for data fetching at React components?',
203
+ }));
203
204
  if (typeof updateTsConfig === 'undefined') {
204
205
  let shouldAsk = false;
205
206
  try {
206
207
  shouldAsk = !(await checkTSConfigForExperimentalDecorators(root));
207
208
  }
208
209
  catch (error) {
209
- log.error(`Failed to check tsconfig.json for experimentalDecorators: ${error.message}`);
210
+ log.error(`Failed to check tsconfig.json for "experimentalDecorators": ${error.message}`);
210
211
  }
211
212
  if (shouldAsk) {
212
213
  updateTsConfig = await confirm({
213
- message: 'Do you want to add experimentalDecorators to tsconfig.json?',
214
+ message: 'Do you want to add "experimentalDecorators" option to tsconfig.json?',
214
215
  });
215
216
  }
216
217
  }
217
- await this.#init({ configPaths }, {
218
+ await this.#init({ configPaths, pkgJson }, {
218
219
  useNpm: useNpm ?? (!useYarn && !usePnpm && !useBun),
219
220
  useYarn: useYarn ?? false,
220
221
  usePnpm: usePnpm ?? false,
@@ -223,7 +224,7 @@ export class Init {
223
224
  updateTsConfig,
224
225
  updateScripts,
225
226
  validationLibrary,
226
- validateOnClient,
227
+ reactQuery,
227
228
  dryRun,
228
229
  channel,
229
230
  });
@@ -4,10 +4,8 @@ import chalk from 'chalk';
4
4
  import getNPMPackageMetadata from '../utils/getNPMPackageMetadata.mjs';
5
5
  async function updateDeps({ packageJson, packageNames, channel, key, }) {
6
6
  return Promise.all(packageNames.map(async (packageName) => {
7
- if (packageJson[key]?.[packageName])
8
- return; // Skip if already present
9
7
  const metadata = await getNPMPackageMetadata(packageName);
10
- const isVovk = packageName.startsWith('vovk');
8
+ const isVovk = packageName.startsWith('vovk') && packageName !== 'vovk-mapped-types';
11
9
  const latestVersion = metadata['dist-tags'][isVovk ? (channel ?? 'latest') : 'latest'];
12
10
  if (!packageJson[key]) {
13
11
  packageJson[key] = {};
@@ -23,10 +21,10 @@ export default async function updateDependenciesWithoutInstalling({ log, dir, de
23
21
  await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
24
22
  log.info('Added dependencies to package.json:');
25
23
  for (const dependency of dependencyNames) {
26
- log.raw.log(` - ${chalk.cyan(dependency)}`);
24
+ log.raw.info(` - ${chalk.cyan(dependency)}`);
27
25
  }
28
26
  log.info('Added devDependencies to package.json:');
29
27
  for (const dependency of devDependencyNames) {
30
- log.raw.log(` - ${chalk.cyan(dependency)}`);
28
+ log.raw.info(` - ${chalk.cyan(dependency)}`);
31
29
  }
32
30
  }
@@ -1 +1,3 @@
1
- export default function updateNPMScripts(root: string, updateScriptsMode: 'implicit' | 'explicit'): Promise<void>;
1
+ import NPMCliPackageJson from '@npmcli/package-json';
2
+ export declare function getDevScript(pkgJson: NPMCliPackageJson, updateScriptsMode: 'implicit' | 'explicit'): string;
3
+ export default function updateNPMScripts(pkgJson: NPMCliPackageJson, root: string, updateScriptsMode: 'implicit' | 'explicit'): Promise<void>;
@@ -1,12 +1,15 @@
1
- import NPMCliPackageJson from '@npmcli/package-json';
2
- export default async function updateNPMScripts(root, updateScriptsMode) {
3
- const pkgJson = await NPMCliPackageJson.load(root);
1
+ export function getDevScript(pkgJson, updateScriptsMode) {
2
+ const nextDev = pkgJson.content.scripts?.dev ?? 'next dev';
3
+ const nextDevFlags = nextDev.replace('next dev', '').trim();
4
+ return updateScriptsMode === 'explicit'
5
+ ? `PORT=3000 concurrently '${nextDev}' 'vovk dev' --kill-others`
6
+ : `vovk dev --next-dev${nextDevFlags ? ` -- ${nextDevFlags}` : ''}`;
7
+ }
8
+ export default async function updateNPMScripts(pkgJson, root, updateScriptsMode) {
4
9
  pkgJson.update({
5
10
  scripts: {
6
- generate: 'vovk generate',
7
- dev: updateScriptsMode === 'explicit'
8
- ? "PORT=3000 concurrently 'vovk dev' 'next dev' --kill-others"
9
- : 'vovk dev --next-dev',
11
+ ...pkgJson.content.scripts,
12
+ dev: getDevScript(pkgJson, updateScriptsMode),
10
13
  },
11
14
  });
12
15
  await pkgJson.save();
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export default function initProgram(program: Command): Command;
@@ -0,0 +1,21 @@
1
+ import { Init } from './init/index.mjs';
2
+ // reused at vovk-init
3
+ export default function initProgram(program) {
4
+ return program
5
+ .argument('[prefix]', 'directory to initialize project in', '.')
6
+ .description('Initialize Vovk.ts project')
7
+ .option('-y, --yes', 'skip all prompts and use default values')
8
+ .option('--log-level <level>', 'set log level', 'info')
9
+ .option('--use-npm', 'use npm as package manager')
10
+ .option('--use-yarn', 'use yarn as package manager')
11
+ .option('--use-pnpm', 'use pnpm as package manager')
12
+ .option('--use-bun', 'use bun as package manager')
13
+ .option('--skip-install', 'skip installing dependencies')
14
+ .option('--update-ts-config', 'update tsconfig.json')
15
+ .option('--update-scripts <mode>', 'update package.json scripts ("implicit" or "explicit")')
16
+ .option('--validation-library <library>', 'validation library to use ("vovk-zod", "vovk-yup", "vovk-dto" or another); set to "none" to skip')
17
+ .option('--react-query', 'use @tanstack/react-query for data fetching inside components')
18
+ .option('--channel <channel>', 'channel to use for fetching packages', 'latest')
19
+ .option('--dry-run', 'do not write files to disk')
20
+ .action((prefix = '.', options) => new Init().main(prefix, options));
21
+ }
@@ -1,5 +1,11 @@
1
+ import type { VovkStrictConfig } from 'vovk';
1
2
  export type Segment = {
2
3
  routeFilePath: string;
3
4
  segmentName: string;
5
+ segmentImportPath: string;
4
6
  };
5
- export default function locateSegments(dir: string, rootDir?: string): Promise<Segment[]>;
7
+ export default function locateSegments({ dir, rootDir, config, }: {
8
+ dir: string;
9
+ rootDir?: string;
10
+ config: VovkStrictConfig | null;
11
+ }): Promise<Segment[]>;
@@ -1,8 +1,9 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import getFileSystemEntryType from './utils/getFileSystemEntryType.mjs';
4
- export default async function locateSegments(dir, rootDir = dir) {
4
+ export default async function locateSegments({ dir, rootDir, config, }) {
5
5
  let results = [];
6
+ rootDir = rootDir ?? dir;
6
7
  // Read the contents of the directory
7
8
  const list = await fs.readdir(dir);
8
9
  // Iterate through each item in the directory
@@ -16,12 +17,13 @@ export default async function locateSegments(dir, rootDir = dir) {
16
17
  const routeFilePath = path.join(filePath, 'route.ts');
17
18
  if (await getFileSystemEntryType(routeFilePath)) {
18
19
  // Calculate the basePath relative to the root directory
19
- const segmentName = path.relative(rootDir, dir);
20
- results.push({ routeFilePath, segmentName });
20
+ const segmentName = path.relative(rootDir, dir).replace(/\\/g, '/'); // windows fix
21
+ const segmentImportPath = path.relative(config?.clientOutDir ?? '.__error', routeFilePath);
22
+ results.push({ routeFilePath, segmentName, segmentImportPath });
21
23
  }
22
24
  }
23
25
  // Recursively search inside subdirectories
24
- const subDirResults = await locateSegments(filePath, rootDir);
26
+ const subDirResults = await locateSegments({ dir: filePath, rootDir, config });
25
27
  results = results.concat(subDirResults);
26
28
  }
27
29
  }
@@ -1,6 +1,5 @@
1
- export default function addClassToSegmentCode(segmentSourceCode: string, { sourceName, compiledName, type, importPath, }: {
1
+ export default function addClassToSegmentCode(segmentSourceCode: string, { sourceName, compiledName, importPath, }: {
2
2
  sourceName: string;
3
3
  compiledName: string;
4
- type: 'worker' | 'controller';
5
4
  importPath: string;
6
5
  }): string;
@@ -1,5 +1,5 @@
1
1
  import { Project, QuoteKind, SyntaxKind } from 'ts-morph';
2
- export default function addClassToSegmentCode(segmentSourceCode, { sourceName, compiledName, type, importPath, }) {
2
+ export default function addClassToSegmentCode(segmentSourceCode, { sourceName, compiledName, importPath, }) {
3
3
  const project = new Project({
4
4
  manipulationSettings: {
5
5
  quoteKind: QuoteKind.Single,
@@ -16,8 +16,8 @@ export default function addClassToSegmentCode(segmentSourceCode, { sourceName, c
16
16
  moduleSpecifier: importPath,
17
17
  });
18
18
  }
19
- // Get the variable declaration for controllers or workers
20
- const variableDeclaration = sourceFile.getVariableDeclaration(`${type}s`);
19
+ // Get the variable declaration for controllers
20
+ const variableDeclaration = sourceFile.getVariableDeclaration('controllers');
21
21
  if (variableDeclaration) {
22
22
  const initializer = variableDeclaration.getInitializer();
23
23
  if (initializer && initializer.getKind() === SyntaxKind.ObjectLiteralExpression) {
@@ -2,6 +2,7 @@ import pluralize from 'pluralize';
2
2
  // feel free to open a direct PR if you have more common terms to add
3
3
  const terms = [
4
4
  ['entity', 'entities'],
5
+ ['entry', 'entries'],
5
6
  ['regex', 'regexes'],
6
7
  ['index', 'indices'],
7
8
  ['matrix', 'matrices'],
@@ -1,2 +1,2 @@
1
1
  import type { NewOptions } from '../types.mjs';
2
- export default function newComponents(components: string[], { dryRun, dir, templates, overwrite, noSegmentUpdate }: NewOptions): Promise<void>;
2
+ export default function newComponents(components: string[], { dryRun, dir, templates, overwrite, noSegmentUpdate, empty }: NewOptions): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  import newModule from './newModule.mjs';
2
2
  import newSegment from './newSegment.mjs';
3
- export default async function newComponents(components, { dryRun, dir, templates, overwrite, noSegmentUpdate }) {
3
+ export default async function newComponents(components, { dryRun, dir, templates, overwrite, noSegmentUpdate, empty }) {
4
4
  if (components[0] === 'segment' || components[0] === 'segments') {
5
5
  // vovk new segment [segmentName]
6
6
  let segmentNames = components
@@ -31,6 +31,7 @@ export default async function newComponents(components, { dryRun, dir, templates
31
31
  overwrite,
32
32
  noSegmentUpdate,
33
33
  dryRun,
34
+ empty,
34
35
  });
35
36
  }
36
37
  }