vovk-cli 0.0.1-draft.136 → 0.0.1-draft.138

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.
@@ -66,14 +66,22 @@ export class VovkDev {
66
66
  })
67
67
  .on('addDir', async (dirPath) => {
68
68
  log.debug(`Directory ${dirPath} has been added to segments folder`);
69
- this.#projectInfo.segments = await locateSegments({ dir: apiDirAbsolutePath, config });
69
+ this.#projectInfo.segments = await locateSegments({
70
+ dir: apiDirAbsolutePath,
71
+ config,
72
+ log: this.#projectInfo.log,
73
+ });
70
74
  for (const { segmentName } of this.#projectInfo.segments) {
71
75
  void this.#requestSchema(segmentName);
72
76
  }
73
77
  })
74
78
  .on('unlinkDir', async (dirPath) => {
75
79
  log.debug(`Directory ${dirPath} has been removed from segments folder`);
76
- this.#projectInfo.segments = await locateSegments({ dir: apiDirAbsolutePath, config });
80
+ this.#projectInfo.segments = await locateSegments({
81
+ dir: apiDirAbsolutePath,
82
+ config,
83
+ log: this.#projectInfo.log,
84
+ });
77
85
  for (const { segmentName } of this.#projectInfo.segments) {
78
86
  void this.#requestSchema(segmentName);
79
87
  }
@@ -27,6 +27,7 @@ export default async function ensureClient({ config, cwd, log, segments }) {
27
27
  config,
28
28
  cwd,
29
29
  generateFrom: config.generateFrom,
30
+ log,
30
31
  });
31
32
  const usedTemplateNames = new Set();
32
33
  const defaultText = `// auto-generated ${new Date().toISOString()}
@@ -35,6 +36,8 @@ export default async function ensureClient({ config, cwd, log, segments }) {
35
36
  // Feel free to report an issue at https://github.com/finom/vovk/issues`;
36
37
  await Promise.all(templateFiles.map(async (clientTemplate) => {
37
38
  const { templatePath, templateName, outDir } = clientTemplate;
39
+ if (!templatePath)
40
+ return;
38
41
  const outPath = path.join(outDir, path.basename(templatePath).replace('.ejs', ''));
39
42
  if (config.emitFullClient) {
40
43
  await writeOnePlaceholder({
@@ -1,8 +1,9 @@
1
1
  import type { VovkStrictConfig } from 'vovk';
2
+ import type { ProjectInfo } from '../getProjectInfo/index.mjs';
2
3
  export declare const DEFAULT_FULL_SCHEMA_FILE_NAME = "full-schema.json";
3
4
  export interface ClientTemplate {
4
5
  templateName: string;
5
- templatePath: string;
6
+ templatePath: string | null;
6
7
  outDir: string;
7
8
  fullSchemaJSONFileName: string | null;
8
9
  origin?: string | null;
@@ -13,10 +14,11 @@ export declare enum BuiltInTemplateName {
13
14
  module = "module",
14
15
  fullSchema = "fullSchema"
15
16
  }
16
- export default function getClientTemplates({ config, cwd, generateFrom, }: {
17
+ export default function getClientTemplates({ config, cwd, generateFrom, log, }: {
17
18
  config: VovkStrictConfig;
18
19
  cwd: string;
19
20
  generateFrom?: VovkStrictConfig['generateFrom'];
21
+ log: ProjectInfo['log'];
20
22
  }): Promise<{
21
23
  clientOutDirAbsolutePath: string;
22
24
  templateFiles: ClientTemplate[];
@@ -9,55 +9,64 @@ export var BuiltInTemplateName;
9
9
  BuiltInTemplateName["module"] = "module";
10
10
  BuiltInTemplateName["fullSchema"] = "fullSchema";
11
11
  })(BuiltInTemplateName || (BuiltInTemplateName = {}));
12
- export default async function getClientTemplates({ config, cwd, generateFrom = [], }) {
12
+ export default async function getClientTemplates({ config, cwd, generateFrom = [], log, }) {
13
13
  const templatesDir = path.join(import.meta.dirname, '../..', 'client-templates');
14
14
  const clientOutDirAbsolutePath = path.resolve(cwd, config.clientOutDir);
15
15
  const builtIn = {
16
16
  ts: {
17
17
  templateName: BuiltInTemplateName.ts,
18
- templatePath: path.resolve(templatesDir, 'ts/*'),
18
+ templateGlob: path.resolve(templatesDir, 'ts/*'),
19
19
  outDir: clientOutDirAbsolutePath,
20
20
  fullSchemaJSON: false,
21
21
  origin: null,
22
22
  },
23
23
  main: {
24
24
  templateName: BuiltInTemplateName.main,
25
- templatePath: path.resolve(templatesDir, 'main/*'),
25
+ templateGlob: path.resolve(templatesDir, 'main/*'),
26
26
  outDir: clientOutDirAbsolutePath,
27
27
  fullSchemaJSON: false,
28
28
  origin: null,
29
29
  },
30
30
  module: {
31
31
  templateName: BuiltInTemplateName.module,
32
- templatePath: path.resolve(templatesDir, 'module/*'),
32
+ templateGlob: path.resolve(templatesDir, 'module/*'),
33
33
  outDir: clientOutDirAbsolutePath,
34
34
  fullSchemaJSON: false,
35
35
  origin: null,
36
36
  },
37
37
  fullSchema: {
38
38
  templateName: BuiltInTemplateName.fullSchema,
39
- templatePath: path.resolve(templatesDir, 'fullSchema/*'),
39
+ templateGlob: path.resolve(templatesDir, 'fullSchema/*'),
40
40
  outDir: clientOutDirAbsolutePath,
41
41
  fullSchemaJSON: false,
42
42
  origin: null,
43
43
  },
44
44
  };
45
45
  const generateFromStrict = generateFrom.map((template) => {
46
+ if (template === 'none') {
47
+ return {
48
+ templateName: template,
49
+ templateGlob: null,
50
+ outDir: clientOutDirAbsolutePath,
51
+ fullSchemaJSON: false,
52
+ origin: null,
53
+ };
54
+ }
46
55
  if (typeof template === 'string') {
47
56
  if (template in builtIn) {
48
57
  return builtIn[template];
49
58
  }
50
59
  return {
51
60
  templateName: template,
52
- templatePath: resolveAbsoluteModulePath(template, cwd),
61
+ templateGlob: resolveAbsoluteModulePath(template, cwd),
53
62
  outDir: clientOutDirAbsolutePath,
54
63
  fullSchemaJSON: false,
55
64
  origin: null,
56
65
  };
57
66
  }
58
67
  return {
59
- templateName: template.templateName ?? template.templatePath,
60
- templatePath: resolveAbsoluteModulePath(template.templatePath, cwd),
68
+ templateName: template.templateName ?? template.templateGlob ?? 'none',
69
+ templateGlob: template.templateGlob ? resolveAbsoluteModulePath(template.templateGlob, cwd) : null,
61
70
  outDir: template.outDir ? path.resolve(cwd, template.outDir) : clientOutDirAbsolutePath,
62
71
  fullSchemaJSON: template.fullSchemaJSON ?? false,
63
72
  origin: template.origin ?? null,
@@ -68,17 +77,34 @@ export default async function getClientTemplates({ config, cwd, generateFrom = [
68
77
  }
69
78
  const templateFiles = [];
70
79
  for (const generateFromItem of generateFromStrict) {
71
- const files = await glob(generateFromItem.templatePath);
80
+ let files = [];
81
+ const fullSchemaJSONFileName = generateFromItem.fullSchemaJSON
82
+ ? generateFromItem.fullSchemaJSON === 'string'
83
+ ? generateFromItem.fullSchemaJSON
84
+ : DEFAULT_FULL_SCHEMA_FILE_NAME
85
+ : null;
86
+ if (generateFromItem.templateGlob) {
87
+ files = await glob(generateFromItem.templateGlob);
88
+ if (files.length === 0) {
89
+ log.error(`Template "${generateFromItem.templateGlob}" not found`);
90
+ continue;
91
+ }
92
+ }
93
+ else {
94
+ templateFiles.push({
95
+ templateName: generateFromItem.templateName,
96
+ templatePath: null,
97
+ outDir: generateFromItem.outDir,
98
+ fullSchemaJSONFileName,
99
+ origin: generateFromItem.origin,
100
+ });
101
+ }
72
102
  for await (const templatePath of files) {
73
103
  templateFiles.push({
74
104
  templateName: generateFromItem.templateName,
75
105
  templatePath,
76
106
  outDir: generateFromItem.outDir,
77
- fullSchemaJSONFileName: generateFromItem.fullSchemaJSON
78
- ? generateFromItem.fullSchemaJSON === 'string'
79
- ? generateFromItem.fullSchemaJSON
80
- : DEFAULT_FULL_SCHEMA_FILE_NAME
81
- : null,
107
+ fullSchemaJSONFileName,
82
108
  origin: generateFromItem.origin,
83
109
  });
84
110
  }
@@ -6,8 +6,8 @@ export type ClientImports = {
6
6
  validateOnClient: string | null;
7
7
  createRPC: string;
8
8
  };
9
- export default function generate({ projectInfo, forceNothingWrittenLog, templates, prettify: prettifyClient, fullSchema, emitFullSchema, }: {
9
+ export default function generate({ projectInfo, forceNothingWrittenLog, templates, prettify: prettifyClient, fullSchema, fullSchemaJson, }: {
10
10
  projectInfo: ProjectInfo;
11
11
  forceNothingWrittenLog?: boolean;
12
12
  fullSchema: VovkFullSchema;
13
- } & Pick<GenerateOptions, 'templates' | 'prettify' | 'emitFullSchema'>): Promise<void>;
13
+ } & Pick<GenerateOptions, 'templates' | 'prettify' | 'fullSchemaJson'>): Promise<void>;
@@ -13,6 +13,9 @@ import removeUnlistedDirectories from '../utils/removeUnlistedDirectories.mjs';
13
13
  async function writeOneClientFile({ projectInfo, clientTemplate, fullSchema, prettifyClient, segmentName, imports, templateContent, matterResult: { data, content }, }) {
14
14
  const { config, apiRoot, segments } = projectInfo;
15
15
  const { templatePath, outDir, origin } = clientTemplate;
16
+ if (!templatePath) {
17
+ throw new Error(`Template path is not defined for ${clientTemplate.templateName} but used at writeOneClientFile`);
18
+ }
16
19
  const outPath = path.join(outDir, typeof segmentName === 'string' ? segmentName || ROOT_SEGMENT_SCHEMA_NAME : '', path.basename(templatePath).replace('.ejs', ''));
17
20
  // Data for the EJS templates:
18
21
  const t = {
@@ -56,21 +59,22 @@ async function writeOneClientFile({ projectInfo, clientTemplate, fullSchema, pre
56
59
  rendered = await prettify(rendered, outPath);
57
60
  }
58
61
  // Read existing file content to compare
59
- const existingContent = await fs.readFile(outPath, 'utf-8').catch(() => '');
62
+ const existingContent = await fs.readFile(outPath, 'utf-8').catch(() => null);
60
63
  // Determine if we need to rewrite the file, ignore 1st line
61
- const needsWriting = existingContent.trim().split('\n').slice(1).join('\n') !== rendered.trim().split('\n').slice(1).join('\n');
64
+ const needsWriting = !existingContent ||
65
+ existingContent.trim().split('\n').slice(1).join('\n') !== rendered.trim().split('\n').slice(1).join('\n');
62
66
  if (needsWriting) {
63
67
  await fs.mkdir(path.dirname(outPath), { recursive: true });
64
68
  await fs.writeFile(outPath, rendered);
65
69
  }
66
70
  return { written: needsWriting };
67
71
  }
68
- export default async function generate({ projectInfo, forceNothingWrittenLog, templates, prettify: prettifyClient = false, fullSchema, emitFullSchema, }) {
72
+ export default async function generate({ projectInfo, forceNothingWrittenLog, templates, prettify: prettifyClient = false, fullSchema, fullSchemaJson, }) {
69
73
  const now = Date.now();
70
74
  const generateFrom = templates ?? projectInfo.config.generateFrom;
71
75
  const noClient = templates?.[0] === 'none';
72
76
  const { config, cwd, log, clientImports, segments } = projectInfo;
73
- const { clientOutDirAbsolutePath, templateFiles } = await getClientTemplates({ config, cwd, generateFrom });
77
+ const { clientOutDirAbsolutePath, templateFiles } = await getClientTemplates({ config, cwd, generateFrom, log });
74
78
  // Ensure that each segment has a matching schema if it needs to be emitted:
75
79
  for (let i = 0; i < segments.length; i++) {
76
80
  const { segmentName } = segments[i];
@@ -87,10 +91,12 @@ export default async function generate({ projectInfo, forceNothingWrittenLog, te
87
91
  // Process each template in parallel
88
92
  await Promise.all(templateFiles.map(async (clientTemplate) => {
89
93
  const { templatePath, templateName, outDir, fullSchemaJSONFileName } = clientTemplate;
90
- if (!noClient) {
94
+ if (!noClient && templatePath) {
91
95
  // Read the EJS template
92
96
  const templateContent = await fs.readFile(templatePath, 'utf-8');
93
- const matterResult = matter(templateContent);
97
+ const matterResult = templatePath.endsWith('.ejs')
98
+ ? matter(templateContent)
99
+ : { data: { imports: [] }, content: templateContent };
94
100
  if (config.emitFullClient) {
95
101
  const { written: isWritten } = await writeOneClientFile({
96
102
  projectInfo,
@@ -153,9 +159,9 @@ export default async function generate({ projectInfo, forceNothingWrittenLog, te
153
159
  });
154
160
  }
155
161
  }));
156
- if (emitFullSchema) {
157
- const fullSchemaOutAbsolutePath = emitFullSchema
158
- ? path.resolve(clientOutDirAbsolutePath, emitFullSchema === 'string' ? emitFullSchema : DEFAULT_FULL_SCHEMA_FILE_NAME)
162
+ if (fullSchemaJson) {
163
+ const fullSchemaOutAbsolutePath = fullSchemaJson
164
+ ? path.resolve(clientOutDirAbsolutePath, typeof fullSchemaJson === 'string' ? fullSchemaJson : DEFAULT_FULL_SCHEMA_FILE_NAME)
159
165
  : null;
160
166
  if (fullSchemaOutAbsolutePath) {
161
167
  fullSchemaOutAbsolutePaths.set(null, fullSchemaOutAbsolutePath);
@@ -163,7 +169,7 @@ export default async function generate({ projectInfo, forceNothingWrittenLog, te
163
169
  }
164
170
  if (fullSchemaOutAbsolutePaths.size) {
165
171
  await Promise.all(Array.from(fullSchemaOutAbsolutePaths.entries()).map(async ([segmentName, outPath]) => {
166
- const schema = segmentName ? pickSegmentFullSchema(fullSchema, segmentName) : fullSchema;
172
+ const schema = typeof segmentName === 'string' ? pickSegmentFullSchema(fullSchema, segmentName) : fullSchema;
167
173
  await fs.mkdir(path.dirname(outPath), { recursive: true });
168
174
  await fs.writeFile(outPath, JSON.stringify(schema, null, 2));
169
175
  }));
@@ -5,9 +5,9 @@ export default async function getConfig({ clientOutDir, configPath, cwd, }) {
5
5
  const { configAbsolutePaths, error, userConfig } = await getUserConfig({ configPath, cwd });
6
6
  const conf = userConfig ?? {};
7
7
  const srcRoot = await getRelativeSrcRoot({ cwd });
8
- const validateOnClientImport = env.VOVK_VALIDATE_ON_CLIENT_PATH ?? conf.imports?.validateOnClient ?? null;
9
- const fetcherImport = env.VOVK_FETCHER_PATH ?? conf.imports?.fetcher ?? 'vovk';
10
- const createRPCImport = env.VOVK_CREATE_RPC_PATH ?? conf.imports?.createRPC ?? 'vovk';
8
+ const validateOnClientImport = env.VOVK_IMPORTS_VALIDATE_ON_CLIENT ?? conf.imports?.validateOnClient ?? null;
9
+ const fetcherImport = env.VOVK_IMPORTS_FETCHER ?? conf.imports?.fetcher ?? 'vovk';
10
+ const createRPCImport = env.VOVK_IMPORTS_CREATE_RPC ?? conf.imports?.createRPC ?? 'vovk';
11
11
  const defaultClientTemplates = ['module', 'main'];
12
12
  const config = {
13
13
  emitConfig: [],
@@ -23,7 +23,7 @@ export default async function getProjectInfo({ port: givenPort, clientOutDir, co
23
23
  }
24
24
  const getImportPath = (p, s = '') => p.startsWith('.') ? path.relative(path.join(config.clientOutDir, s), p) : p;
25
25
  const apiDirAbsolutePath = path.join(cwd, apiDir);
26
- const segments = await locateSegments({ dir: apiDirAbsolutePath, config });
26
+ const segments = await locateSegments({ dir: apiDirAbsolutePath, config, log });
27
27
  // TODO Refactor
28
28
  const clientImports = {
29
29
  fullClient: {
package/dist/index.mjs CHANGED
@@ -74,11 +74,11 @@ program
74
74
  .description('Generate RPC client from schema')
75
75
  .option('--out, --client-out-dir <path>', 'path to output directory')
76
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')
77
+ .option('--full-schema-json [fileName]', 'generate client with full schema')
78
78
  .option('--prettify', 'prettify output files')
79
79
  .option('--config <config>', 'path to config file')
80
80
  .action(async (options) => {
81
- const { clientOutDir, templates, prettify, emitFullSchema, config: configPath } = options;
81
+ const { clientOutDir, templates, prettify, fullSchemaJson, config: configPath } = options;
82
82
  const projectInfo = await getProjectInfo({ clientOutDir, configPath });
83
83
  const { cwd, config } = projectInfo;
84
84
  const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
@@ -89,7 +89,7 @@ program
89
89
  templates,
90
90
  prettify,
91
91
  forceNothingWrittenLog: true,
92
- emitFullSchema,
92
+ fullSchemaJson,
93
93
  });
94
94
  });
95
95
  program
@@ -1,11 +1,13 @@
1
1
  import type { VovkStrictConfig } from 'vovk';
2
+ import type { ProjectInfo } from './getProjectInfo/index.mjs';
2
3
  export type Segment = {
3
4
  routeFilePath: string;
4
5
  segmentName: string;
5
6
  segmentImportPath: string;
6
7
  };
7
- export default function locateSegments({ dir, rootDir, config, }: {
8
+ export default function locateSegments({ dir, rootDir, config, log, }: {
8
9
  dir: string;
9
10
  rootDir?: string;
10
11
  config: VovkStrictConfig | null;
12
+ log: ProjectInfo['log'];
11
13
  }): Promise<Segment[]>;
@@ -1,11 +1,18 @@
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, config, }) {
4
+ export default async function locateSegments({ dir, rootDir, config, log, }) {
5
5
  let results = [];
6
6
  rootDir = rootDir ?? dir;
7
+ let list = [];
7
8
  // Read the contents of the directory
8
- const list = await fs.readdir(dir);
9
+ try {
10
+ list = await fs.readdir(dir);
11
+ }
12
+ catch {
13
+ // do nothing
14
+ return results;
15
+ }
9
16
  // Iterate through each item in the directory
10
17
  for (const file of list) {
11
18
  const filePath = path.join(dir, file);
@@ -23,7 +30,7 @@ export default async function locateSegments({ dir, rootDir, config, }) {
23
30
  }
24
31
  }
25
32
  // Recursively search inside subdirectories
26
- const subDirResults = await locateSegments({ dir: filePath, rootDir, config });
33
+ const subDirResults = await locateSegments({ dir: filePath, rootDir, config, log });
27
34
  results = results.concat(subDirResults);
28
35
  }
29
36
  }
@@ -3,7 +3,6 @@ import fs from 'node:fs/promises';
3
3
  import render from './render.mjs';
4
4
  import addClassToSegmentCode from './addClassToSegmentCode.mjs';
5
5
  import getProjectInfo from '../getProjectInfo/index.mjs';
6
- import locateSegments from '../locateSegments.mjs';
7
6
  import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
8
7
  import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
9
8
  import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
@@ -20,7 +19,7 @@ function splitByLast(str, delimiter = '/') {
20
19
  return [before, after];
21
20
  }
22
21
  export default async function newModule({ what, moduleNameWithOptionalSegment, dryRun, dir: dirFlag, templates: templatesFlag, noSegmentUpdate, overwrite, empty, }) {
23
- const { config, log, apiDir, cwd } = await getProjectInfo();
22
+ const { config, log, cwd, segments } = await getProjectInfo();
24
23
  let templates = config.templates;
25
24
  const [segmentName, moduleName] = splitByLast(moduleNameWithOptionalSegment);
26
25
  // replace c by controller, s by service, everything else keeps the same
@@ -48,7 +47,6 @@ export default async function newModule({ what, moduleNameWithOptionalSegment, d
48
47
  throw new Error(`Template for "${type}" not found`);
49
48
  }
50
49
  }
51
- const segments = await locateSegments({ dir: apiDir, config });
52
50
  const segment = segments.find((s) => s.segmentName === segmentName);
53
51
  if (!segment) {
54
52
  throw new Error(`Unable to create module. Segment "${segmentName}" not found. Run "vovk new segment ${segmentName}" to create it`);
package/dist/types.d.mts CHANGED
@@ -14,7 +14,7 @@ export interface GenerateOptions {
14
14
  clientOutDir?: string;
15
15
  templates?: string[];
16
16
  prettify?: boolean;
17
- emitFullSchema?: string | boolean;
17
+ fullSchemaJson?: string | boolean;
18
18
  config?: string;
19
19
  }
20
20
  export interface InitOptions {
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
+ import getFileSystemEntryType, { FileSystemEntryType } from './getFileSystemEntryType.mjs';
3
4
  /**
4
5
  * Removes all directories in a folder that aren't in the provided allowlist
5
6
  * Supports nested directory paths like 'foo/bar/baz'
@@ -23,6 +24,12 @@ async function removeUnlistedDirectories(folderPath, allowedDirs) {
23
24
  */
24
25
  async function processDirectory(basePath, relativePath, allowedDirs) {
25
26
  const currentDirPath = path.join(basePath, relativePath);
27
+ // check if the current path is a directory
28
+ const type = await getFileSystemEntryType(currentDirPath);
29
+ if (type !== FileSystemEntryType.DIRECTORY) {
30
+ // If it's not a directory, return early
31
+ return;
32
+ }
26
33
  // Read all entries in the current directory
27
34
  const entries = await fs.readdir(currentDirPath, { withFileTypes: true });
28
35
  // Process only directories
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vovk-cli",
3
- "version": "0.0.1-draft.136",
3
+ "version": "0.0.1-draft.138",
4
4
  "bin": {
5
5
  "vovk": "./dist/index.mjs"
6
6
  },
@@ -35,10 +35,10 @@
35
35
  },
36
36
  "homepage": "https://vovk.dev",
37
37
  "peerDependencies": {
38
- "vovk": "^3.0.0-draft.110"
38
+ "vovk": "^3.0.0-draft.112"
39
39
  },
40
40
  "optionalDependencies": {
41
- "vovk-python-client": "*"
41
+ "vovk-python-client": "^0.0.1-draft.6"
42
42
  },
43
43
  "dependencies": {
44
44
  "@inquirer/prompts": "^7.3.1",
@@ -70,6 +70,6 @@
70
70
  "concat-stream": "^2.0.0",
71
71
  "create-next-app": "^15.1.6",
72
72
  "node-pty": "^1.0.0",
73
- "type-fest": "^4.33.0"
73
+ "type-fest": "^4.37.0"
74
74
  }
75
75
  }