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

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 (97) 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 +14 -32
  14. package/dist/dev/index.d.mts +6 -0
  15. package/dist/{watcher → dev}/index.mjs +152 -79
  16. package/dist/dev/isSegmentSchemaEmpty.d.mts +2 -0
  17. package/dist/dev/isSegmentSchemaEmpty.mjs +4 -0
  18. package/dist/dev/logDiffResult.d.mts +3 -0
  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} +11 -7
  24. package/dist/generate/ensureClient.d.mts +4 -0
  25. package/dist/generate/ensureClient.mjs +41 -0
  26. package/dist/generate/getClientTemplates.d.mts +24 -0
  27. package/dist/generate/getClientTemplates.mjs +86 -0
  28. package/dist/generate/getFullSchemaFromJSON.d.mts +3 -0
  29. package/dist/generate/getFullSchemaFromJSON.mjs +64 -0
  30. package/dist/generate/index.d.mts +13 -0
  31. package/dist/generate/index.mjs +115 -0
  32. package/dist/getProjectInfo/getConfig.d.mts +5 -4
  33. package/dist/getProjectInfo/getConfig.mjs +26 -7
  34. package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +2 -1
  35. package/dist/getProjectInfo/getConfigAbsolutePaths.mjs +6 -3
  36. package/dist/getProjectInfo/getRelativeSrcRoot.mjs +1 -1
  37. package/dist/getProjectInfo/getUserConfig.d.mts +3 -2
  38. package/dist/getProjectInfo/getUserConfig.mjs +5 -3
  39. package/dist/getProjectInfo/importUncachedModule.mjs +0 -1
  40. package/dist/getProjectInfo/importUncachedModuleWorker.mjs +0 -1
  41. package/dist/getProjectInfo/index.d.mts +14 -6
  42. package/dist/getProjectInfo/index.mjs +23 -15
  43. package/dist/index.d.mts +0 -28
  44. package/dist/index.mjs +60 -64
  45. package/dist/init/checkTSConfigForExperimentalDecorators.mjs +2 -2
  46. package/dist/init/createConfig.d.mts +3 -4
  47. package/dist/init/createConfig.mjs +14 -10
  48. package/dist/init/getTemplateFilesFromPackage.d.mts +2 -1
  49. package/dist/init/getTemplateFilesFromPackage.mjs +4 -5
  50. package/dist/init/index.d.mts +2 -3
  51. package/dist/init/index.mjs +31 -88
  52. package/dist/init/installDependencies.d.mts +1 -1
  53. package/dist/init/installDependencies.mjs +1 -1
  54. package/dist/init/logUpdateDependenciesError.d.mts +2 -2
  55. package/dist/init/logUpdateDependenciesError.mjs +3 -3
  56. package/dist/init/updateDependenciesWithoutInstalling.d.mts +3 -2
  57. package/dist/init/updateDependenciesWithoutInstalling.mjs +7 -9
  58. package/dist/init/updateNPMScripts.d.mts +3 -1
  59. package/dist/init/updateNPMScripts.mjs +10 -7
  60. package/dist/init/updateTypeScriptConfig.mjs +2 -2
  61. package/dist/initProgram.d.mts +2 -0
  62. package/dist/initProgram.mjs +21 -0
  63. package/dist/locateSegments.d.mts +7 -1
  64. package/dist/locateSegments.mjs +9 -6
  65. package/dist/new/addClassToSegmentCode.d.mts +1 -2
  66. package/dist/new/addClassToSegmentCode.mjs +5 -5
  67. package/dist/new/addCommonTerms.mjs +1 -0
  68. package/dist/new/index.d.mts +2 -2
  69. package/dist/new/index.mjs +3 -2
  70. package/dist/new/newModule.d.mts +3 -2
  71. package/dist/new/newModule.mjs +41 -37
  72. package/dist/new/newSegment.mjs +8 -6
  73. package/dist/new/render.d.mts +6 -3
  74. package/dist/new/render.mjs +25 -13
  75. package/dist/types.d.mts +36 -40
  76. package/dist/utils/debounceWithArgs.d.mts +2 -2
  77. package/dist/utils/debounceWithArgs.mjs +24 -9
  78. package/dist/utils/formatLoggedSegmentName.mjs +1 -1
  79. package/dist/utils/getAvailablePort.mjs +2 -1
  80. package/dist/utils/getFileSystemEntryType.mjs +1 -1
  81. package/dist/utils/resolveAbsoluteModulePath.d.mts +1 -0
  82. package/dist/utils/resolveAbsoluteModulePath.mjs +6 -0
  83. package/package.json +26 -21
  84. package/templates/controller.ejs +22 -23
  85. package/templates/service.ejs +13 -13
  86. package/dist/generateClient.d.mts +0 -7
  87. package/dist/generateClient.mjs +0 -97
  88. package/dist/postinstall.d.mts +0 -1
  89. package/dist/postinstall.mjs +0 -24
  90. package/dist/watcher/diffSchema.d.mts +0 -43
  91. package/dist/watcher/index.d.mts +0 -6
  92. package/dist/watcher/isMetadataEmpty.d.mts +0 -2
  93. package/dist/watcher/isMetadataEmpty.mjs +0 -4
  94. package/dist/watcher/logDiffResult.d.mts +0 -3
  95. package/dist/watcher/logDiffResult.mjs +0 -90
  96. package/dist/watcher/writeOneSchemaFile.d.mts +0 -11
  97. package/templates/worker.ejs +0 -24
@@ -0,0 +1,86 @@
1
+ import path from 'node:path';
2
+ import { glob } from 'node:fs/promises';
3
+ import resolveAbsoluteModulePath from '../utils/resolveAbsoluteModulePath.mjs';
4
+ export const DEFAULT_FULL_SCHEMA_FILE_NAME = 'full-schema.json';
5
+ export var BuiltInTemplateName;
6
+ (function (BuiltInTemplateName) {
7
+ BuiltInTemplateName["ts"] = "ts";
8
+ BuiltInTemplateName["main"] = "main";
9
+ BuiltInTemplateName["module"] = "module";
10
+ BuiltInTemplateName["fullSchema"] = "fullSchema";
11
+ })(BuiltInTemplateName || (BuiltInTemplateName = {}));
12
+ export default async function getClientTemplates({ config, cwd, generateFrom = [], }) {
13
+ const templatesDir = path.join(import.meta.dirname, '../..', 'client-templates');
14
+ const clientOutDirAbsolutePath = path.resolve(cwd, config.clientOutDir);
15
+ const builtIn = {
16
+ ts: {
17
+ templateName: BuiltInTemplateName.ts,
18
+ templatePath: path.resolve(templatesDir, 'ts/*'),
19
+ outDir: clientOutDirAbsolutePath,
20
+ fullSchema: false,
21
+ origin: null,
22
+ },
23
+ main: {
24
+ templateName: BuiltInTemplateName.main,
25
+ templatePath: path.resolve(templatesDir, 'main/*'),
26
+ outDir: clientOutDirAbsolutePath,
27
+ fullSchema: false,
28
+ origin: null,
29
+ },
30
+ module: {
31
+ templateName: BuiltInTemplateName.module,
32
+ templatePath: path.resolve(templatesDir, 'module/*'),
33
+ outDir: clientOutDirAbsolutePath,
34
+ fullSchema: false,
35
+ origin: null,
36
+ },
37
+ fullSchema: {
38
+ templateName: BuiltInTemplateName.fullSchema,
39
+ templatePath: path.resolve(templatesDir, 'fullSchema/*'),
40
+ outDir: clientOutDirAbsolutePath,
41
+ fullSchema: false,
42
+ origin: null,
43
+ },
44
+ };
45
+ const generateFromStrict = generateFrom.map((template) => {
46
+ if (typeof template === 'string') {
47
+ if (template in builtIn) {
48
+ return builtIn[template];
49
+ }
50
+ return {
51
+ templateName: template,
52
+ templatePath: resolveAbsoluteModulePath(template, cwd),
53
+ outDir: clientOutDirAbsolutePath,
54
+ fullSchema: false,
55
+ origin: null,
56
+ };
57
+ }
58
+ return {
59
+ templateName: template.templateName ?? template.templatePath,
60
+ templatePath: resolveAbsoluteModulePath(template.templatePath, cwd),
61
+ outDir: template.outDir ? path.resolve(cwd, template.outDir) : clientOutDirAbsolutePath,
62
+ fullSchema: template.fullSchema ?? false,
63
+ origin: template.origin ?? null,
64
+ };
65
+ });
66
+ if (['ts', 'main', 'module'].some((template) => generateFromStrict.some((item) => item.templateName === template))) {
67
+ generateFromStrict.unshift(builtIn.fullSchema);
68
+ }
69
+ const templateFiles = [];
70
+ for (const generateFromItem of generateFromStrict) {
71
+ const files = await glob(generateFromItem.templatePath);
72
+ for await (const templatePath of files) {
73
+ const fullSchemaOutAbsolutePath = generateFromItem.fullSchema
74
+ ? path.resolve(generateFromItem.outDir, generateFromItem.fullSchema === 'string' ? generateFromItem.fullSchema : DEFAULT_FULL_SCHEMA_FILE_NAME)
75
+ : null;
76
+ templateFiles.push({
77
+ templateName: generateFromItem.templateName,
78
+ templatePath,
79
+ outPath: path.join(generateFromItem.outDir, path.basename(templatePath).replace('.ejs', '')),
80
+ fullSchemaOutAbsolutePath,
81
+ origin: generateFromItem.origin,
82
+ });
83
+ }
84
+ }
85
+ return { clientOutDirAbsolutePath, templateFiles };
86
+ }
@@ -0,0 +1,3 @@
1
+ import type { VovkFullSchema } from 'vovk';
2
+ import { ProjectInfo } from '../getProjectInfo/index.mjs';
3
+ export declare function getFullSchemaFromJSON(schemaOutAbsolutePath: string, projectInfo: ProjectInfo): Promise<VovkFullSchema>;
@@ -0,0 +1,64 @@
1
+ import { readFile, access } from 'node:fs/promises';
2
+ import { glob } from 'glob';
3
+ import * as path from 'node:path';
4
+ export async function getFullSchemaFromJSON(schemaOutAbsolutePath, projectInfo) {
5
+ const result = {
6
+ config: {},
7
+ segments: {},
8
+ };
9
+ const { log } = projectInfo;
10
+ // Handle config.json
11
+ const configPath = path.join(schemaOutAbsolutePath, 'config.json');
12
+ try {
13
+ const configContent = await readFile(configPath, 'utf-8');
14
+ result.config = JSON.parse(configContent);
15
+ }
16
+ catch {
17
+ log.warn(`Warning: config.json not found at ${configPath}. Using empty config as fallback.`);
18
+ result.config = {};
19
+ }
20
+ // Handle segments directory
21
+ const segmentsDir = path.join(schemaOutAbsolutePath, 'segments');
22
+ try {
23
+ await access(segmentsDir); // Check if directory exists
24
+ // Use glob to get all JSON files recursively
25
+ const files = await glob(`${segmentsDir}/**/*.json`);
26
+ // Process each JSON file
27
+ for await (const filePath of files) {
28
+ try {
29
+ const content = await readFile(filePath, 'utf-8');
30
+ const jsonData = JSON.parse(content);
31
+ // Get relative path from segments directory and convert to key
32
+ let relativePath = path
33
+ .relative(segmentsDir, filePath)
34
+ .replace(/\.json$/, '') // Remove .json extension
35
+ .replace(/\\/g, '/'); // Normalize to forward slashes
36
+ // Special case for _root.json
37
+ if (path.basename(filePath) === '_root.json' && path.dirname(filePath) === segmentsDir) {
38
+ relativePath = '';
39
+ }
40
+ result.segments[relativePath] = jsonData;
41
+ }
42
+ catch (error) {
43
+ log.warn(`Warning: Failed to process file ${filePath}: ${error}`);
44
+ }
45
+ }
46
+ }
47
+ catch {
48
+ log.warn(`Warning: Segments directory not found at ${segmentsDir}. Using empty segments as fallback.`);
49
+ result.segments = {};
50
+ }
51
+ return result;
52
+ }
53
+ // Example usage:
54
+ /*
55
+ async function main() {
56
+ try {
57
+ const schema = await getSchemaFromJSON('/path/to/directory');
58
+ console.log(schema);
59
+ } catch (error) {
60
+ console.error('Error:', error);
61
+ }
62
+ }
63
+ main();
64
+ */
@@ -0,0 +1,13 @@
1
+ import type { VovkFullSchema } from 'vovk';
2
+ import type { ProjectInfo } from '../getProjectInfo/index.mjs';
3
+ import type { Segment } from '../locateSegments.mjs';
4
+ import type { GenerateOptions } from '../types.mjs';
5
+ export default function generate({ projectInfo, segments, forceNothingWrittenLog, templates, prettify: prettifyClient, fullSchema, emitFullSchema, }: {
6
+ projectInfo: ProjectInfo;
7
+ segments: Segment[];
8
+ forceNothingWrittenLog?: boolean;
9
+ fullSchema: VovkFullSchema;
10
+ } & Pick<GenerateOptions, 'templates' | 'prettify' | 'emitFullSchema'>): Promise<{
11
+ written: boolean;
12
+ path: string;
13
+ }>;
@@ -0,0 +1,115 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ import ejs from 'ejs';
4
+ import matter from 'gray-matter';
5
+ import uniq from 'lodash/uniq.js';
6
+ import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
7
+ import prettify from '../utils/prettify.mjs';
8
+ import getClientTemplates, { DEFAULT_FULL_SCHEMA_FILE_NAME } from './getClientTemplates.mjs';
9
+ import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
10
+ import _ from 'lodash';
11
+ import { ROOT_SEGMENT_SCHEMA_NAME, SEGMENTS_SCHEMA_DIR_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
12
+ export default async function generate({ projectInfo, segments, forceNothingWrittenLog, templates, prettify: prettifyClient, fullSchema, emitFullSchema, }) {
13
+ const segmentsSchema = fullSchema.segments;
14
+ const generateFrom = templates ?? projectInfo.config.generateFrom;
15
+ const noClient = templates?.[0] === 'none';
16
+ const { config, cwd, log, clientImports, apiRoot } = projectInfo;
17
+ const { clientOutDirAbsolutePath, templateFiles } = await getClientTemplates({ config, cwd, generateFrom });
18
+ // Ensure that each segment has a matching schema if it needs to be emitted:
19
+ for (let i = 0; i < segments.length; i++) {
20
+ const { segmentName } = segments[i];
21
+ const schema = segmentsSchema[segmentName];
22
+ if (!schema) {
23
+ throw new Error(`Unable to generate client. No schema found for ${formatLoggedSegmentName(segmentName)}`);
24
+ }
25
+ if (!schema.emitSchema)
26
+ continue;
27
+ }
28
+ const now = Date.now();
29
+ const schemaOutDir = path.relative(config.clientOutDir, config.schemaOutDir).replace(/\\/g, '/'); // windows fix
30
+ // Process each template in parallel
31
+ const processedTemplates = noClient
32
+ ? []
33
+ : await Promise.all(templateFiles.map(async ({ templatePath, outPath, templateName, origin }) => {
34
+ /*const parsed = matter((await ejs.render(codeTemplate, { t }, { async: true, filename: templateFileName })).trim());
35
+ const { dir, fileName, sourceName, compiledName } = parsed.data as VovkModuleRenderResult;
36
+ const code = empty ? (sourceName ? `export default class ${sourceName} {}` : '') : parsed.content;*/
37
+ // Read the EJS template
38
+ const templateContent = await fs.readFile(templatePath, 'utf-8');
39
+ const { data, content } = matter(templateContent);
40
+ // Data for the EJS templates:
41
+ const t = {
42
+ _, // lodash
43
+ ROOT_SEGMENT_SCHEMA_NAME,
44
+ SEGMENTS_SCHEMA_DIR_NAME,
45
+ apiRoot: origin ? `${origin}/${config.rootEntry}` : apiRoot,
46
+ imports: clientImports,
47
+ fullSchema,
48
+ schemaOutDir,
49
+ segmentMeta: Object.fromEntries(segments.map(({ segmentName, ...s }) => [segmentName, s])),
50
+ };
51
+ if (data.imports instanceof Array) {
52
+ for (const imp of data.imports) {
53
+ t.imports = {
54
+ ...t.imports,
55
+ [imp]: await import(imp),
56
+ };
57
+ }
58
+ }
59
+ // Render the template
60
+ let rendered = templatePath.endsWith('.ejs')
61
+ ? ejs.render(content, { t }, {
62
+ filename: templatePath,
63
+ })
64
+ : templateContent;
65
+ // Optionally prettify
66
+ if (prettifyClient || config.prettifyClient) {
67
+ rendered = await prettify(rendered, outPath);
68
+ }
69
+ // Read existing file content to compare
70
+ const existingContent = await fs.readFile(outPath, 'utf-8').catch(() => '');
71
+ // Determine if we need to rewrite the file, ignore 1st line
72
+ const needsWriting = existingContent.trim().split('\n').slice(1).join('\n') !== rendered.trim().split('\n').slice(1).join('\n');
73
+ return {
74
+ outPath,
75
+ rendered,
76
+ needsWriting,
77
+ templateName,
78
+ };
79
+ }));
80
+ const usedTemplateNames = uniq(processedTemplates.filter(({ needsWriting }) => needsWriting).map(({ templateName }) => templateName));
81
+ let schemaPaths = templateFiles
82
+ .map(({ fullSchemaOutAbsolutePath }) => fullSchemaOutAbsolutePath)
83
+ .filter(Boolean);
84
+ if (emitFullSchema) {
85
+ const fullSchemaOutAbsolutePath = emitFullSchema
86
+ ? path.resolve(clientOutDirAbsolutePath, emitFullSchema === 'string' ? emitFullSchema : DEFAULT_FULL_SCHEMA_FILE_NAME)
87
+ : null;
88
+ if (fullSchemaOutAbsolutePath) {
89
+ schemaPaths.push(fullSchemaOutAbsolutePath);
90
+ }
91
+ }
92
+ schemaPaths = uniq(schemaPaths);
93
+ if (schemaPaths.length) {
94
+ await Promise.all(schemaPaths.map(async (fullSchemaOutAbsolutePath) => {
95
+ fs.mkdir(path.dirname(fullSchemaOutAbsolutePath), { recursive: true });
96
+ return fs.writeFile(fullSchemaOutAbsolutePath, JSON.stringify(fullSchema, null, 2));
97
+ }));
98
+ log.info(`Full schema has been written to ${schemaPaths.map((s) => `"${s}"`).join(', ')}`);
99
+ }
100
+ if (usedTemplateNames.length === 0) {
101
+ const logOrDebug = forceNothingWrittenLog ? log.info : log.debug;
102
+ logOrDebug(`Client is up to date and doesn't need to be regenerated (${Date.now() - now}ms)`);
103
+ return { written: false, path: clientOutDirAbsolutePath };
104
+ }
105
+ // Write updated files where needed
106
+ await Promise.all(processedTemplates.map(async ({ outPath, rendered, needsWriting }) => {
107
+ if (needsWriting) {
108
+ await fs.mkdir(path.dirname(outPath), { recursive: true });
109
+ return fs.writeFile(outPath, rendered);
110
+ }
111
+ return null;
112
+ }));
113
+ log.info(`Client generated from template${usedTemplateNames.length !== 1 ? 's' : ''} ${chalkHighlightThing(usedTemplateNames.map((s) => `"${s}"`).join(', '))} in ${Date.now() - now}ms`);
114
+ return { written: true, path: clientOutDirAbsolutePath };
115
+ }
@@ -1,11 +1,12 @@
1
- import type { VovkConfig } from '../types.mjs';
2
- export default function getConfig({ clientOutDir, cwd }: {
1
+ import type { VovkStrictConfig } from 'vovk';
2
+ export default function getConfig({ clientOutDir, configPath, cwd, }: {
3
3
  clientOutDir?: string;
4
+ configPath?: string;
4
5
  cwd: string;
5
6
  }): Promise<{
6
- config: Required<VovkConfig>;
7
+ config: VovkStrictConfig;
7
8
  srcRoot: string;
8
9
  configAbsolutePaths: string[];
9
- userConfig: VovkConfig | null;
10
+ userConfig: import("vovk").VovkConfig | null;
10
11
  error: Error | undefined;
11
12
  }>;
@@ -1,29 +1,48 @@
1
1
  import getUserConfig from './getUserConfig.mjs';
2
2
  import getRelativeSrcRoot from './getRelativeSrcRoot.mjs';
3
- export default async function getConfig({ clientOutDir, cwd }) {
3
+ export default async function getConfig({ clientOutDir, configPath, cwd, }) {
4
4
  const env = process.env;
5
- const { configAbsolutePaths, error, userConfig } = await getUserConfig({ cwd });
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';
11
+ const defaultClientTemplates = ['module', 'main'];
8
12
  const config = {
13
+ emitConfig: [],
9
14
  modulesDir: env.VOVK_MODULES_DIR ?? conf.modulesDir ?? './' + [srcRoot, 'modules'].filter(Boolean).join('/'),
10
- validateOnClient: env.VOVK_VALIDATE_ON_CLIENT ?? conf.validateOnClient ?? null,
11
- validationLibrary: env.VOVK_VALIDATION_LIBRARY ?? conf.validationLibrary ?? null,
12
- fetcher: env.VOVK_FETCHER ?? conf.fetcher ?? 'vovk/client/defaultFetcher',
15
+ imports: {
16
+ fetcher: typeof fetcherImport === 'string' ? [fetcherImport] : fetcherImport,
17
+ validateOnClient: typeof validateOnClientImport === 'string' ? [validateOnClientImport] : (validateOnClientImport ?? null),
18
+ createRPC: typeof createRPCImport === 'string' ? [createRPCImport, 'vovk'] : createRPCImport,
19
+ },
13
20
  schemaOutDir: env.VOVK_SCHEMA_OUT_DIR ?? conf.schemaOutDir ?? './.vovk-schema',
14
21
  clientOutDir: clientOutDir ?? env.VOVK_CLIENT_OUT_DIR ?? conf.clientOutDir ?? './node_modules/.vovk-client',
15
22
  origin: (env.VOVK_ORIGIN ?? conf.origin ?? '').replace(/\/$/, ''), // Remove trailing slash
16
23
  rootEntry: env.VOVK_ROOT_ENTRY ?? conf.rootEntry ?? 'api',
17
24
  rootSegmentModulesDirName: env.VOVK_ROOT_SEGMENT_MODULES_DIR_NAME ?? conf.rootSegmentModulesDirName ?? '',
18
- logLevel: env.VOVK_LOG_LEVEL ?? conf.logLevel ?? 'debug', // TODO: change to 'warn' when v3 is ready
25
+ logLevel: env.VOVK_LOG_LEVEL ?? conf.logLevel ?? 'info',
19
26
  prettifyClient: (env.VOVK_PRETTIFY_CLIENT ? !!env.VOVK_PRETTIFY_CLIENT : null) ?? conf.prettifyClient ?? false,
20
27
  devHttps: (env.VOVK_DEV_HTTPS ? !!env.VOVK_DEV_HTTPS : null) ?? conf.devHttps ?? false,
28
+ generateFrom: typeof conf.generateFrom === 'function'
29
+ ? conf.generateFrom(defaultClientTemplates)
30
+ : (conf.generateFrom ?? defaultClientTemplates),
21
31
  templates: {
22
32
  service: 'vovk-cli/templates/service.ejs',
23
33
  controller: 'vovk-cli/templates/controller.ejs',
24
- worker: 'vovk-cli/templates/worker.ejs',
25
34
  ...conf.templates,
26
35
  },
36
+ libs: conf.libs ?? {},
27
37
  };
38
+ if (typeof conf.emitConfig === 'undefined') {
39
+ config.emitConfig = ['libs'];
40
+ }
41
+ else if (conf.emitConfig === true) {
42
+ config.emitConfig = Object.keys(config);
43
+ }
44
+ else if (Array.isArray(conf.emitConfig)) {
45
+ config.emitConfig = conf.emitConfig;
46
+ } // else it's false and emitConfig already is []
28
47
  return { config, srcRoot, configAbsolutePaths, userConfig, error };
29
48
  }
@@ -1,4 +1,5 @@
1
- export default function getConfigAbsolutePaths({ cwd, relativePath, }: {
1
+ export default function getConfigAbsolutePaths({ cwd, configPath, relativePath, }: {
2
2
  cwd: string;
3
+ configPath?: string;
3
4
  relativePath?: string;
4
5
  }): Promise<string[]>;
@@ -1,6 +1,9 @@
1
- import fs from 'fs/promises';
2
- import path from 'path';
3
- export default async function getConfigAbsolutePaths({ cwd, relativePath, }) {
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ export default async function getConfigAbsolutePaths({ cwd, configPath, relativePath, }) {
4
+ if (configPath) {
5
+ return [path.resolve(cwd, configPath)];
6
+ }
4
7
  const rootDir = path.resolve(cwd, relativePath || '');
5
8
  const baseName = 'vovk.config';
6
9
  const extensions = ['cjs', 'mjs', 'js'];
@@ -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,5 +1,6 @@
1
- import type { VovkConfig } from '../types.mjs';
2
- declare function getUserConfig({ cwd, }: {
1
+ import type { VovkConfig } from 'vovk';
2
+ declare function getUserConfig({ configPath: givenConfigPath, cwd, }: {
3
+ configPath?: string;
3
4
  cwd: string;
4
5
  }): Promise<{
5
6
  userConfig: VovkConfig | null;
@@ -1,7 +1,8 @@
1
+ import { pathToFileURL } from 'node:url';
1
2
  import getConfigAbsolutePaths from './getConfigAbsolutePaths.mjs';
2
3
  import importUncachedModule from './importUncachedModule.mjs';
3
- async function getUserConfig({ cwd, }) {
4
- const configAbsolutePaths = await getConfigAbsolutePaths({ cwd });
4
+ async function getUserConfig({ configPath: givenConfigPath, cwd, }) {
5
+ const configAbsolutePaths = await getConfigAbsolutePaths({ configPath: givenConfigPath, cwd });
5
6
  let userConfig;
6
7
  if (!configAbsolutePaths.length) {
7
8
  return { userConfig: null, configAbsolutePaths, error: new Error('No config file found') };
@@ -14,7 +15,8 @@ async function getUserConfig({ cwd, }) {
14
15
  catch {
15
16
  try {
16
17
  const cacheBuster = Date.now();
17
- ({ default: userConfig } = (await import(`${configPath}?cache=${cacheBuster}`)));
18
+ const configPathUrl = pathToFileURL(configPath).href;
19
+ ({ default: userConfig } = (await import(`${configPathUrl}?cache=${cacheBuster}`)));
18
20
  }
19
21
  catch (e) {
20
22
  return { userConfig: null, configAbsolutePaths, error: e };
@@ -3,7 +3,6 @@ import { Worker } from 'node:worker_threads';
3
3
  import path from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import './importUncachedModuleWorker.mjs'; // required for TS compilation
6
- // TODO comments
7
6
  function importUncachedModule(modulePath) {
8
7
  return new Promise((resolve, reject) => {
9
8
  const __filename = fileURLToPath(import.meta.url);
@@ -1,7 +1,6 @@
1
1
  import { parentPort, workerData as wData } from 'node:worker_threads';
2
2
  import { pathToFileURL } from 'node:url';
3
3
  void (async () => {
4
- // TODO Comments
5
4
  if (!parentPort)
6
5
  return;
7
6
  const workerData = wData;
@@ -1,18 +1,26 @@
1
1
  export type ProjectInfo = Awaited<ReturnType<typeof getProjectInfo>>;
2
- export default function getProjectInfo({ port: givenPort, clientOutDir, cwd, }?: {
2
+ export default function getProjectInfo({ port: givenPort, clientOutDir, configPath, cwd, }?: {
3
3
  port?: number;
4
4
  clientOutDir?: string;
5
+ configPath?: string;
5
6
  cwd?: string;
6
7
  }): Promise<{
7
8
  cwd: string;
8
9
  port: string;
9
- apiEntryPoint: string;
10
+ apiRoot: string;
10
11
  apiDir: string;
11
12
  srcRoot: string;
12
- schemaOutImportPath: string;
13
- fetcherClientImportPath: string;
14
- validateOnClientImportPath: string | null;
15
- config: Required<import("../types.mjs").VovkConfig>;
13
+ config: import("vovk").VovkStrictConfig;
14
+ clientImports: {
15
+ fetcher: string;
16
+ createRPC: string;
17
+ validateOnClient: string | null;
18
+ module: {
19
+ fetcher: string;
20
+ createRPC: string;
21
+ validateOnClient: string | null;
22
+ };
23
+ };
16
24
  log: {
17
25
  info: (msg: string) => void;
18
26
  warn: (msg: string) => void;
@@ -1,20 +1,17 @@
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
- export default async function getProjectInfo({ port: givenPort, clientOutDir, cwd = process.cwd(), } = {}) {
4
+ export default async function getProjectInfo({ port: givenPort, clientOutDir, configPath, cwd = process.cwd(), } = {}) {
5
5
  const port = givenPort?.toString() ?? process.env.PORT ?? '3000';
6
6
  // Make PORT available to the config file at getConfig
7
7
  process.env.PORT = port;
8
- const { config, srcRoot, configAbsolutePaths, userConfig, error } = await getConfig({ clientOutDir, cwd });
9
- const apiEntryPoint = `${config.origin ?? ''}/${config.rootEntry}`;
8
+ const { config, srcRoot, configAbsolutePaths, userConfig, error } = await getConfig({
9
+ clientOutDir,
10
+ configPath,
11
+ cwd,
12
+ });
13
+ const apiRoot = `${config.origin ?? ''}/${config.rootEntry}`;
10
14
  const apiDir = path.join(srcRoot, 'app', config.rootEntry);
11
- const schemaOutImportPath = path.relative(config.clientOutDir, config.schemaOutDir);
12
- const fetcherClientImportPath = config.fetcher.startsWith('.')
13
- ? path.relative(config.clientOutDir, config.fetcher)
14
- : config.fetcher;
15
- const validateOnClientImportPath = config.validateOnClient?.startsWith('.')
16
- ? path.relative(config.clientOutDir, config.validateOnClient)
17
- : config.validateOnClient;
18
15
  const log = getLogger(config.logLevel);
19
16
  if (configAbsolutePaths.length > 1) {
20
17
  log.warn(`Multiple config files found. Using the first one: ${configAbsolutePaths[0]}`);
@@ -22,16 +19,27 @@ export default async function getProjectInfo({ port: givenPort, clientOutDir, cw
22
19
  if (!userConfig && configAbsolutePaths.length > 0) {
23
20
  log.error(`Error reading config file at ${configAbsolutePaths[0]}: ${error?.message ?? 'Unknown Error'}`);
24
21
  }
22
+ const getImportPath = (p) => (p.startsWith('.') ? path.relative(config.clientOutDir, p) : p);
23
+ const clientImports = {
24
+ fetcher: getImportPath(config.imports.fetcher[0]),
25
+ createRPC: getImportPath(config.imports.createRPC[0]),
26
+ validateOnClient: config.imports.validateOnClient ? getImportPath(config.imports.validateOnClient[0]) : null,
27
+ module: {
28
+ fetcher: getImportPath(config.imports.fetcher[1] ?? config.imports.fetcher[0]),
29
+ createRPC: getImportPath(config.imports.createRPC[1] ?? config.imports.createRPC[0]),
30
+ validateOnClient: config.imports.validateOnClient
31
+ ? getImportPath(config.imports.validateOnClient[1] ?? config.imports.validateOnClient[0])
32
+ : null,
33
+ },
34
+ };
25
35
  return {
26
36
  cwd,
27
37
  port,
28
- apiEntryPoint,
38
+ apiRoot,
29
39
  apiDir,
30
40
  srcRoot,
31
- schemaOutImportPath,
32
- fetcherClientImportPath,
33
- validateOnClientImportPath,
34
41
  config,
42
+ clientImports,
35
43
  log,
36
44
  };
37
45
  }
package/dist/index.d.mts CHANGED
@@ -1,30 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import type { LogLevelNames } from 'loglevel';
4
- import type { VovkConfig, VovkEnv } from './types.mjs';
5
2
  import 'dotenv/config';
6
- export type { VovkConfig, VovkEnv };
7
- export interface InitOptions {
8
- yes?: boolean;
9
- logLevel: LogLevelNames;
10
- useNpm?: boolean;
11
- useYarn?: boolean;
12
- usePnpm?: boolean;
13
- useBun?: boolean;
14
- skipInstall?: boolean;
15
- updateTsConfig?: boolean;
16
- updateScripts?: 'implicit' | 'explicit';
17
- validationLibrary?: string | null;
18
- validateOnClient?: boolean;
19
- dryRun?: boolean;
20
- channel?: 'latest' | 'beta' | 'dev';
21
- }
22
- export interface NewOptions {
23
- dryRun?: boolean;
24
- template?: string;
25
- dir?: string;
26
- overwrite?: boolean;
27
- noSegmentUpdate?: boolean;
28
- }
29
- declare const program: Command;
30
- export declare function initProgram(p: typeof program, command: string): Command;