vovk-cli 0.0.1-draft.202 → 0.0.1-draft.203

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.
@@ -1,4 +1,8 @@
1
1
  <%- `// auto-generated by Vovk.ts ${new Date().toISOString()}` %>
2
+ <% if(t.isTsStandalone) { %>
3
+ const schema = require('./schema.json');
4
+ module.exports.schema = schema;
5
+ <% } else { %>
2
6
  const meta = require('./<%= t.schemaOutDir %>/_meta.json');
3
7
  const segments = {<% Object.values(t.schema.segments).filter((segment) => segment.emitSchema).forEach((segment) => { %>
4
8
  '<%= segment.segmentName %>': require('./<%= t.schemaOutDir %>/<%= segment.segmentName || t.ROOT_SEGMENT_FILE_NAME %>.json'),<% }) %>
@@ -7,9 +11,10 @@ const schema = {
7
11
  $schema: '<%- t.VovkSchemaIdEnum.SCHEMA %>',
8
12
  segments,
9
13
  meta: {
10
- apiRoot: '<%= t.apiRoot %>',
14
+ apiRoot: '<%= t.apiRoot %>', // for debugging purposes
11
15
  ...meta
12
16
  }
13
17
  };
14
18
 
15
19
  module.exports.schema = schema;
20
+ <% } %>
@@ -1,4 +1,8 @@
1
1
  <%- `// auto-generated by Vovk.ts ${new Date().toISOString()}` %>
2
+ <% if(t.isTsStandalone) { %>
3
+ import schema from './schema.json' with { type: "json" };
4
+ export { schema };
5
+ <% } else { %>
2
6
  import meta from './<%= t.schemaOutDir %>/_meta.json' with { type: "json" };
3
7
  <% Object.values(t.schema.segments).filter((segment) => segment.emitSchema).forEach((segment, i) => { %>
4
8
  import segment<%= i %> from './<%= t.schemaOutDir %>/<%= segment.segmentName || t.ROOT_SEGMENT_FILE_NAME %>.json' with { type: "json" };
@@ -11,7 +15,8 @@ export const schema = {
11
15
  $schema: '<%- t.VovkSchemaIdEnum.SCHEMA %>',
12
16
  segments,
13
17
  meta: {
14
- apiRoot: '<%= t.apiRoot %>',
18
+ apiRoot: '<%= t.apiRoot %>', // for debugging purposes
15
19
  ...meta,
16
20
  }
17
- };
21
+ };
22
+ <% } %>
@@ -0,0 +1,12 @@
1
+ import type { VovkRequest, VovkStreamAsyncIterable, KnownAny } from 'vovk';
2
+ <% Object.entries(t.schema.segments).forEach(([segmentName, segment], i) => {
3
+ Object.values(segment.controllers).forEach((controllerSchema) => { %>
4
+
5
+ export type Segment<%= i %><%= controllerSchema.originalControllerName ?? controllerSchema.rpcModuleName + 'Controller' %> = {
6
+ <% Object.entries(controllerSchema.handlers).forEach(([handlerName, handler]) => { %>
7
+ <%= handlerName %>: (req: VovkRequest<<%- t.convertJSONSchemaToTypeScriptDef(handler.validation?.body) ?? 'KnownAny' %>, <%- t.convertJSONSchemaToTypeScriptDef(handler.validation?.query) ?? 'KnownAny' %>, <%- t.convertJSONSchemaToTypeScriptDef(handler.validation?.params ?? 'KnownAny') %>>): <%= validation?.output ? `Promise<${t.convertJSONSchemaToTypeScriptDef(handler.validation?.body)}>` : validation?.iteration ? `Promise<VovkStreamAsyncIterable<${t.convertJSONSchemaToTypeScriptDef(handler.validation?.iteration)}>>` : 'Promise<KnownAny>' %>:
8
+ <% }) %>
9
+ };
10
+ <% }) %>
11
+ <% }) %>
12
+
@@ -7,8 +7,8 @@ import { BuiltInTemplateName } from '../getProjectInfo/getConfig/getTemplateDefs
7
7
  import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
8
8
  import { locateSegments } from '../locateSegments.mjs';
9
9
  export async function bundle({ projectInfo, fullSchema, cliBundleOptions, }) {
10
- const { config, log, cwd, apiDir } = projectInfo;
11
- const locatedSegments = await locateSegments({ dir: path.join(cwd, apiDir), config, log });
10
+ const { config, log, cwd, apiDirAbsolutePath } = projectInfo;
11
+ const locatedSegments = await locateSegments({ dir: apiDirAbsolutePath, config, log });
12
12
  const { bundle: bundleConfig } = config;
13
13
  const tsFullClientOutAbsoluteDirInput = path.join(cwd, bundleConfig.tsClientOutDir);
14
14
  const tsClientOutDir = cliBundleOptions?.tsClientOutDir ?? bundleConfig.tsClientOutDir;
@@ -41,9 +41,11 @@ export class VovkDev {
41
41
  }
42
42
  #watchSegments = (callback) => {
43
43
  const segmentReg = /\/?\[\[\.\.\.[a-zA-Z-_]+\]\]\/route.ts$/;
44
- const { cwd, log, config, apiDir } = this.#projectInfo;
44
+ const { cwd, log, config, apiDirAbsolutePath } = this.#projectInfo;
45
+ if (!apiDirAbsolutePath) {
46
+ throw new Error('Unable to watch segments. It looks like CWD is not a Next.js app.');
47
+ }
45
48
  const schemaOutAbsolutePath = path.resolve(cwd, this.#schemaOut ?? config.schemaOutDir);
46
- const apiDirAbsolutePath = path.join(cwd, apiDir);
47
49
  const getSegmentName = (filePath) => path.relative(apiDirAbsolutePath, filePath).replace(segmentReg, '');
48
50
  log.debug(`Watching segments at ${apiDirAbsolutePath}`);
49
51
  this.#segmentWatcher = chokidar
@@ -161,8 +163,8 @@ export class VovkDev {
161
163
  let isReady = false;
162
164
  const handle = debounce(async () => {
163
165
  this.#projectInfo = await getProjectInfo();
164
- const { config, apiDir } = this.#projectInfo;
165
- this.#segments = await locateSegments({ dir: path.join(cwd, apiDir), config, log });
166
+ const { config, apiDirAbsolutePath } = this.#projectInfo;
167
+ this.#segments = await locateSegments({ dir: apiDirAbsolutePath, config, log });
166
168
  await this.#modulesWatcher?.close();
167
169
  await this.#segmentWatcher?.close();
168
170
  await Promise.all([
@@ -316,8 +318,8 @@ export class VovkDev {
316
318
  async start({ exit }) {
317
319
  const now = Date.now();
318
320
  this.#projectInfo = await getProjectInfo();
319
- const { log, config, cwd, apiDir } = this.#projectInfo;
320
- this.#segments = await locateSegments({ dir: path.join(cwd, apiDir), config, log });
321
+ const { log, config, cwd, apiDirAbsolutePath } = this.#projectInfo;
322
+ this.#segments = await locateSegments({ dir: apiDirAbsolutePath, config, log });
321
323
  log.info('Starting...');
322
324
  if (exit) {
323
325
  this.#onFirstTimeGenerate = once(() => {
@@ -56,8 +56,9 @@ export async function generate({ isEnsuringClient = false, projectInfo, forceNot
56
56
  // sort segments by name to avoid unnecessary rendering
57
57
  segments: Object.fromEntries(Object.entries(fullSchema.segments).sort(([a], [b]) => a.localeCompare(b))),
58
58
  };
59
- const { config, cwd, log } = projectInfo;
59
+ const { config, cwd, log, srcRoot } = projectInfo;
60
60
  const isNodeNextResolution = ['node16', 'nodenext'].includes((await getTsconfig(cwd)?.config?.compilerOptions?.moduleResolution?.toLowerCase()) ?? '');
61
+ const isTsStandalone = cliGenerateOptions?.forceTsStandalone ?? !srcRoot;
61
62
  const isComposedEnabled = cliGenerateOptions?.composedOnly ||
62
63
  !!cliGenerateOptions?.composedFrom ||
63
64
  !!cliGenerateOptions?.composedOut ||
@@ -110,6 +111,7 @@ export async function generate({ isEnsuringClient = false, projectInfo, forceNot
110
111
  templateDef,
111
112
  locatedSegments,
112
113
  isNodeNextResolution,
114
+ isTsStandalone,
113
115
  });
114
116
  const outAbsoluteDir = path.join(cwd, outCwdRelativeDir);
115
117
  return {
@@ -181,6 +183,7 @@ export async function generate({ isEnsuringClient = false, projectInfo, forceNot
181
183
  templateDef,
182
184
  locatedSegments,
183
185
  isNodeNextResolution,
186
+ isTsStandalone,
184
187
  });
185
188
  return {
186
189
  written,
@@ -30,8 +30,8 @@ export class VovkGenerate {
30
30
  }
31
31
  }
32
32
  async generate({ fullSchema }) {
33
- const { log, config, apiDir, cwd } = this.#projectInfo;
34
- const locatedSegments = await locateSegments({ dir: path.join(cwd, apiDir), config, log });
33
+ const { log, config, apiDirAbsolutePath } = this.#projectInfo;
34
+ const locatedSegments = await locateSegments({ dir: apiDirAbsolutePath, config, log });
35
35
  await generate({
36
36
  projectInfo: this.#projectInfo,
37
37
  fullSchema,
@@ -4,7 +4,7 @@ import type { ProjectInfo } from '../getProjectInfo/index.mjs';
4
4
  import type { ClientTemplateFile } from './getClientTemplateFiles.mjs';
5
5
  import type { ClientImports } from './getTemplateClientImports.mjs';
6
6
  import type { Segment } from '../locateSegments.mjs';
7
- export default function writeOneClientFile({ cwd, projectInfo, clientTemplateFile, fullSchema, prettifyClient, segmentName, imports, templateContent, matterResult: { data, content }, package: packageJson, isEnsuringClient, outCwdRelativeDir, origin, templateDef, locatedSegments, isNodeNextResolution, }: {
7
+ export default function writeOneClientFile({ cwd, projectInfo, clientTemplateFile, fullSchema, prettifyClient, segmentName, imports, templateContent, matterResult: { data, content }, package: packageJson, isEnsuringClient, outCwdRelativeDir, origin, templateDef, locatedSegments, isNodeNextResolution, isTsStandalone, }: {
8
8
  cwd: string;
9
9
  projectInfo: ProjectInfo;
10
10
  clientTemplateFile: ClientTemplateFile;
@@ -26,6 +26,7 @@ export default function writeOneClientFile({ cwd, projectInfo, clientTemplateFil
26
26
  templateDef: VovkStrictConfig['clientTemplateDefs'][string];
27
27
  locatedSegments: Segment[];
28
28
  isNodeNextResolution: boolean;
29
+ isTsStandalone: boolean;
29
30
  }): Promise<{
30
31
  written: boolean;
31
32
  }>;
@@ -7,7 +7,8 @@ import * as YAML from 'yaml';
7
7
  import TOML from '@iarna/toml';
8
8
  import prettify from '../utils/prettify.mjs';
9
9
  import { ROOT_SEGMENT_FILE_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
10
- export default async function writeOneClientFile({ cwd, projectInfo, clientTemplateFile, fullSchema, prettifyClient, segmentName, imports, templateContent, matterResult: { data, content }, package: packageJson, isEnsuringClient, outCwdRelativeDir, origin, templateDef, locatedSegments, isNodeNextResolution, }) {
10
+ import { convertJSONSchemaToTypeScriptDef } from '../utils/convertJSONSchemaToTypeScriptDef.mjs';
11
+ export default async function writeOneClientFile({ cwd, projectInfo, clientTemplateFile, fullSchema, prettifyClient, segmentName, imports, templateContent, matterResult: { data, content }, package: packageJson, isEnsuringClient, outCwdRelativeDir, origin, templateDef, locatedSegments, isNodeNextResolution, isTsStandalone, }) {
11
12
  const { config, apiRoot } = projectInfo;
12
13
  const { templateFilePath, relativeDir } = clientTemplateFile;
13
14
  const locatedSegmentsByName = _.keyBy(locatedSegments, 'segmentName');
@@ -19,6 +20,7 @@ export default async function writeOneClientFile({ cwd, projectInfo, clientTempl
19
20
  // Data for the EJS templates:
20
21
  const t = {
21
22
  _, // lodash
23
+ isTsStandalone,
22
24
  package: packageJson,
23
25
  ROOT_SEGMENT_FILE_NAME,
24
26
  apiRoot: origin ? `${origin}/${config.rootEntry}` : apiRoot,
@@ -26,6 +28,7 @@ export default async function writeOneClientFile({ cwd, projectInfo, clientTempl
26
28
  schema: fullSchema,
27
29
  VovkSchemaIdEnum,
28
30
  createCodeExamples,
31
+ convertJSONSchemaToTypeScriptDef,
29
32
  YAML,
30
33
  TOML,
31
34
  nodeNextResolutionExt: {
@@ -6,6 +6,7 @@ export declare enum BuiltInTemplateName {
6
6
  schemaTs = "schemaTs",
7
7
  schemaCjs = "schemaCjs",
8
8
  schemaJson = "schemaJson",
9
+ standaloneTypesTs = "standaloneTypesTs",
9
10
  readme = "readme",
10
11
  packageJson = "packageJson",
11
12
  rs = "rs",
@@ -8,6 +8,8 @@ export var BuiltInTemplateName;
8
8
  BuiltInTemplateName["schemaTs"] = "schemaTs";
9
9
  BuiltInTemplateName["schemaCjs"] = "schemaCjs";
10
10
  BuiltInTemplateName["schemaJson"] = "schemaJson";
11
+ // types
12
+ BuiltInTemplateName["standaloneTypesTs"] = "standaloneTypesTs";
11
13
  // misc
12
14
  BuiltInTemplateName["readme"] = "readme";
13
15
  BuiltInTemplateName["packageJson"] = "packageJson";
@@ -30,12 +32,6 @@ export default function getTemplateDefs(userTemplateDefs = {}) {
30
32
  templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.mjs}/`,
31
33
  requires: { [BuiltInTemplateName.schemaCjs]: '.' },
32
34
  },
33
- [BuiltInTemplateName.readme]: {
34
- templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.readme}/`,
35
- },
36
- [BuiltInTemplateName.packageJson]: {
37
- templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.packageJson}/`,
38
- },
39
35
  [BuiltInTemplateName.schemaTs]: {
40
36
  templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.schemaTs}/`,
41
37
  },
@@ -45,13 +41,23 @@ export default function getTemplateDefs(userTemplateDefs = {}) {
45
41
  [BuiltInTemplateName.schemaJson]: {
46
42
  templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.schemaJson}/`,
47
43
  },
44
+ [BuiltInTemplateName.standaloneTypesTs]: {
45
+ templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.standaloneTypesTs}/`,
46
+ requires: { [BuiltInTemplateName.schemaJson]: '.' },
47
+ },
48
+ [BuiltInTemplateName.readme]: {
49
+ templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.readme}/`,
50
+ },
51
+ [BuiltInTemplateName.packageJson]: {
52
+ templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.packageJson}/`,
53
+ },
48
54
  [BuiltInTemplateName.rs]: {
49
55
  templatePath: 'vovk-rust-client/template/',
50
56
  composedClient: {
51
57
  outDir: 'dist_rust',
52
58
  },
53
59
  requires: {
54
- schemaJson: './data',
60
+ [BuiltInTemplateName.schemaJson]: './data',
55
61
  },
56
62
  },
57
63
  [BuiltInTemplateName.py]: {
@@ -60,7 +66,7 @@ export default function getTemplateDefs(userTemplateDefs = {}) {
60
66
  outDir: 'dist_python',
61
67
  },
62
68
  requires: {
63
- schemaJson: './data',
69
+ [BuiltInTemplateName.schemaJson]: './data',
64
70
  },
65
71
  },
66
72
  };
@@ -8,7 +8,7 @@ export default function getProjectInfo({ port: givenPort, cwd, configPath, srcRo
8
8
  cwd: string;
9
9
  port: string;
10
10
  apiRoot: string;
11
- apiDir: string;
11
+ apiDirAbsolutePath: string | null;
12
12
  srcRoot: string | null;
13
13
  config: import("vovk").VovkStrictConfig;
14
14
  log: {
@@ -12,7 +12,7 @@ export default async function getProjectInfo({ port: givenPort, cwd = process.cw
12
12
  throw new Error(`Could not find app router directory at ${cwd}. Check Next.js docs for more info.`);
13
13
  }
14
14
  const apiRoot = `${config.origin ?? ''}/${config.rootEntry}`;
15
- const apiDir = path.join(srcRoot ?? '.', 'app', config.rootEntry);
15
+ const apiDirAbsolutePath = srcRoot ? path.resolve(cwd, srcRoot, 'app', config.rootEntry) : null;
16
16
  if (configAbsolutePaths.length > 1) {
17
17
  log.warn(`Multiple config files found. Using the first one: ${configAbsolutePaths[0]}`);
18
18
  }
@@ -20,7 +20,7 @@ export default async function getProjectInfo({ port: givenPort, cwd = process.cw
20
20
  cwd,
21
21
  port,
22
22
  apiRoot,
23
- apiDir,
23
+ apiDirAbsolutePath,
24
24
  srcRoot,
25
25
  config,
26
26
  log,
package/dist/index.mjs CHANGED
@@ -88,6 +88,7 @@ program
88
88
  .option('--prettify', 'prettify output files')
89
89
  .option('--schema, --schema-path <path>', 'path to schema folder (default: ./.vovk-schema)')
90
90
  .option('--config, --config-path <config>', 'path to config file')
91
+ .option('--force-ts-standalone', 'force TypeScript standalone mode (Next.js environment will be ignored, by default it\'s "true" for non-Next.js directories)')
91
92
  .option('--watch <s>', 'watch for changes in schema or openapi spec and regenerate client; accepts a number in seconds to throttle the watcher or make an HTTP request to the OpenAPI spec URL')
92
93
  .option('--openapi, --openapi-spec <openapi_path_or_url>', 'use OpenAPI schema instead of Vovk schema')
93
94
  .action(async (cliGenerateOptions) => {
@@ -5,7 +5,7 @@ export type Segment = {
5
5
  segmentName: string;
6
6
  };
7
7
  export declare function locateSegments({ dir, rootDir, config, log, }: {
8
- dir: string;
8
+ dir: string | null;
9
9
  rootDir?: string;
10
10
  config: VovkStrictConfig | null;
11
11
  log: ProjectInfo['log'];
@@ -3,6 +3,8 @@ import path from 'node:path';
3
3
  import getFileSystemEntryType from './utils/getFileSystemEntryType.mjs';
4
4
  export async function locateSegments({ dir, rootDir, config, log, }) {
5
5
  let results = [];
6
+ if (!dir)
7
+ return results; // If dir is null, return empty results because this isn't a Next.js app
6
8
  rootDir = rootDir ?? dir;
7
9
  let list = [];
8
10
  // Read the contents of the directory
@@ -21,8 +21,8 @@ function splitByLast(str, delimiter = '/') {
21
21
  return [before, after];
22
22
  }
23
23
  export default async function newModule({ what, moduleNameWithOptionalSegment, dryRun, dir: dirFlag, templates: templatesFlag, noSegmentUpdate, overwrite, empty, }) {
24
- const { config, log, cwd, apiDir } = await getProjectInfo();
25
- const segments = await locateSegments({ dir: path.join(cwd, apiDir), config, log });
24
+ const { config, log, cwd, apiDirAbsolutePath } = await getProjectInfo();
25
+ const segments = await locateSegments({ dir: apiDirAbsolutePath, config, log });
26
26
  const isNodeNextResolution = ['node16', 'nodenext'].includes((await getTsconfig(cwd)?.config?.compilerOptions?.moduleResolution?.toLowerCase()) ?? '');
27
27
  let templates = config.moduleTemplates;
28
28
  const [segmentName, moduleName] = splitByLast(moduleNameWithOptionalSegment);
@@ -7,8 +7,11 @@ import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
7
7
  import prettify from '../utils/prettify.mjs';
8
8
  import chalk from 'chalk';
9
9
  export default async function newSegment({ segmentName, isStaticSegment, overwrite, dryRun, }) {
10
- const { apiDir, cwd, log, config } = await getProjectInfo();
11
- const absoluteSegmentRoutePath = path.join(cwd, apiDir, segmentName, '[[...vovk]]/route.ts');
10
+ const { apiDirAbsolutePath, log, config } = await getProjectInfo();
11
+ if (!apiDirAbsolutePath) {
12
+ throw new Error('No API directory found. Please ensure you are in a Nest.js project.');
13
+ }
14
+ const absoluteSegmentRoutePath = path.join(apiDirAbsolutePath, segmentName, '[[...vovk]]/route.ts');
12
15
  if (!overwrite && (await getFileSystemEntryType(absoluteSegmentRoutePath))) {
13
16
  throw new Error(`Unable to create new segment. ${formatLoggedSegmentName(segmentName, { upperFirst: true })} already exists.`);
14
17
  }
package/dist/types.d.mts CHANGED
@@ -18,6 +18,7 @@ export interface GenerateOptions {
18
18
  schemaPath?: string;
19
19
  openapiSpec?: string;
20
20
  watch?: boolean | string;
21
+ forceTsStandalone?: boolean;
21
22
  composedFrom?: string[];
22
23
  composedOut?: string;
23
24
  composedOnly?: boolean;
@@ -0,0 +1,2 @@
1
+ import { JSONSchema7 } from 'json-schema';
2
+ export declare function convertJSONSchemaToTypeScriptDef(schema: JSONSchema7): string | null;
@@ -0,0 +1,150 @@
1
+ export function convertJSONSchemaToTypeScriptDef(schema) {
2
+ if (!schema)
3
+ return null;
4
+ // Helper function to get JSDoc from schema
5
+ const getJSDoc = (schema, indentation = '') => {
6
+ if (typeof schema === 'boolean') {
7
+ return `${indentation}/**\n${indentation} * none\n${indentation} */`;
8
+ }
9
+ const description = schema.description || schema.title || 'none';
10
+ return `${indentation}/**\n${indentation} * ${description}\n${indentation} */`;
11
+ };
12
+ // Helper function to convert schema to TypeScript type
13
+ const schemaToType = (schema, indentation = ' ') => {
14
+ if (typeof schema === 'boolean') {
15
+ return schema ? 'KnownAny' : 'never';
16
+ }
17
+ if (schema.enum) {
18
+ return schema.enum
19
+ .map((value) => {
20
+ if (typeof value === 'string') {
21
+ return `'${value}'`;
22
+ }
23
+ else if (value === null) {
24
+ return 'null';
25
+ }
26
+ else {
27
+ return String(value);
28
+ }
29
+ })
30
+ .join(' | ');
31
+ }
32
+ if (schema.const !== undefined) {
33
+ if (typeof schema.const === 'string') {
34
+ return `'${schema.const}'`;
35
+ }
36
+ else if (schema.const === null) {
37
+ return 'null';
38
+ }
39
+ else {
40
+ return String(schema.const);
41
+ }
42
+ }
43
+ if (schema.oneOf) {
44
+ return schema.oneOf.map((s) => schemaToType(s, indentation)).join(' | ');
45
+ }
46
+ if (schema.anyOf) {
47
+ return schema.anyOf.map((s) => schemaToType(s, indentation)).join(' | ');
48
+ }
49
+ if (schema.allOf) {
50
+ return schema.allOf.map((s) => schemaToType(s, indentation)).join(' & ');
51
+ }
52
+ if (schema.type === 'object' || schema.properties) {
53
+ const properties = schema.properties || {};
54
+ const required = schema.required || [];
55
+ const propertyEntries = Object.entries(properties);
56
+ if (propertyEntries.length === 0) {
57
+ // Handle additional properties
58
+ if (schema.additionalProperties) {
59
+ if (typeof schema.additionalProperties === 'boolean') {
60
+ return schema.additionalProperties ? 'Record<string, KnownAny>' : '{}';
61
+ }
62
+ else {
63
+ const valueType = schemaToType(schema.additionalProperties, indentation);
64
+ return `Record<string, ${valueType}>`;
65
+ }
66
+ }
67
+ return '{}';
68
+ }
69
+ const props = propertyEntries
70
+ .map(([propName, propSchema]) => {
71
+ if (typeof propSchema === 'boolean') {
72
+ const type = propSchema ? 'KnownAny' : 'never';
73
+ const isOptional = !required.includes(propName);
74
+ const jsDoc = getJSDoc(propSchema, indentation);
75
+ return `${jsDoc}\n${indentation}${propName}${isOptional ? '?' : ''}: ${type};`;
76
+ }
77
+ const isOptional = !required.includes(propName);
78
+ const defaultValue = propSchema.default !== undefined ? ` // default: ${JSON.stringify(propSchema.default)}` : '';
79
+ const jsDoc = getJSDoc(propSchema, indentation);
80
+ const propType = schemaToType(propSchema, indentation + ' ');
81
+ return `${jsDoc}\n${indentation}${propName}${isOptional ? '?' : ''}: ${propType};${defaultValue}`;
82
+ })
83
+ .join('\n');
84
+ // Handle additional properties
85
+ let additionalPropsType = '';
86
+ if (schema.additionalProperties) {
87
+ if (typeof schema.additionalProperties === 'boolean') {
88
+ if (schema.additionalProperties) {
89
+ additionalPropsType = `\n${indentation}[key: string]: KnownAny;`;
90
+ }
91
+ }
92
+ else {
93
+ const valueType = schemaToType(schema.additionalProperties, indentation + ' ');
94
+ additionalPropsType = `\n${indentation}[key: string]: ${valueType};`;
95
+ }
96
+ }
97
+ return `{\n${props}${additionalPropsType}\n${indentation.slice(2)}}`;
98
+ }
99
+ if (schema.type === 'array' && schema.items) {
100
+ if (Array.isArray(schema.items)) {
101
+ // Tuple
102
+ const tupleTypes = schema.items.map((item) => schemaToType(item, indentation));
103
+ let tupleType = `[${tupleTypes.join(', ')}]`;
104
+ // Handle additional items
105
+ if (schema.additionalItems === true) {
106
+ tupleType += ' & KnownAny[]';
107
+ }
108
+ else if (typeof schema.additionalItems === 'object') {
109
+ const additionalType = schemaToType(schema.additionalItems, indentation);
110
+ tupleType += ` & ${additionalType}[]`;
111
+ }
112
+ return tupleType;
113
+ }
114
+ else {
115
+ // Array
116
+ return `${schemaToType(schema.items, indentation)}[]`;
117
+ }
118
+ }
119
+ // Handle multiple types
120
+ if (Array.isArray(schema.type)) {
121
+ return schema.type
122
+ .map((t) => {
123
+ const singleTypeSchema = { ...schema, type: t };
124
+ singleTypeSchema.type = t;
125
+ return schemaToType(singleTypeSchema, indentation);
126
+ })
127
+ .join(' | ');
128
+ }
129
+ // Handle primitive types
130
+ switch (schema.type) {
131
+ case 'string':
132
+ return 'string';
133
+ case 'number':
134
+ case 'integer':
135
+ return 'number';
136
+ case 'boolean':
137
+ return 'boolean';
138
+ case 'null':
139
+ return 'null';
140
+ case 'array':
141
+ return 'KnownAny[]'; // For arrays with no items defined
142
+ default:
143
+ return 'KnownAny';
144
+ }
145
+ };
146
+ // Generate the interface
147
+ const jsDoc = getJSDoc(schema);
148
+ const interfaceBody = schemaToType(schema);
149
+ return `${jsDoc}\n${interfaceBody}`;
150
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vovk-cli",
3
- "version": "0.0.1-draft.202",
3
+ "version": "0.0.1-draft.203",
4
4
  "bin": {
5
5
  "vovk": "./dist/index.mjs"
6
6
  },
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "homepage": "https://vovk.dev",
37
37
  "peerDependencies": {
38
- "vovk": "^3.0.0-draft.180"
38
+ "vovk": "^3.0.0-draft.181"
39
39
  },
40
40
  "optionalDependencies": {
41
41
  "vovk-python-client": "^0.0.1-draft.37"
@@ -48,6 +48,7 @@
48
48
  "@rollup/plugin-json": "^6.1.0",
49
49
  "@rollup/plugin-node-resolve": "^16.0.1",
50
50
  "@rollup/plugin-typescript": "^12.1.2",
51
+ "@types/json-schema": "^7.0.15",
51
52
  "chalk": "^5.4.1",
52
53
  "chokidar": "^4.0.3",
53
54
  "commander": "^13.1.0",