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

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 (44) hide show
  1. package/dist/generateClient.d.mts +1 -1
  2. package/dist/generateClient.mjs +2 -2
  3. package/dist/getProjectInfo/getConfigAbsolutePaths.mjs +2 -2
  4. package/dist/getProjectInfo/getRelativeSrcRoot.mjs +1 -1
  5. package/dist/getProjectInfo/index.mjs +1 -1
  6. package/dist/index.d.mts +0 -23
  7. package/dist/index.mjs +9 -6
  8. package/dist/init/checkTSConfigForExperimentalDecorators.mjs +2 -2
  9. package/dist/init/createConfig.d.mts +1 -1
  10. package/dist/init/createConfig.mjs +3 -3
  11. package/dist/init/getTemplateFilesFromPackage.mjs +2 -2
  12. package/dist/init/index.d.mts +1 -2
  13. package/dist/init/index.mjs +2 -60
  14. package/dist/init/installDependencies.d.mts +1 -1
  15. package/dist/init/installDependencies.mjs +1 -1
  16. package/dist/init/logUpdateDependenciesError.d.mts +2 -2
  17. package/dist/init/logUpdateDependenciesError.mjs +3 -3
  18. package/dist/init/updateDependenciesWithoutInstalling.d.mts +3 -2
  19. package/dist/init/updateDependenciesWithoutInstalling.mjs +4 -4
  20. package/dist/init/updateTypeScriptConfig.mjs +2 -2
  21. package/dist/locateSegments.mjs +2 -2
  22. package/dist/new/addClassToSegmentCode.mjs +2 -2
  23. package/dist/new/index.d.mts +2 -2
  24. package/dist/new/index.mjs +2 -2
  25. package/dist/new/newModule.d.mts +2 -2
  26. package/dist/new/newModule.mjs +33 -27
  27. package/dist/new/newSegment.mjs +3 -3
  28. package/dist/postinstall.mjs +2 -2
  29. package/dist/types.d.mts +30 -1
  30. package/dist/utils/formatLoggedSegmentName.mjs +1 -1
  31. package/dist/utils/getAvailablePort.mjs +2 -1
  32. package/dist/utils/getFileSystemEntryType.mjs +1 -1
  33. package/dist/watcher/diffSchema.d.mts +1 -1
  34. package/dist/watcher/ensureClient.d.mts +5 -0
  35. package/dist/watcher/ensureClient.mjs +31 -0
  36. package/dist/watcher/ensureSchemaFiles.mjs +2 -2
  37. package/dist/watcher/index.mjs +13 -11
  38. package/dist/watcher/logDiffResult.d.mts +2 -2
  39. package/dist/watcher/logDiffResult.mjs +13 -9
  40. package/dist/watcher/writeOneSchemaFile.d.mts +1 -1
  41. package/dist/watcher/writeOneSchemaFile.mjs +2 -2
  42. package/package.json +4 -3
  43. package/templates/controller.ejs +5 -5
  44. package/templates/service.ejs +4 -4
@@ -1,6 +1,6 @@
1
+ import type { VovkSchema } from 'vovk';
1
2
  import type { ProjectInfo } from './getProjectInfo/index.mjs';
2
3
  import type { Segment } from './locateSegments.mjs';
3
- import type { VovkSchema } from 'vovk';
4
4
  export default function generateClient(projectInfo: ProjectInfo, segments: Segment[], segmentsSchema: Record<string, VovkSchema>): Promise<{
5
5
  written: boolean;
6
6
  path: string;
@@ -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 formatLoggedSegmentName from './utils/formatLoggedSegmentName.mjs';
4
4
  import prettify from './utils/prettify.mjs';
5
5
  export default async function generateClient(projectInfo, segments, segmentsSchema) {
@@ -1,5 +1,5 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
3
  export default async function getConfigAbsolutePaths({ cwd, relativePath, }) {
4
4
  const rootDir = path.resolve(cwd, relativePath || '');
5
5
  const baseName = 'vovk.config';
@@ -1,4 +1,4 @@
1
- import path from 'path';
1
+ import path from 'node:path';
2
2
  import getFileSystemEntryType, { FileSystemEntryType } from '../utils/getFileSystemEntryType.mjs';
3
3
  export default async function getRelativeSrcRoot({ cwd }) {
4
4
  // Next.js Docs: src/app or src/pages will be ignored if app or pages are present in the root directory.
@@ -1,4 +1,4 @@
1
- import path from 'path';
1
+ import path from 'node:path';
2
2
  import getConfig from './getConfig.mjs';
3
3
  import getLogger from '../utils/getLogger.mjs';
4
4
  export default async function getProjectInfo({ port: givenPort, clientOutDir, cwd = process.cwd(), } = {}) {
package/dist/index.d.mts CHANGED
@@ -1,30 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import type { LogLevelNames } from 'loglevel';
4
3
  import type { VovkConfig, VovkEnv } from './types.mjs';
5
4
  import 'dotenv/config';
6
5
  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
- template?: string;
25
- dir?: string;
26
- overwrite?: boolean;
27
- noSegmentUpdate?: boolean;
28
- }
29
6
  declare const program: Command;
30
7
  export declare function initProgram(p: typeof program, command: string): Command;
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import path from 'path';
2
+ import path from 'node:path';
3
+ import { readFileSync } from 'node:fs';
3
4
  import { Command } from 'commander';
4
- import { readFileSync } from 'fs';
5
5
  import concurrently from 'concurrently';
6
6
  import getAvailablePort from './utils/getAvailablePort.mjs';
7
7
  import getProjectInfo from './getProjectInfo/index.mjs';
@@ -25,12 +25,15 @@ program
25
25
  const PORT = !options.nextDev
26
26
  ? process.env.PORT
27
27
  : process.env.PORT ||
28
- (await getAvailablePort(3000, portAttempts, 0, (failedPort, tryingPort) => console.warn(`🐺 Next.js Port ${failedPort} is in use, trying ${tryingPort} instead.`)).catch(() => {
28
+ (await getAvailablePort(3000, portAttempts, 0, (failedPort, tryingPort) =>
29
+ // eslint-disable-next-line no-console
30
+ console.warn(`🐺 Next.js Port ${failedPort} is in use, trying ${tryingPort} instead.`)).catch(() => {
29
31
  throw new Error(`🐺 ❌ Failed to find available Next port after ${portAttempts} attempts`);
30
32
  }));
31
33
  if (!PORT) {
32
34
  throw new Error('🐺 ❌ PORT env variable is required');
33
35
  }
36
+ console.log(`TODO:`, command.args);
34
37
  if (options.nextDev) {
35
38
  const { result } = concurrently([
36
39
  {
@@ -75,7 +78,7 @@ export function initProgram(p, command) {
75
78
  return p
76
79
  .command(command + '[prefix]')
77
80
  .description('Initialize Vovk project')
78
- .option('-Y, --yes', 'Skip all prompts and use default values')
81
+ .option('-y, --yes', 'Skip all prompts and use default values')
79
82
  .option('--log-level <level>', 'Set log level', 'info')
80
83
  .option('--use-npm', 'Use npm as package manager')
81
84
  .option('--use-yarn', 'Use yarn as package manager')
@@ -95,8 +98,8 @@ program
95
98
  .command('new [components...]')
96
99
  .alias('n')
97
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')
98
- .option('-O, --overwrite', 'Overwrite existing files')
99
- .option('--template <template>', 'Override config template')
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')
100
103
  .option('--dir <dirname>', 'Override dirName in template file. Relative to the root of the project')
101
104
  .option('--no-segment-update', 'Do not update segment files when creating a new module')
102
105
  .option('--dry-run', 'Do not write files to disk')
@@ -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,5 +1,5 @@
1
1
  import type getLogger from '../utils/getLogger.mjs';
2
- import type { InitOptions } from '../index.mjs';
2
+ import type { InitOptions } from '../types.mjs';
3
3
  export default function createConfig({ root, log, dryRun, options: { validationLibrary, validateOnClient }, }: {
4
4
  root: string;
5
5
  log: ReturnType<typeof getLogger>;
@@ -1,8 +1,8 @@
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';
5
+ import getFileSystemEntryType, { FileSystemEntryType } from '../utils/getFileSystemEntryType.mjs';
6
6
  export default async function createConfig({ root, log, dryRun, options: { validationLibrary, validateOnClient }, }) {
7
7
  const config = {};
8
8
  const dotConfigPath = path.join(root, '.config');
@@ -1,6 +1,6 @@
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.
@@ -1,5 +1,4 @@
1
- #!/usr/bin/env node
2
- import type { InitOptions } from '../index.mjs';
1
+ import type { InitOptions } from '../types.mjs';
3
2
  import getLogger from '../utils/getLogger.mjs';
4
3
  export declare class Init {
5
4
  #private;
@@ -1,64 +1,6 @@
1
- #!/usr/bin/env node
2
- /*
3
- npx vovk-cli init
4
- - Check if the project is already initialized
5
- - Do you want to reinitialize the project?
6
- - Yes
7
- - No (exit)
8
- - Check for package.json, if not found, show error and exit
9
- - Check for tsconfig.json, if not found, show error and exit
10
- - Check Next.js installed
11
- - Choose validation library: add to the installation list
12
- - vovk-zod
13
- - Further installation notes: install zod
14
- - vovk-yup
15
- - Further installation notes: install yup
16
- - vovk-dto
17
- - Further installation notes: install class-validator and class-transformer
18
- - None
19
- - If validation library is not None,
20
- - Do you want to enable client validation?
21
- - Yes
22
- - Add client validation to the config
23
- - No
24
- - Do you want to update NPM scripts?
25
- - Yes
26
- - Update NPM scripts
27
- - No
28
- - Do you want to use explicit concurrently?
29
- - Yes (recommended)
30
- - Add concurrently to the installation list
31
- - No
32
- - if experimentalDecorators is not found in tsconfig.json,
33
- - Do you want to add experimentalDecorators to tsconfig.json?
34
- - Yes
35
- - Add experimentalDecorators to tsconfig.json
36
- - No
37
- - Do you want to create route file with example service and controller? (NO NEED)
38
- - Yes
39
- - Create route file with example controller
40
- - No, I will create it myself
41
- - End
42
- - If there are any packages to install, install them
43
- - Show installation notes
44
- - If there are any files to create, create
45
- - If there are any config files to update, update
46
- - If example route file is NOT created, show example route file and controller
47
- - Show how to run the project
48
- - If npm scripts are updated
49
- - npm run dev
50
- - If npm scripts are NOT updated
51
- - If concurrently is installed
52
- - concurrently "vovk dev" "next dev"
53
- - If concurrently is NOT installed
54
- - vovk dev --next-dev
55
- - Open http://localhost:3000/api/hello-world
56
- - Show how to make a request to the example route
57
- - Show success message
58
- */
59
1
  import { confirm, select } from '@inquirer/prompts';
60
- import path from 'path';
61
- import fs from 'fs/promises';
2
+ import path from 'node:path';
3
+ import fs from 'node:fs/promises';
62
4
  import getConfigPaths from '../getProjectInfo/getConfigAbsolutePaths.mjs';
63
5
  import chalk from 'chalk';
64
6
  import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
@@ -1,5 +1,5 @@
1
- import { InitOptions } from '../index.mjs';
2
1
  import getLogger from '../utils/getLogger.mjs';
2
+ import type { InitOptions } from '../types.mjs';
3
3
  type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun';
4
4
  export declare function getPackageManager(options: Pick<InitOptions, 'useNpm' | 'useYarn' | 'usePnpm' | 'useBun'>): PackageManager;
5
5
  export default function installDependencies({ log, cwd, options, }: {
@@ -1,4 +1,4 @@
1
- import { spawn } from 'child_process';
1
+ import { spawn } from 'node:child_process';
2
2
  export function getPackageManager(options) {
3
3
  if (options.useNpm)
4
4
  return 'npm';
@@ -1,5 +1,5 @@
1
- import getLogger from "../utils/getLogger.mjs";
2
- export default function logUpdateDependenciesError({ useNpm, useYarn, usePnpm, useBun, log, dependencies, devDependencies, error }: {
1
+ import type getLogger from '../utils/getLogger.mjs';
2
+ export default function logUpdateDependenciesError({ useNpm, useYarn, usePnpm, useBun, log, dependencies, devDependencies, error, }: {
3
3
  useNpm?: boolean;
4
4
  useYarn?: boolean;
5
5
  usePnpm?: boolean;
@@ -1,6 +1,6 @@
1
- import chalkHighlightThing from "../utils/chalkHighlightThing.mjs";
2
- import { getPackageManager } from "./installDependencies.mjs";
3
- export default function logUpdateDependenciesError({ useNpm, useYarn, usePnpm, useBun, log, dependencies, devDependencies, error }) {
1
+ import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
2
+ import { getPackageManager } from './installDependencies.mjs';
3
+ export default function logUpdateDependenciesError({ useNpm, useYarn, usePnpm, useBun, log, dependencies, devDependencies, error, }) {
4
4
  const packageManager = getPackageManager({ useNpm, useYarn, usePnpm, useBun });
5
5
  const installCommands = [];
6
6
  if (dependencies.length > 0) {
@@ -1,8 +1,9 @@
1
- import getLogger from '../utils/getLogger.mjs';
1
+ import type getLogger from '../utils/getLogger.mjs';
2
+ import { InitOptions } from '../types.mjs';
2
3
  export default function updateDependenciesWithoutInstalling({ log, dir, dependencyNames, devDependencyNames, channel, }: {
3
4
  log: ReturnType<typeof getLogger>;
4
5
  dir: string;
5
6
  dependencyNames: string[];
6
7
  devDependencyNames: string[];
7
- channel: 'latest' | 'beta' | 'dev';
8
+ channel: InitOptions['channel'];
8
9
  }): Promise<void>;
@@ -1,14 +1,14 @@
1
- import fs from 'fs/promises';
2
- import getNPMPackageMetadata from '../utils/getNPMPackageMetadata.mjs';
3
- import path from 'path';
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
4
3
  import chalk from 'chalk';
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
7
  if (packageJson[key]?.[packageName])
8
8
  return; // Skip if already present
9
9
  const metadata = await getNPMPackageMetadata(packageName);
10
10
  const isVovk = packageName.startsWith('vovk');
11
- const latestVersion = metadata['dist-tags'][isVovk ? channel : 'latest'];
11
+ const latestVersion = metadata['dist-tags'][isVovk ? (channel ?? 'latest') : 'latest'];
12
12
  if (!packageJson[key]) {
13
13
  packageJson[key] = {};
14
14
  }
@@ -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
  import prettify from '../utils/prettify.mjs';
5
5
  export default async function updateTypeScriptConfig(root) {
@@ -1,5 +1,5 @@
1
- import { promises as fs } from 'fs';
2
- import * as path from 'path';
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
3
  import getFileSystemEntryType from './utils/getFileSystemEntryType.mjs';
4
4
  export default async function locateSegments(dir, rootDir = dir) {
5
5
  let results = [];
@@ -2,8 +2,8 @@ import { Project, QuoteKind, SyntaxKind } from 'ts-morph';
2
2
  export default function addClassToSegmentCode(segmentSourceCode, { sourceName, compiledName, type, importPath, }) {
3
3
  const project = new Project({
4
4
  manipulationSettings: {
5
- quoteKind: QuoteKind.Single
6
- }
5
+ quoteKind: QuoteKind.Single,
6
+ },
7
7
  });
8
8
  const sourceFile = project.createSourceFile('route.ts', segmentSourceCode, { overwrite: true });
9
9
  // Add the import if it doesn't exist
@@ -1,2 +1,2 @@
1
- import type { NewOptions } from '../index.mjs';
2
- export default function newComponents(components: string[], { dryRun, dir, template, overwrite, noSegmentUpdate }: NewOptions): Promise<void>;
1
+ import type { NewOptions } from '../types.mjs';
2
+ export default function newComponents(components: string[], { dryRun, dir, templates, overwrite, noSegmentUpdate }: 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, template, overwrite, noSegmentUpdate }) {
3
+ export default async function newComponents(components, { dryRun, dir, templates, overwrite, noSegmentUpdate }) {
4
4
  if (components[0] === 'segment' || components[0] === 'segments') {
5
5
  // vovk new segment [segmentName]
6
6
  let segmentNames = components
@@ -27,7 +27,7 @@ export default async function newComponents(components, { dryRun, dir, template,
27
27
  what,
28
28
  moduleNameWithOptionalSegment,
29
29
  dir,
30
- template,
30
+ templates,
31
31
  overwrite,
32
32
  noSegmentUpdate,
33
33
  dryRun,
@@ -1,9 +1,9 @@
1
- export default function newModule({ what, moduleNameWithOptionalSegment, dryRun, dir: dirNameFlag, template: templateFlag, noSegmentUpdate, overwrite, }: {
1
+ export default function newModule({ what, moduleNameWithOptionalSegment, dryRun, dir: dirNameFlag, templates: templatesFlag, noSegmentUpdate, overwrite, }: {
2
2
  what: string[];
3
3
  moduleNameWithOptionalSegment: string;
4
4
  dryRun?: boolean;
5
5
  dir?: string;
6
- template?: string;
6
+ templates?: string[];
7
7
  noSegmentUpdate?: boolean;
8
8
  overwrite?: boolean;
9
9
  }): Promise<void>;
@@ -1,11 +1,11 @@
1
- import path from 'path';
2
- import fs from 'fs/promises';
3
- import getProjectInfo from '../getProjectInfo/index.mjs';
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
4
3
  import render from './render.mjs';
4
+ import addClassToSegmentCode from './addClassToSegmentCode.mjs';
5
+ import getProjectInfo from '../getProjectInfo/index.mjs';
6
+ import locateSegments from '../locateSegments.mjs';
5
7
  import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
6
8
  import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
7
- import locateSegments from '../locateSegments.mjs';
8
- import addClassToSegmentCode from './addClassToSegmentCode.mjs';
9
9
  import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
10
10
  import prettify from '../utils/prettify.mjs';
11
11
  function splitByLast(str, delimiter = '/') {
@@ -18,9 +18,9 @@ function splitByLast(str, delimiter = '/') {
18
18
  const after = str.substring(index + delimiter.length);
19
19
  return [before, after];
20
20
  }
21
- export default async function newModule({ what, moduleNameWithOptionalSegment, dryRun, dir: dirNameFlag, template: templateFlag, noSegmentUpdate, overwrite, }) {
21
+ export default async function newModule({ what, moduleNameWithOptionalSegment, dryRun, dir: dirNameFlag, templates: templatesFlag, noSegmentUpdate, overwrite, }) {
22
22
  const { config, log, apiDir, cwd } = await getProjectInfo();
23
- const templates = config.templates;
23
+ let templates = config.templates;
24
24
  const [segmentName, moduleName] = splitByLast(moduleNameWithOptionalSegment);
25
25
  // replace c by controller, s by service, w by worker, everything else keeps the same
26
26
  what = what.map((s) => {
@@ -35,28 +35,27 @@ export default async function newModule({ what, moduleNameWithOptionalSegment, d
35
35
  return s;
36
36
  }
37
37
  });
38
- console.log('moduleNameWithOptionalSegment', moduleNameWithOptionalSegment);
39
- console.log('what', what);
40
- if (templateFlag) {
41
- if (what.length > 1) {
42
- throw new Error('Cannot use --template flag with multiple types');
38
+ if (templatesFlag) {
39
+ if (templatesFlag.length > what.length) {
40
+ throw new Error('Too many templates provided');
43
41
  }
42
+ templates = templatesFlag.reduce((acc, templatePath, index) => ({
43
+ ...acc,
44
+ [what[index]]: templatePath,
45
+ }), templates);
44
46
  }
45
- else {
46
- // check if template exists
47
- for (const type of what) {
48
- if (!templates[type]) {
49
- throw new Error(`Template for ${type} not found in config`);
50
- }
47
+ for (const type of what) {
48
+ if (!templates[type]) {
49
+ throw new Error(`Template for "${type}" not found`);
51
50
  }
52
51
  }
53
52
  const segments = await locateSegments(apiDir);
54
53
  const segment = segments.find((s) => s.segmentName === segmentName);
55
54
  if (!segment) {
56
- throw new Error(`Segment ${segmentName} not found`);
55
+ throw new Error(`Unable to create module. Segment "${segmentName}" not found. Run "vovk new segment ${segmentName}" to create it`);
57
56
  }
58
57
  for (const type of what) {
59
- const templatePath = templateFlag ?? templates[type];
58
+ const templatePath = templates[type];
60
59
  const templateAbsolutePath = templatePath.startsWith('/') || templatePath.startsWith('.')
61
60
  ? path.resolve(cwd, templatePath)
62
61
  : path.resolve(cwd, './node_modules', templatePath);
@@ -68,7 +67,14 @@ export default async function newModule({ what, moduleNameWithOptionalSegment, d
68
67
  segmentName,
69
68
  moduleName,
70
69
  });
71
- const absoluteModuleDir = path.join(cwd, dirNameFlag || renderedDirName);
70
+ const dirName = dirNameFlag || renderedDirName;
71
+ if (!dirName) {
72
+ throw new Error(`The template for "${type}" does not provide a dirName`);
73
+ }
74
+ if (!fileName) {
75
+ throw new Error(`The template for "${type}" does not provide a fileName`);
76
+ }
77
+ const absoluteModuleDir = path.join(cwd, dirName);
72
78
  const absoluteModulePath = path.join(absoluteModuleDir, fileName);
73
79
  const prettiedCode = await prettify(code, absoluteModulePath);
74
80
  if (!dryRun) {
@@ -78,31 +84,31 @@ export default async function newModule({ what, moduleNameWithOptionalSegment, d
78
84
  else {
79
85
  await fs.mkdir(absoluteModuleDir, { recursive: true });
80
86
  await fs.writeFile(absoluteModulePath, prettiedCode);
87
+ log.info(`Created ${chalkHighlightThing(fileName)} using ${chalkHighlightThing(`"${type}"`)} template for ${formatLoggedSegmentName(segmentName)}`);
81
88
  }
82
89
  }
83
90
  if (type === 'controller' || type === 'worker') {
84
91
  if (!sourceName) {
85
- throw new Error('sourceName is required');
92
+ throw new Error(`The template for "${type}" does not provide a sourceName`);
86
93
  }
87
94
  if (!compiledName) {
88
- throw new Error('compiledName is required');
95
+ throw new Error('The template for "${type}" does not provide a compiledName');
89
96
  }
90
97
  const { routeFilePath } = segment;
91
98
  const segmentSourceCode = await fs.readFile(routeFilePath, 'utf-8');
92
99
  const importPath = path.relative(path.dirname(routeFilePath), absoluteModulePath).replace(/\.(ts|tsx)$/, '');
93
100
  if (!noSegmentUpdate) {
94
- const newSegmentCode = addClassToSegmentCode(segmentSourceCode, {
101
+ const newSegmentCode = await prettify(addClassToSegmentCode(segmentSourceCode, {
95
102
  sourceName,
96
103
  compiledName,
97
104
  type,
98
105
  importPath,
99
- });
106
+ }), routeFilePath);
100
107
  if (!dryRun) {
101
108
  await fs.writeFile(routeFilePath, newSegmentCode);
102
109
  }
103
110
  }
104
- log.info(`Added ${chalkHighlightThing(sourceName)} ${type} to ${formatLoggedSegmentName(segmentName)} as ${chalkHighlightThing(compiledName)}`);
111
+ log.info(`Added ${chalkHighlightThing(sourceName)} ${type} as ${chalkHighlightThing(compiledName)} to ${formatLoggedSegmentName(segmentName)}`);
105
112
  }
106
- log.info(`Created ${chalkHighlightThing(fileName)} using "${chalkHighlightThing(type)}" template for ${formatLoggedSegmentName(segmentName)}`);
107
113
  }
108
114
  }
@@ -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 getProjectInfo from '../getProjectInfo/index.mjs';
4
4
  import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
5
5
  import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
@@ -29,5 +29,5 @@ ${segmentName ? ` segmentName: '${segmentName}',\n` : ''} emitSchema: true,
29
29
  await fs.mkdir(path.dirname(absoluteSegmentRoutePath), { recursive: true });
30
30
  await fs.writeFile(absoluteSegmentRoutePath, code);
31
31
  }
32
- log.info(`${formatLoggedSegmentName(segmentName, { upperFirst: true })} created at ${absoluteSegmentRoutePath}. Run ${chalkHighlightThing(`vovk new controller ${[segmentName, 'someName'].filter(Boolean).join('/')}`)} to create a controller.`);
32
+ log.info(`${formatLoggedSegmentName(segmentName, { upperFirst: true })} created at ${absoluteSegmentRoutePath}. Run ${chalkHighlightThing(`vovk new controller ${[segmentName, 'someName'].filter(Boolean).join('/')}`)} to create a new controller`);
33
33
  }
@@ -1,5 +1,5 @@
1
- import { promises as fs } from 'fs';
2
- import path from 'path';
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
3
  /**
4
4
  * Checks if a file exists at the given path.
5
5
  * @param {string} filePath - The path to the file.
package/dist/types.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { LogLevelNames } from 'loglevel';
1
+ import type { LogLevelNames } from 'loglevel';
2
2
  export type KnownAny = any;
3
3
  export type VovkEnv = {
4
4
  PORT?: string;
@@ -44,3 +44,32 @@ export type VovkModuleRenderResult = {
44
44
  compiledName?: string;
45
45
  code: string;
46
46
  };
47
+ export interface DevOptions {
48
+ clientOut?: string;
49
+ nextDev?: boolean;
50
+ }
51
+ export interface GenerateOptions {
52
+ clientOut?: string;
53
+ }
54
+ export interface InitOptions {
55
+ yes?: boolean;
56
+ logLevel: LogLevelNames;
57
+ useNpm?: boolean;
58
+ useYarn?: boolean;
59
+ usePnpm?: boolean;
60
+ useBun?: boolean;
61
+ skipInstall?: boolean;
62
+ updateTsConfig?: boolean;
63
+ updateScripts?: 'implicit' | 'explicit';
64
+ validationLibrary?: string | null;
65
+ validateOnClient?: boolean;
66
+ dryRun?: boolean;
67
+ channel?: 'latest' | 'beta' | 'draft';
68
+ }
69
+ export interface NewOptions {
70
+ dryRun?: boolean;
71
+ templates?: string[];
72
+ dir?: string;
73
+ overwrite?: boolean;
74
+ noSegmentUpdate?: boolean;
75
+ }
@@ -1,5 +1,5 @@
1
- import chalkHighlightThing from './chalkHighlightThing.mjs';
2
1
  import upperFirstLodash from 'lodash/upperFirst.js';
2
+ import chalkHighlightThing from './chalkHighlightThing.mjs';
3
3
  export default function formatLoggedSegmentName(segmentName, { withChalk = true, upperFirst = false } = {}) {
4
4
  let text = segmentName ? `segment "${segmentName}"` : 'the root segment';
5
5
  text = upperFirst ? upperFirstLodash(text) : text;
@@ -1,4 +1,5 @@
1
- import net from 'net';
1
+ import net from 'node:net';
2
+ // TODO check comments
2
3
  /**
3
4
  * Checks if a port is available.
4
5
  * @param {number} port - The port to check.
@@ -1,4 +1,4 @@
1
- import fs from 'fs/promises';
1
+ import fs from 'node:fs/promises';
2
2
  export var FileSystemEntryType;
3
3
  (function (FileSystemEntryType) {
4
4
  FileSystemEntryType["FILE"] = "FILE";
@@ -1,5 +1,5 @@
1
1
  import type { VovkSchema } from 'vovk';
2
- import { _VovkControllerSchema, _VovkWorkerSchema } from 'vovk/types';
2
+ import type { _VovkControllerSchema, _VovkWorkerSchema } from 'vovk/types';
3
3
  interface HandlersDiff {
4
4
  nameOfClass: string;
5
5
  added: string[];
@@ -0,0 +1,5 @@
1
+ import type { ProjectInfo } from '../getProjectInfo/index.mjs';
2
+ export default function ensureClient(projectInfo: ProjectInfo): Promise<{
3
+ written: boolean;
4
+ path: string;
5
+ }>;
@@ -0,0 +1,31 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ export default async function ensureClient(projectInfo) {
4
+ const { config, cwd, log } = projectInfo;
5
+ const now = Date.now();
6
+ const clientoOutDirAbsolutePath = path.join(cwd, config.clientOutDir);
7
+ const dts = `// auto-generated
8
+ // This is a temporary placeholder to avoid errors if client is imported before it's generated.
9
+ // If you still see this text, the client is not generated yet because of an unknown problem.
10
+ // Feel free to report an issue at https://github.com/finom/vovk/issues`;
11
+ const js = dts;
12
+ const ts = dts;
13
+ const localJsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'client.js');
14
+ const localDtsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'client.d.ts');
15
+ const localTsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'index.ts');
16
+ const existingJs = await fs.readFile(localJsAbsolutePath, 'utf-8').catch(() => null);
17
+ const existingDts = await fs.readFile(localDtsAbsolutePath, 'utf-8').catch(() => null);
18
+ const existingTs = await fs.readFile(localTsAbsolutePath, 'utf-8').catch(() => null);
19
+ if (existingJs && existingDts && existingTs) {
20
+ return { written: false, path: clientoOutDirAbsolutePath };
21
+ }
22
+ await fs.mkdir(clientoOutDirAbsolutePath, { recursive: true });
23
+ if (!existingJs)
24
+ await fs.writeFile(localJsAbsolutePath, js);
25
+ if (!existingDts)
26
+ await fs.writeFile(localDtsAbsolutePath, dts);
27
+ if (!existingTs)
28
+ await fs.writeFile(localTsAbsolutePath, ts);
29
+ log.info(`Empty client files are generated in ${Date.now() - now}ms`);
30
+ return { written: true, path: clientoOutDirAbsolutePath };
31
+ }
@@ -1,5 +1,5 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
3
  import debounce from 'lodash/debounce.js';
4
4
  import writeOneSchemaFile, { ROOT_SEGMENT_SCHEMA_NAME } from './writeOneSchemaFile.mjs';
5
5
  import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
@@ -1,19 +1,20 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
1
3
  import * as chokidar from 'chokidar';
2
- import fs from 'fs/promises';
3
- import getProjectInfo from '../getProjectInfo/index.mjs';
4
- import path from 'path';
4
+ import { Agent, setGlobalDispatcher } from 'undici';
5
+ import keyBy from 'lodash/keyBy.js';
6
+ import capitalize from 'lodash/capitalize.js';
7
+ import debounce from 'lodash/debounce.js';
8
+ import isEmpty from 'lodash/isEmpty.js';
5
9
  import { debouncedEnsureSchemaFiles } from './ensureSchemaFiles.mjs';
6
10
  import writeOneSchemaFile from './writeOneSchemaFile.mjs';
7
11
  import logDiffResult from './logDiffResult.mjs';
12
+ import ensureClient from './ensureClient.mjs';
13
+ import getProjectInfo from '../getProjectInfo/index.mjs';
8
14
  import generateClient from '../generateClient.mjs';
9
15
  import locateSegments from '../locateSegments.mjs';
10
16
  import debounceWithArgs from '../utils/debounceWithArgs.mjs';
11
- import debounce from 'lodash/debounce.js';
12
- import isEmpty from 'lodash/isEmpty.js';
13
17
  import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
14
- import keyBy from 'lodash/keyBy.js';
15
- import capitalize from 'lodash/capitalize.js';
16
- import { Agent, setGlobalDispatcher } from 'undici';
17
18
  export class VovkCLIWatcher {
18
19
  #projectInfo;
19
20
  #segments = [];
@@ -27,7 +28,7 @@ export class VovkCLIWatcher {
27
28
  const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
28
29
  const apiDirAbsolutePath = path.join(cwd, apiDir);
29
30
  const getSegmentName = (filePath) => path.relative(apiDirAbsolutePath, filePath).replace(segmentReg, '');
30
- log.debug(`Watching segments in ${apiDirAbsolutePath}`);
31
+ log.debug(`Watching segments at ${apiDirAbsolutePath}`);
31
32
  this.#segmentWatcher = chokidar
32
33
  .watch(apiDirAbsolutePath, {
33
34
  persistent: true,
@@ -85,7 +86,7 @@ export class VovkCLIWatcher {
85
86
  #watchModules = () => {
86
87
  const { config, cwd, log } = this.#projectInfo;
87
88
  const modulesDirAbsolutePath = path.join(cwd, config.modulesDir);
88
- log.debug(`Watching modules in ${modulesDirAbsolutePath}`);
89
+ log.debug(`Watching modules at ${modulesDirAbsolutePath}`);
89
90
  const processControllerChange = debounceWithArgs(this.#processControllerChange, 500);
90
91
  this.#modulesWatcher = chokidar
91
92
  .watch(modulesDirAbsolutePath, {
@@ -156,11 +157,12 @@ export class VovkCLIWatcher {
156
157
  .on('error', (error) => log.error(`Error watching config files: ${error?.message ?? 'Unknown error'}`));
157
158
  void handle();
158
159
  };
159
- #watch() {
160
+ async #watch() {
160
161
  if (this.#isWatching)
161
162
  throw new Error('Already watching');
162
163
  const { log } = this.#projectInfo;
163
164
  log.debug(`Starting segments and modules watcher. Detected initial segments: ${JSON.stringify(this.#segments.map((s) => s.segmentName))}.`);
165
+ await ensureClient(this.#projectInfo);
164
166
  // automatically watches segments and modules
165
167
  this.#watchConfig();
166
168
  }
@@ -1,3 +1,3 @@
1
- import { ProjectInfo } from '../getProjectInfo/index.mjs';
2
- import { DiffResult } from './diffSchema.mjs';
1
+ import type { DiffResult } from './diffSchema.mjs';
2
+ import type { ProjectInfo } from '../getProjectInfo/index.mjs';
3
3
  export default function logDiffResult(segmentName: string, diffResult: DiffResult, projectInfo: ProjectInfo): void;
@@ -1,3 +1,4 @@
1
+ import chalk from 'chalk';
1
2
  import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
2
3
  import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
3
4
  export default function logDiffResult(segmentName, diffResult, projectInfo) {
@@ -37,48 +38,51 @@ export default function logDiffResult(segmentName, diffResult, projectInfo) {
37
38
  });
38
39
  });
39
40
  const LIMIT = diffNormalized.length < 12 ? diffNormalized.length : 10;
41
+ const addedText = chalk.green('added');
42
+ const removedText = chalk.red('removed');
43
+ const changedText = chalk.cyan('changed');
40
44
  for (const diffNormalizedItem of diffNormalized.slice(0, LIMIT)) {
41
45
  switch (diffNormalizedItem.what) {
42
46
  case 'worker':
43
47
  switch (diffNormalizedItem.type) {
44
48
  case 'added':
45
- projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been added at ${formatLoggedSegmentName(segmentName)}`);
49
+ projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
46
50
  break;
47
51
  case 'removed':
48
- projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been removed from ${formatLoggedSegmentName(segmentName)}`);
52
+ projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
49
53
  break;
50
54
  }
51
55
  break;
52
56
  case 'controller':
53
57
  switch (diffNormalizedItem.type) {
54
58
  case 'added':
55
- projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been added at ${formatLoggedSegmentName(segmentName)}`);
59
+ projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
56
60
  break;
57
61
  case 'removed':
58
- projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been removed from ${formatLoggedSegmentName(segmentName)}`);
62
+ projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
59
63
  break;
60
64
  }
61
65
  break;
62
66
  case 'workerHandler':
63
67
  switch (diffNormalizedItem.type) {
64
68
  case 'added':
65
- projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been added at ${formatLoggedSegmentName(segmentName)}`);
69
+ projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
66
70
  break;
67
71
  case 'removed':
68
- projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been removed from ${formatLoggedSegmentName(segmentName)}`);
72
+ projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
69
73
  break;
70
74
  }
71
75
  break;
72
76
  case 'controllerHandler':
73
77
  switch (diffNormalizedItem.type) {
74
78
  case 'added':
75
- projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been added at ${formatLoggedSegmentName(segmentName)}`);
79
+ projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
76
80
  break;
77
81
  case 'removed':
78
- projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been removed from ${formatLoggedSegmentName(segmentName)}`);
82
+ projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
79
83
  break;
80
84
  case 'changed':
81
- projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been changed at ${formatLoggedSegmentName(segmentName)}`);
85
+ projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${changedText} at ${formatLoggedSegmentName(segmentName)}`);
82
86
  break;
83
87
  }
84
88
  break;
@@ -1,5 +1,5 @@
1
1
  import type { VovkSchema } from 'vovk';
2
- import { DiffResult } from './diffSchema.mjs';
2
+ import { type DiffResult } from './diffSchema.mjs';
3
3
  export declare const ROOT_SEGMENT_SCHEMA_NAME = "_root";
4
4
  export default function writeOneSchemaFile({ schemaOutAbsolutePath, schema, skipIfExists, }: {
5
5
  schemaOutAbsolutePath: string;
@@ -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 diffSchema from './diffSchema.mjs';
4
4
  import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
5
5
  export const ROOT_SEGMENT_SCHEMA_NAME = '_root';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vovk-cli",
3
- "version": "0.0.1-draft.12",
3
+ "version": "0.0.1-draft.13",
4
4
  "bin": {
5
5
  "vovk": "./dist/index.mjs"
6
6
  },
@@ -14,6 +14,7 @@
14
14
  "pre-test": "npm run build && chmod +x ./dist/index.mjs && npm run build-test",
15
15
  "test-only": "npm run pre-test && node --test --test-only test_dist/test/**/*.mjs",
16
16
  "test": "npm run pre-test && node --test --test-concurrency=1 test_dist/test/**/*.mjs",
17
+ "tsc": "tsc --noEmit",
17
18
  "ncu": "npm-check-updates -u",
18
19
  "npm-publish": "npm publish"
19
20
  },
@@ -34,7 +35,7 @@
34
35
  },
35
36
  "homepage": "https://vovk.dev",
36
37
  "peerDependencies": {
37
- "vovk": "^3.0.0-draft.12"
38
+ "vovk": "^3.0.0-draft.13"
38
39
  },
39
40
  "dependencies": {
40
41
  "@inquirer/prompts": "^7.0.1",
@@ -63,7 +64,7 @@
63
64
  "@types/pluralize": "^0.0.33",
64
65
  "@types/tar-stream": "^3.1.3",
65
66
  "concat-stream": "^2.0.0",
66
- "create-next-app": "^15.0.1",
67
+ "create-next-app": "^15.0.2",
67
68
  "node-pty": "^1.0.0",
68
69
  "type-fest": "^4.26.1"
69
70
  }
@@ -18,12 +18,12 @@ import <%= serviceName %> from './<%= serviceName %>';
18
18
  @prefix('<%= _.kebabCase(moduleName).toLowerCase() %>')
19
19
  export default class <%= controllerName %> {
20
20
  @get()
21
- static get<%= modulePascalNamePlural %> = async (req: VovkRequest<null, { q: string }>) => {
22
- const q = req.nextUrl.searchParams.get('q');
21
+ static get<%= modulePascalNamePlural %> = async (req: VovkRequest<null, { search: string }>) => {
22
+ const search = req.nextUrl.searchParams.get('search');
23
23
  <% if(withService) { %>
24
- return <%= serviceName %>.get<%= modulePascalNamePlural %>(q);
24
+ return <%= serviceName %>.get<%= modulePascalNamePlural %>(search);
25
25
  <% } else { %>
26
- return { q };
26
+ return { results: [], search };
27
27
  <% } %>
28
28
  }
29
29
 
@@ -33,7 +33,7 @@ export default class <%= controllerName %> {
33
33
  const body = await req.json();
34
34
  const q = req.nextUrl.searchParams.get('q');
35
35
  <% if(withService) { %>
36
- return MyThingService.update<%= modulePascalName %>(id, q, body);
36
+ return <%= serviceName %>.update<%= modulePascalName %>(id, q, body);
37
37
  <% } else { %>
38
38
  return { id, body, q };
39
39
  <% } %>
@@ -11,14 +11,14 @@ import type { VovkControllerBody, VovkControllerQuery } from 'vovk';
11
11
  import type <%= controllerName %> from './<%= controllerName %>';
12
12
 
13
13
  export default class <%= serviceName %> {
14
- static get<%= modulePascalNamePlural %> = (q: VovkControllerQuery<typeof <%= controllerName %>.get<%= modulePascalNamePlural %>>['q']) => {
15
- return [];
14
+ static get<%= modulePascalNamePlural %> = (search: VovkControllerQuery<typeof <%= controllerName %>.get<%= modulePascalNamePlural %>>['search']) => {
15
+ return { results: [], search };
16
16
  };
17
17
 
18
18
  static update<%= modulePascalName %> = (
19
19
  id: string,
20
- q: VovkControllerQuery<typeof <%= controllerName %>.get<%= modulePascalName %>>['q'],
21
- body: VovkControllerBody<typeof <%= controllerName %>.get<%= modulePascalName %>>
20
+ q: VovkControllerQuery<typeof <%= controllerName %>.update<%= modulePascalName %>>['q'],
21
+ body: VovkControllerBody<typeof <%= controllerName %>.update<%= modulePascalName %>>
22
22
  ) => {
23
23
  return { id, q, body };
24
24
  };