vovk-cli 0.0.1-draft.33 → 0.0.1-draft.330

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 (130) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +29 -1
  3. package/client-templates/cjs/index.cjs.ejs +19 -0
  4. package/client-templates/cjs/index.d.cts.ejs +25 -0
  5. package/client-templates/mixins/mixins.d.ts.ejs +64 -0
  6. package/client-templates/mixins/mixins.json.ejs +1 -0
  7. package/client-templates/mjs/index.d.mts.ejs +25 -0
  8. package/client-templates/mjs/index.mjs.ejs +23 -0
  9. package/client-templates/packageJson/package.json.ejs +1 -0
  10. package/client-templates/readme/README.md.ejs +38 -0
  11. package/client-templates/schemaCjs/schema.cjs.ejs +26 -0
  12. package/client-templates/schemaCjs/schema.d.cts.ejs +10 -0
  13. package/client-templates/schemaJson/schema.json.ejs +1 -0
  14. package/client-templates/schemaTs/schema.ts.ejs +35 -0
  15. package/client-templates/ts/index.ts.ejs +33 -0
  16. package/dist/bundle/index.d.mts +8 -0
  17. package/dist/bundle/index.mjs +90 -0
  18. package/dist/dev/diffSegmentSchema.d.mts +36 -0
  19. package/dist/dev/{diffSchema.mjs → diffSegmentSchema.mjs} +3 -11
  20. package/dist/dev/ensureSchemaFiles.d.mts +3 -0
  21. package/dist/dev/ensureSchemaFiles.mjs +15 -31
  22. package/dist/dev/index.d.mts +5 -1
  23. package/dist/dev/index.mjs +181 -78
  24. package/dist/dev/logDiffResult.d.mts +1 -1
  25. package/dist/dev/logDiffResult.mjs +6 -43
  26. package/dist/dev/writeMetaJson.d.mts +2 -0
  27. package/dist/dev/writeMetaJson.mjs +17 -0
  28. package/dist/dev/writeOneSegmentSchemaFile.d.mts +12 -0
  29. package/dist/dev/{writeOneSchemaFile.mjs → writeOneSegmentSchemaFile.mjs} +10 -6
  30. package/dist/generate/ensureClient.d.mts +3 -0
  31. package/dist/generate/ensureClient.mjs +32 -0
  32. package/dist/generate/generate.d.mts +16 -0
  33. package/dist/generate/generate.mjs +295 -0
  34. package/dist/generate/getClientTemplateFiles.d.mts +20 -0
  35. package/dist/generate/getClientTemplateFiles.mjs +81 -0
  36. package/dist/generate/getProjectFullSchema.d.mts +7 -0
  37. package/dist/generate/getProjectFullSchema.mjs +65 -0
  38. package/dist/generate/getTemplateClientImports.d.mts +18 -0
  39. package/dist/generate/getTemplateClientImports.mjs +38 -0
  40. package/dist/generate/index.d.mts +33 -0
  41. package/dist/generate/index.mjs +189 -0
  42. package/dist/generate/mergePackages.d.mts +7 -0
  43. package/dist/generate/mergePackages.mjs +55 -0
  44. package/dist/generate/writeOneClientFile.d.mts +37 -0
  45. package/dist/generate/writeOneClientFile.mjs +122 -0
  46. package/dist/getProjectInfo/getConfig/getConfigAbsolutePaths.d.mts +5 -0
  47. package/dist/getProjectInfo/{getConfigAbsolutePaths.mjs → getConfig/getConfigAbsolutePaths.mjs} +4 -1
  48. package/dist/getProjectInfo/{getRelativeSrcRoot.d.mts → getConfig/getRelativeSrcRoot.d.mts} +1 -1
  49. package/dist/getProjectInfo/{getRelativeSrcRoot.mjs → getConfig/getRelativeSrcRoot.mjs} +2 -2
  50. package/dist/getProjectInfo/getConfig/getTemplateDefs.d.mts +22 -0
  51. package/dist/getProjectInfo/getConfig/getTemplateDefs.mjs +145 -0
  52. package/dist/getProjectInfo/{getUserConfig.d.mts → getConfig/getUserConfig.d.mts} +3 -2
  53. package/dist/getProjectInfo/{getUserConfig.mjs → getConfig/getUserConfig.mjs} +6 -4
  54. package/dist/getProjectInfo/{importUncachedModule.mjs → getConfig/importUncachedModule.mjs} +1 -4
  55. package/dist/getProjectInfo/getConfig/index.d.mts +122 -0
  56. package/dist/getProjectInfo/getConfig/index.mjs +98 -0
  57. package/dist/getProjectInfo/index.d.mts +12 -9
  58. package/dist/getProjectInfo/index.mjs +21 -22
  59. package/dist/index.d.mts +2 -2
  60. package/dist/index.mjs +102 -36
  61. package/dist/init/createConfig.d.mts +2 -2
  62. package/dist/init/createConfig.mjs +14 -12
  63. package/dist/init/getTemplateFilesFromPackage.mjs +10 -5
  64. package/dist/init/index.d.mts +2 -2
  65. package/dist/init/index.mjs +81 -60
  66. package/dist/init/installDependencies.mjs +4 -2
  67. package/dist/init/logUpdateDependenciesError.d.mts +3 -1
  68. package/dist/init/logUpdateDependenciesError.mjs +7 -1
  69. package/dist/init/updateDependenciesWithoutInstalling.mjs +39 -9
  70. package/dist/init/updateNPMScripts.d.mts +4 -1
  71. package/dist/init/updateNPMScripts.mjs +14 -7
  72. package/dist/init/updateTypeScriptConfig.d.mts +4 -1
  73. package/dist/init/updateTypeScriptConfig.mjs +11 -7
  74. package/dist/initProgram.d.mts +1 -1
  75. package/dist/initProgram.mjs +16 -16
  76. package/dist/locateSegments.d.mts +8 -1
  77. package/dist/locateSegments.mjs +14 -4
  78. package/dist/new/addClassToSegmentCode.d.mts +1 -2
  79. package/dist/new/addClassToSegmentCode.mjs +3 -3
  80. package/dist/new/index.d.mts +1 -1
  81. package/dist/new/index.mjs +3 -2
  82. package/dist/new/newModule.d.mts +2 -1
  83. package/dist/new/newModule.mjs +18 -16
  84. package/dist/new/newSegment.d.mts +2 -1
  85. package/dist/new/newSegment.mjs +19 -10
  86. package/dist/new/render.d.mts +7 -3
  87. package/dist/new/render.mjs +29 -8
  88. package/dist/types.d.mts +51 -40
  89. package/dist/utils/compileJSONSchemaToTypeScriptType.d.mts +5 -0
  90. package/dist/utils/compileJSONSchemaToTypeScriptType.mjs +9 -0
  91. package/dist/utils/compileTs.d.mts +12 -0
  92. package/dist/utils/compileTs.mjs +261 -0
  93. package/dist/utils/debounceWithArgs.d.mts +2 -2
  94. package/dist/utils/debounceWithArgs.mjs +24 -6
  95. package/dist/utils/formatLoggedSegmentName.d.mts +3 -1
  96. package/dist/utils/formatLoggedSegmentName.mjs +3 -2
  97. package/dist/utils/getPackageJson.d.mts +3 -0
  98. package/dist/utils/getPackageJson.mjs +22 -0
  99. package/dist/utils/getPublicModuleNameFromPath.d.mts +4 -0
  100. package/dist/utils/getPublicModuleNameFromPath.mjs +9 -0
  101. package/dist/utils/normalizeOpenAPIMixins.d.mts +7 -0
  102. package/dist/utils/normalizeOpenAPIMixins.mjs +67 -0
  103. package/dist/utils/pickSegmentFullSchema.d.mts +3 -0
  104. package/dist/utils/pickSegmentFullSchema.mjs +15 -0
  105. package/dist/utils/removeUnlistedDirectories.d.mts +10 -0
  106. package/dist/utils/removeUnlistedDirectories.mjs +61 -0
  107. package/dist/utils/resolveAbsoluteModulePath.d.mts +2 -0
  108. package/dist/utils/resolveAbsoluteModulePath.mjs +32 -0
  109. package/module-templates/controller.ts.ejs +56 -0
  110. package/module-templates/service.ts.ejs +28 -0
  111. package/package.json +35 -22
  112. package/dist/dev/diffSchema.d.mts +0 -43
  113. package/dist/dev/ensureClient.d.mts +0 -5
  114. package/dist/dev/ensureClient.mjs +0 -31
  115. package/dist/dev/isMetadataEmpty.d.mts +0 -2
  116. package/dist/dev/isMetadataEmpty.mjs +0 -4
  117. package/dist/dev/writeOneSchemaFile.d.mts +0 -11
  118. package/dist/generateClient.d.mts +0 -7
  119. package/dist/generateClient.mjs +0 -97
  120. package/dist/getProjectInfo/getConfig.d.mts +0 -11
  121. package/dist/getProjectInfo/getConfig.mjs +0 -29
  122. package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +0 -4
  123. package/dist/postinstall.d.mts +0 -1
  124. package/dist/postinstall.mjs +0 -24
  125. package/templates/controller.ejs +0 -51
  126. package/templates/service.ejs +0 -27
  127. package/templates/worker.ejs +0 -24
  128. /package/dist/getProjectInfo/{importUncachedModule.d.mts → getConfig/importUncachedModule.d.mts} +0 -0
  129. /package/dist/getProjectInfo/{importUncachedModuleWorker.d.mts → getConfig/importUncachedModuleWorker.d.mts} +0 -0
  130. /package/dist/getProjectInfo/{importUncachedModuleWorker.mjs → getConfig/importUncachedModuleWorker.mjs} +0 -0
@@ -0,0 +1,189 @@
1
+ import path from 'node:path';
2
+ import * as chokidar from 'chokidar';
3
+ import { getProjectFullSchema } from './getProjectFullSchema.mjs';
4
+ import { generate } from './generate.mjs';
5
+ import { locateSegments } from '../locateSegments.mjs';
6
+ import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
7
+ const THROTTLE_DELAY = 5000;
8
+ export class VovkGenerate {
9
+ #cliGenerateOptions;
10
+ #projectInfo;
11
+ #forceNothingWrittenLog;
12
+ constructor({ cliGenerateOptions, projectInfo, forceNothingWrittenLog, }) {
13
+ this.#cliGenerateOptions = cliGenerateOptions;
14
+ this.#projectInfo = projectInfo;
15
+ this.#forceNothingWrittenLog = forceNothingWrittenLog ?? true;
16
+ }
17
+ start() {
18
+ const { watch } = this.#cliGenerateOptions;
19
+ if (watch) {
20
+ const throttleDelay = typeof watch === 'boolean' ? THROTTLE_DELAY : parseFloat(watch) * 1e3 || THROTTLE_DELAY;
21
+ this.watch({ throttleDelay });
22
+ }
23
+ else {
24
+ this.generate();
25
+ }
26
+ }
27
+ async generate() {
28
+ const fullSchema = await this.getFullSchema();
29
+ const { log, config, apiDirAbsolutePath } = this.#projectInfo;
30
+ const locatedSegments = await locateSegments({ dir: apiDirAbsolutePath, config, log });
31
+ await generate({
32
+ projectInfo: this.#projectInfo,
33
+ fullSchema,
34
+ forceNothingWrittenLog: this.#forceNothingWrittenLog,
35
+ cliGenerateOptions: this.#cliGenerateOptions,
36
+ locatedSegments,
37
+ });
38
+ }
39
+ async getFullSchema() {
40
+ const { log, config, cwd, isNextInstalled } = this.#projectInfo;
41
+ const { schemaPath } = this.#cliGenerateOptions;
42
+ const fullSchema = await getProjectFullSchema({
43
+ schemaOutAbsolutePath: path.resolve(cwd, schemaPath ?? config.schemaOutDir),
44
+ isNextInstalled,
45
+ log,
46
+ });
47
+ return fullSchema;
48
+ }
49
+ watch({ throttleDelay }) {
50
+ const { openapiSpec, schemaPath } = this.#cliGenerateOptions;
51
+ const { log, cwd, config } = this.#projectInfo;
52
+ if (openapiSpec) {
53
+ log.debug(`Watching OpenAPI spec: ${openapiSpec}`);
54
+ this.watchOpenApiSpec({ openApiSpec: openapiSpec, throttleDelay });
55
+ }
56
+ else {
57
+ const resolvedSchemaPath = path.resolve(cwd, schemaPath ?? config.schemaOutDir);
58
+ log.debug(`Watching schema directory: ${resolvedSchemaPath}`);
59
+ this.watchSchema({ schemaPath: resolvedSchemaPath, throttleDelay });
60
+ }
61
+ }
62
+ watchSchema({ schemaPath, throttleDelay }) {
63
+ const { log } = this.#projectInfo;
64
+ let lastGenerationTime = 0;
65
+ chokidar
66
+ .watch(schemaPath, {
67
+ persistent: true,
68
+ ignoreInitial: true,
69
+ })
70
+ .on('all', (event, path) => {
71
+ if (event === 'change' || event === 'add' || event === 'ready' || event === 'unlink') {
72
+ log.debug(`Schema file ${event}: ${path}`);
73
+ const now = Date.now();
74
+ const shouldGenerateImmediately = now - lastGenerationTime > throttleDelay;
75
+ const generateCode = async () => {
76
+ try {
77
+ lastGenerationTime = Date.now();
78
+ await this.generate();
79
+ log.debug(`Regenerated from schema changes`);
80
+ }
81
+ catch (error) {
82
+ log.error(`Failed to regenerate from schema: ${error instanceof Error ? error.message : String(error)}`);
83
+ }
84
+ };
85
+ // Generate immediately if it's been a while since the last generation
86
+ if (shouldGenerateImmediately) {
87
+ generateCode();
88
+ }
89
+ }
90
+ });
91
+ }
92
+ watchOpenApiSpec({ openApiSpec, throttleDelay }) {
93
+ const fileSpecs = openApiSpec.filter((spec) => !spec.startsWith('http://') && !spec.startsWith('https://'));
94
+ const remoteSpecs = openApiSpec.filter((spec) => spec.startsWith('http://') || spec.startsWith('https://'));
95
+ if (fileSpecs.length) {
96
+ this.watchOpenApiSpecLocal({
97
+ openApiSpecPaths: fileSpecs,
98
+ throttleDelay,
99
+ });
100
+ }
101
+ if (remoteSpecs.length) {
102
+ remoteSpecs.forEach((spec) => {
103
+ this.watchOpenApiSpecRemote({
104
+ openApiSpecUrl: spec,
105
+ throttleDelay,
106
+ });
107
+ });
108
+ }
109
+ }
110
+ watchOpenApiSpecLocal({ openApiSpecPaths, throttleDelay }) {
111
+ const { log, cwd } = this.#projectInfo;
112
+ let lastGenerationTime = 0;
113
+ chokidar
114
+ .watch(openApiSpecPaths, {
115
+ cwd,
116
+ persistent: true,
117
+ ignoreInitial: true,
118
+ })
119
+ .on('all', (event, path) => {
120
+ if (event === 'change' || event === 'add' || event === 'ready' || event === 'unlink') {
121
+ log.debug(`OpenAPI spec file changed: ${path}`);
122
+ const now = Date.now();
123
+ const shouldGenerateImmediately = now - lastGenerationTime > throttleDelay;
124
+ const generateCode = async () => {
125
+ try {
126
+ lastGenerationTime = Date.now();
127
+ await this.generate();
128
+ log.debug(`Regenerated from OpenAPI spec changes`);
129
+ }
130
+ catch (error) {
131
+ log.error(`Failed to regenerate from OpenAPI spec: ${error instanceof Error ? error.message : String(error)}`);
132
+ }
133
+ };
134
+ // Generate immediately if it's been a while since the last generation
135
+ if (shouldGenerateImmediately) {
136
+ generateCode();
137
+ }
138
+ }
139
+ });
140
+ }
141
+ watchOpenApiSpecRemote({ openApiSpecUrl, throttleDelay }) {
142
+ const { log } = this.#projectInfo;
143
+ let lastContent = null;
144
+ let isPolling = false;
145
+ log.info(`Polling remote OpenAPI spec at ${chalkHighlightThing(openApiSpecUrl)} every ${throttleDelay}ms`);
146
+ const pollRemoteSpec = async () => {
147
+ if (isPolling)
148
+ return;
149
+ isPolling = true;
150
+ try {
151
+ const response = await fetch(openApiSpecUrl, {
152
+ headers: {
153
+ Accept: 'application/json, application/yaml',
154
+ },
155
+ });
156
+ if (!response.ok) {
157
+ log.error(`Failed to fetch OpenAPI spec: ${response.status} ${response.statusText}`);
158
+ return;
159
+ }
160
+ const content = await response.text();
161
+ if (lastContent === null) {
162
+ lastContent = content;
163
+ return;
164
+ }
165
+ if (content !== lastContent) {
166
+ log.info(`Remote OpenAPI spec changed at ${chalkHighlightThing(openApiSpecUrl)}`);
167
+ lastContent = content;
168
+ try {
169
+ await this.generate();
170
+ log.debug(`Regenerated from remote OpenAPI spec changes`);
171
+ }
172
+ catch (error) {
173
+ log.error(`Failed to regenerate from OpenAPI spec: ${error instanceof Error ? error.message : String(error)}`);
174
+ }
175
+ }
176
+ }
177
+ catch (error) {
178
+ log.error(`Error polling OpenAPI spec: ${error instanceof Error ? error.message : String(error)}`);
179
+ }
180
+ finally {
181
+ isPolling = false;
182
+ }
183
+ };
184
+ // Initial fetch
185
+ pollRemoteSpec();
186
+ // Set up polling
187
+ setInterval(pollRemoteSpec, throttleDelay);
188
+ }
189
+ }
@@ -0,0 +1,7 @@
1
+ import type { PackageJson } from 'type-fest';
2
+ import type { ProjectInfo } from '../getProjectInfo/index.mjs';
3
+ export default function mergePackages({ rootPackageJson, packages, }: {
4
+ rootPackageJson: PackageJson;
5
+ packages: (PackageJson | undefined)[];
6
+ log: ProjectInfo['log'];
7
+ }): Promise<PackageJson>;
@@ -0,0 +1,55 @@
1
+ import pick from 'lodash/pick.js';
2
+ function mergeTwoPackageJsons(base, additional) {
3
+ const merged = { ...base, ...additional };
4
+ // TODO: Add deep merge for all properties
5
+ if (base.scripts || additional.scripts) {
6
+ merged.scripts = { ...base.scripts, ...additional.scripts };
7
+ }
8
+ if (base.dependencies || additional.dependencies) {
9
+ merged.dependencies = { ...base.dependencies, ...additional.dependencies };
10
+ }
11
+ if (base.devDependencies || additional.devDependencies) {
12
+ merged.devDependencies = { ...base.devDependencies, ...additional.devDependencies };
13
+ }
14
+ if (base.peerDependencies || additional.peerDependencies) {
15
+ merged.peerDependencies = { ...base.peerDependencies, ...additional.peerDependencies };
16
+ }
17
+ return merged;
18
+ }
19
+ export default async function mergePackages({ rootPackageJson, packages, }) {
20
+ const defaultPackageJson = {
21
+ main: './index.cjs',
22
+ module: './index.mjs',
23
+ types: './index.d.mts',
24
+ exports: {
25
+ '.': {
26
+ import: './index.mjs',
27
+ require: './index.cjs',
28
+ },
29
+ './schema': {
30
+ import: './schema.cjs',
31
+ require: './schema.cjs',
32
+ types: './schema.d.cts',
33
+ },
34
+ },
35
+ };
36
+ const pickedPackageJson = pick(rootPackageJson, [
37
+ 'name',
38
+ 'version',
39
+ 'description',
40
+ 'author',
41
+ 'contributors',
42
+ 'license',
43
+ 'repository',
44
+ 'homepage',
45
+ 'bugs',
46
+ 'keywords',
47
+ ]);
48
+ let result = { ...pickedPackageJson, ...defaultPackageJson };
49
+ for (const pkg of packages) {
50
+ if (pkg) {
51
+ result = mergeTwoPackageJsons(result, pkg);
52
+ }
53
+ }
54
+ return result;
55
+ }
@@ -0,0 +1,37 @@
1
+ import { type VovkSchema, type VovkStrictConfig } from 'vovk';
2
+ import type { PackageJson } from 'type-fest';
3
+ import type { ProjectInfo } from '../getProjectInfo/index.mjs';
4
+ import type { ClientTemplateFile } from './getClientTemplateFiles.mjs';
5
+ import type { ClientImports } from './getTemplateClientImports.mjs';
6
+ import type { Segment } from '../locateSegments.mjs';
7
+ export declare function normalizeOutTemplatePath(out: string, packageJson: PackageJson): string;
8
+ export default function writeOneClientFile({ cwd, projectInfo, clientTemplateFile, fullSchema, prettifyClient, segmentName, imports, templateContent, matterResult: { data, content }, package: packageJson, readme, isEnsuringClient, outCwdRelativeDir, templateDef, locatedSegments, isNodeNextResolution, hasMixins, isVovkProject, vovkCliPackage, isBundle, origin, }: {
9
+ cwd: string;
10
+ projectInfo: ProjectInfo;
11
+ clientTemplateFile: ClientTemplateFile;
12
+ fullSchema: VovkSchema;
13
+ prettifyClient: boolean;
14
+ segmentName: string | null;
15
+ imports: ClientImports;
16
+ templateContent: string;
17
+ matterResult: {
18
+ data: {
19
+ imports?: string[];
20
+ };
21
+ content: string;
22
+ };
23
+ package: PackageJson;
24
+ readme: VovkStrictConfig['bundle']['readme'];
25
+ isEnsuringClient: boolean;
26
+ outCwdRelativeDir: string;
27
+ templateDef: VovkStrictConfig['clientTemplateDefs'][string];
28
+ locatedSegments: Segment[];
29
+ isNodeNextResolution: boolean;
30
+ hasMixins: boolean;
31
+ isVovkProject: boolean;
32
+ vovkCliPackage: PackageJson;
33
+ isBundle: boolean;
34
+ origin: string | null;
35
+ }): Promise<{
36
+ written: boolean;
37
+ }>;
@@ -0,0 +1,122 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'node:path';
3
+ import ejs from 'ejs';
4
+ import _ from 'lodash';
5
+ import { createCodeExamples, VovkSchemaIdEnum } from 'vovk';
6
+ import * as YAML from 'yaml';
7
+ import TOML from '@iarna/toml';
8
+ import prettify from '../utils/prettify.mjs';
9
+ import { ROOT_SEGMENT_FILE_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
10
+ import { compileJSONSchemaToTypeScriptType } from '../utils/compileJSONSchemaToTypeScriptType.mjs';
11
+ export function normalizeOutTemplatePath(out, packageJson) {
12
+ return out.replace('[package_name]', packageJson.name?.replace(/-/g, '_') ?? 'my_package_name');
13
+ }
14
+ export default async function writeOneClientFile({ cwd, projectInfo, clientTemplateFile, fullSchema, prettifyClient, segmentName, imports, templateContent, matterResult: { data, content }, package: packageJson, readme, isEnsuringClient, outCwdRelativeDir, templateDef, locatedSegments, isNodeNextResolution, hasMixins, isVovkProject, vovkCliPackage, isBundle, origin, }) {
15
+ const { config, apiRoot } = projectInfo;
16
+ const { templateFilePath, relativeDir } = clientTemplateFile;
17
+ const locatedSegmentsByName = _.keyBy(locatedSegments, 'segmentName');
18
+ const outPath = normalizeOutTemplatePath(path.resolve(cwd, outCwdRelativeDir, typeof segmentName === 'string' ? segmentName || ROOT_SEGMENT_FILE_NAME : '', relativeDir, path.basename(templateFilePath).replace('.ejs', '')), packageJson);
19
+ let placeholder = `// This is a temporary placeholder to avoid compilation errors if client is imported before it's generated.
20
+ // If you still see this text, the client is not generated yet because of an unknown problem.
21
+ // Feel free to report an issue at https://github.com/finom/vovk/issues`;
22
+ placeholder = outPath.endsWith('.py') ? placeholder.replace(/\/\//g, '#') : placeholder;
23
+ const getFirstLineBanner = (type = 'c') => {
24
+ const text = `auto-generated by vovk-cli v${vovkCliPackage.version} at ${new Date().toISOString()}`;
25
+ switch (type) {
26
+ case 'html':
27
+ return `<!-- ${text} -->`;
28
+ case 'sh':
29
+ return `# ${text}`;
30
+ case 'c':
31
+ return `// ${text}`;
32
+ }
33
+ };
34
+ // Data for the EJS templates:
35
+ const t = {
36
+ _, // lodash
37
+ hasMixins,
38
+ isVovkProject,
39
+ package: packageJson,
40
+ readme,
41
+ ROOT_SEGMENT_FILE_NAME,
42
+ apiRoot: origin ? `${origin}/${config.rootEntry}` : apiRoot,
43
+ imports,
44
+ schema: fullSchema,
45
+ VovkSchemaIdEnum,
46
+ createCodeExamples,
47
+ compileJSONSchemaToTypeScriptType,
48
+ YAML,
49
+ TOML,
50
+ getFirstLineBanner,
51
+ nodeNextResolutionExt: {
52
+ ts: isNodeNextResolution ? '.ts' : '',
53
+ js: isNodeNextResolution ? '.js' : '',
54
+ cjs: isNodeNextResolution ? '.cjs' : '',
55
+ mjs: isNodeNextResolution ? '.mjs' : '',
56
+ },
57
+ schemaOutDir: typeof segmentName === 'string'
58
+ ? path.relative(path.join(outCwdRelativeDir, segmentName || ROOT_SEGMENT_FILE_NAME), config.schemaOutDir)
59
+ : path.relative(outCwdRelativeDir, config.schemaOutDir),
60
+ segmentMeta: Object.fromEntries(Object.values(fullSchema.segments).map(({ segmentName: sName, forceApiRoot }) => {
61
+ const { routeFilePath = null } = locatedSegmentsByName[sName] ?? {};
62
+ const segmentImportPath = routeFilePath
63
+ ? path.relative(path.resolve(cwd, outCwdRelativeDir, typeof segmentName === 'string' ? segmentName || ROOT_SEGMENT_FILE_NAME : '.'), path.resolve(cwd, routeFilePath))
64
+ : null;
65
+ const segmentConfig = {
66
+ ...(config.segmentConfig ? config.segmentConfig[sName] : {}),
67
+ ...(templateDef.segmentConfig ? templateDef.segmentConfig[sName] : {}),
68
+ };
69
+ const { origin: segmentConfigOrigin, rootEntry: segmentConfigRootEntry, segmentNameOverride } = segmentConfig;
70
+ const reExports = { ...segmentConfig.reExports, ...(isBundle ? projectInfo.config.bundle.reExports : {}) };
71
+ return [
72
+ sName,
73
+ {
74
+ forceApiRoot: forceApiRoot ??
75
+ (segmentConfigOrigin || segmentConfigRootEntry
76
+ ? `${segmentConfigOrigin ?? origin}/${segmentConfigRootEntry ?? config.rootEntry}`
77
+ : null),
78
+ routeFilePath,
79
+ segmentImportPath,
80
+ segmentNameOverride,
81
+ reExports: _.mapValues(reExports ?? {}, (p) => p.startsWith('.')
82
+ ? path.relative(path.join(outCwdRelativeDir, typeof segmentName === 'string' ? segmentName || ROOT_SEGMENT_FILE_NAME : '.'), path.resolve(cwd, p))
83
+ : p),
84
+ },
85
+ ];
86
+ })),
87
+ };
88
+ if (data.imports instanceof Array) {
89
+ for (const imp of data.imports) {
90
+ t.imports = {
91
+ ...t.imports,
92
+ [imp]: await import(imp),
93
+ };
94
+ }
95
+ }
96
+ // Render the template
97
+ let rendered = templateFilePath.endsWith('.ejs')
98
+ ? await ejs.render(content, { t }, {
99
+ filename: templateFilePath,
100
+ async: true,
101
+ })
102
+ : templateContent;
103
+ // Optionally prettify
104
+ if (prettifyClient) {
105
+ rendered = await prettify(rendered, outPath);
106
+ }
107
+ if (isEnsuringClient) {
108
+ rendered = rendered + `\n\n${placeholder}`;
109
+ }
110
+ // Read existing file content to compare
111
+ const existingContent = await fs.readFile(outPath, 'utf-8').catch(() => null);
112
+ // Determine if we need to rewrite the file, ignore 1st line
113
+ const needsWriting = isEnsuringClient
114
+ ? !existingContent
115
+ : !existingContent ||
116
+ existingContent.trim().split('\n').slice(1).join('\n') !== rendered.trim().split('\n').slice(1).join('\n');
117
+ if (needsWriting) {
118
+ await fs.mkdir(path.dirname(outPath), { recursive: true });
119
+ await fs.writeFile(outPath, rendered, 'utf-8');
120
+ }
121
+ return { written: needsWriting };
122
+ }
@@ -0,0 +1,5 @@
1
+ export default function getConfigAbsolutePaths({ cwd, configPath, relativePath, }: {
2
+ cwd: string;
3
+ configPath?: string;
4
+ relativePath?: string;
5
+ }): Promise<string[]>;
@@ -1,6 +1,9 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- export default async function getConfigAbsolutePaths({ cwd, relativePath, }) {
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,3 +1,3 @@
1
1
  export default function getRelativeSrcRoot({ cwd }: {
2
2
  cwd: string;
3
- }): Promise<"." | "./src">;
3
+ }): Promise<"." | "./src" | null>;
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import getFileSystemEntryType, { FileSystemEntryType } from '../utils/getFileSystemEntryType.mjs';
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.
5
5
  if ((await getFileSystemEntryType(path.join(cwd, 'app'))) === FileSystemEntryType.DIRECTORY) {
@@ -8,5 +8,5 @@ export default async function getRelativeSrcRoot({ cwd }) {
8
8
  else if ((await getFileSystemEntryType(path.join(cwd, 'src/app'))) === FileSystemEntryType.DIRECTORY) {
9
9
  return './src';
10
10
  }
11
- throw new Error(`${cwd} Could not find app router directory. Check Next.js docs for more info.`);
11
+ return null;
12
12
  }
@@ -0,0 +1,22 @@
1
+ import type { VovkStrictConfig } from 'vovk';
2
+ export declare enum BuiltInTemplateName {
3
+ ts = "ts",
4
+ cjs = "cjs",
5
+ mjs = "mjs",
6
+ schemaTs = "schemaTs",
7
+ schemaCjs = "schemaCjs",
8
+ schemaJson = "schemaJson",
9
+ standaloneTypesTs = "standaloneTypesTs",
10
+ readme = "readme",
11
+ packageJson = "packageJson",
12
+ mixins = "mixins",
13
+ rsSrc = "rsSrc",
14
+ rsPkg = "rsPkg",
15
+ rsReadme = "rsReadme",
16
+ rs = "rs",
17
+ pySrc = "pySrc",
18
+ pyPkg = "pyPkg",
19
+ pyReadme = "pyReadme",
20
+ py = "py"
21
+ }
22
+ export default function getTemplateDefs(userTemplateDefs?: VovkStrictConfig['clientTemplateDefs']): VovkStrictConfig['clientTemplateDefs'];
@@ -0,0 +1,145 @@
1
+ export var BuiltInTemplateName;
2
+ (function (BuiltInTemplateName) {
3
+ // ts/js
4
+ BuiltInTemplateName["ts"] = "ts";
5
+ BuiltInTemplateName["cjs"] = "cjs";
6
+ BuiltInTemplateName["mjs"] = "mjs";
7
+ // schema
8
+ BuiltInTemplateName["schemaTs"] = "schemaTs";
9
+ BuiltInTemplateName["schemaCjs"] = "schemaCjs";
10
+ BuiltInTemplateName["schemaJson"] = "schemaJson";
11
+ // types
12
+ BuiltInTemplateName["standaloneTypesTs"] = "standaloneTypesTs";
13
+ // misc
14
+ BuiltInTemplateName["readme"] = "readme";
15
+ BuiltInTemplateName["packageJson"] = "packageJson";
16
+ BuiltInTemplateName["mixins"] = "mixins";
17
+ // other languages (packages installed separately)
18
+ BuiltInTemplateName["rsSrc"] = "rsSrc";
19
+ BuiltInTemplateName["rsPkg"] = "rsPkg";
20
+ BuiltInTemplateName["rsReadme"] = "rsReadme";
21
+ BuiltInTemplateName["rs"] = "rs";
22
+ BuiltInTemplateName["pySrc"] = "pySrc";
23
+ BuiltInTemplateName["pyPkg"] = "pyPkg";
24
+ BuiltInTemplateName["pyReadme"] = "pyReadme";
25
+ BuiltInTemplateName["py"] = "py";
26
+ })(BuiltInTemplateName || (BuiltInTemplateName = {}));
27
+ export default function getTemplateDefs(userTemplateDefs = {}) {
28
+ const defs = {};
29
+ const builtInDefs = {
30
+ [BuiltInTemplateName.ts]: {
31
+ templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.ts}/`,
32
+ requires: {
33
+ [BuiltInTemplateName.schemaTs]: '.',
34
+ [BuiltInTemplateName.mixins]: '.', // used conditionally if OpenAPI mixins are used
35
+ },
36
+ },
37
+ [BuiltInTemplateName.cjs]: {
38
+ templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.cjs}/`,
39
+ requires: {
40
+ [BuiltInTemplateName.schemaCjs]: '.',
41
+ [BuiltInTemplateName.mixins]: '.', // used conditionally if OpenAPI mixins are used
42
+ },
43
+ },
44
+ [BuiltInTemplateName.mjs]: {
45
+ templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.mjs}/`,
46
+ requires: {
47
+ [BuiltInTemplateName.schemaCjs]: '.',
48
+ [BuiltInTemplateName.mixins]: '.', // used conditionally if OpenAPI mixins are used
49
+ },
50
+ },
51
+ [BuiltInTemplateName.schemaTs]: {
52
+ templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.schemaTs}/`,
53
+ },
54
+ [BuiltInTemplateName.schemaCjs]: {
55
+ templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.schemaCjs}/`,
56
+ },
57
+ [BuiltInTemplateName.schemaJson]: {
58
+ templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.schemaJson}/`,
59
+ },
60
+ [BuiltInTemplateName.readme]: {
61
+ templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.readme}/`,
62
+ },
63
+ [BuiltInTemplateName.packageJson]: {
64
+ templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.packageJson}/`,
65
+ },
66
+ [BuiltInTemplateName.mixins]: {
67
+ templatePath: `vovk-cli/client-templates/${BuiltInTemplateName.mixins}/`,
68
+ },
69
+ [BuiltInTemplateName.rsSrc]: {
70
+ templatePath: `vovk-rust/client-templates/${BuiltInTemplateName.rsSrc}/`,
71
+ requires: {
72
+ [BuiltInTemplateName.schemaJson]: './',
73
+ },
74
+ },
75
+ [BuiltInTemplateName.rsPkg]: {
76
+ templatePath: `vovk-rust/client-templates/${BuiltInTemplateName.rsPkg}/`,
77
+ },
78
+ [BuiltInTemplateName.rsReadme]: {
79
+ templatePath: `vovk-rust/client-templates/${BuiltInTemplateName.rsReadme}/`,
80
+ },
81
+ [BuiltInTemplateName.rs]: {
82
+ composedClient: {
83
+ outDir: 'dist_rust',
84
+ },
85
+ requires: {
86
+ [BuiltInTemplateName.rsSrc]: './src/',
87
+ [BuiltInTemplateName.rsPkg]: './',
88
+ [BuiltInTemplateName.rsReadme]: './',
89
+ },
90
+ },
91
+ [BuiltInTemplateName.pySrc]: {
92
+ templatePath: `vovk-python/client-templates/${BuiltInTemplateName.pySrc}/`,
93
+ requires: {
94
+ [BuiltInTemplateName.schemaJson]: '.',
95
+ },
96
+ },
97
+ [BuiltInTemplateName.pyPkg]: {
98
+ templatePath: `vovk-python/client-templates/${BuiltInTemplateName.pyPkg}/`,
99
+ },
100
+ [BuiltInTemplateName.pyReadme]: {
101
+ templatePath: `vovk-python/client-templates/${BuiltInTemplateName.pyReadme}/`,
102
+ },
103
+ [BuiltInTemplateName.py]: {
104
+ composedClient: {
105
+ outDir: 'dist_python',
106
+ },
107
+ requires: {
108
+ [BuiltInTemplateName.pySrc]: './src/[package_name]/',
109
+ [BuiltInTemplateName.pyPkg]: './',
110
+ [BuiltInTemplateName.pyReadme]: './',
111
+ },
112
+ },
113
+ };
114
+ for (const [name, templateDef] of Object.entries(userTemplateDefs)) {
115
+ if ('extends' in templateDef) {
116
+ if (templateDef.extends) {
117
+ const builtIn = builtInDefs[templateDef.extends];
118
+ if (!builtIn) {
119
+ throw new Error(`Unknown template extends: ${templateDef.extends}`);
120
+ }
121
+ defs[name] = {
122
+ ...builtIn,
123
+ ...templateDef,
124
+ composedClient: {
125
+ ...builtIn.composedClient,
126
+ ...templateDef.composedClient,
127
+ },
128
+ segmentedClient: {
129
+ ...builtIn.segmentedClient,
130
+ ...templateDef.segmentedClient,
131
+ },
132
+ segmentConfig: {
133
+ ...builtIn.segmentConfig,
134
+ ...templateDef.segmentConfig,
135
+ },
136
+ // 'requires' and other props will be overridden
137
+ };
138
+ }
139
+ }
140
+ else {
141
+ defs[name] = templateDef;
142
+ }
143
+ }
144
+ return { ...builtInDefs, ...defs };
145
+ }
@@ -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,10 +1,11 @@
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
- return { userConfig: null, configAbsolutePaths, error: new Error('No config file found') };
8
+ return { userConfig: null, configAbsolutePaths };
8
9
  }
9
10
  const configPath = configAbsolutePaths[0];
10
11
  // TODO explain
@@ -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 };