vovk-cli 0.0.1-draft.99 → 0.0.3

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 (164) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +24 -16
  3. package/client-templates/jsBase/index.d.ts.ejs +21 -0
  4. package/client-templates/jsBase/index.js.ejs +18 -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/openapiJs/openapi.d.ts.ejs +4 -0
  8. package/client-templates/openapiJs/openapi.js.ejs +4 -0
  9. package/client-templates/openapiJson/openapi.json.ejs +1 -0
  10. package/client-templates/openapiTs/openapi.ts.ejs +4 -0
  11. package/client-templates/packageJson/package.json.ejs +1 -0
  12. package/client-templates/readme/README.md.ejs +39 -0
  13. package/client-templates/schemaJs/schema.d.ts.ejs +10 -0
  14. package/client-templates/schemaJs/schema.js.ejs +29 -0
  15. package/client-templates/schemaJson/schema.json.ejs +1 -0
  16. package/client-templates/schemaTs/schema.ts.ejs +28 -0
  17. package/client-templates/tsBase/index.ts.ejs +27 -0
  18. package/dist/bundle/index.d.mts +8 -0
  19. package/dist/bundle/index.mjs +76 -0
  20. package/dist/dev/{diffSchema.d.mts → diffSegmentSchema.d.mts} +3 -3
  21. package/dist/dev/{diffSchema.mjs → diffSegmentSchema.mjs} +1 -1
  22. package/dist/dev/ensureSchemaFiles.d.mts +2 -2
  23. package/dist/dev/ensureSchemaFiles.mjs +16 -47
  24. package/dist/dev/index.d.mts +2 -0
  25. package/dist/dev/index.mjs +113 -64
  26. package/dist/dev/logDiffResult.d.mts +2 -2
  27. package/dist/dev/logDiffResult.mjs +6 -6
  28. package/dist/dev/writeMetaJson.d.mts +2 -0
  29. package/dist/dev/writeMetaJson.mjs +19 -0
  30. package/dist/dev/writeOneSegmentSchemaFile.d.mts +12 -0
  31. package/dist/dev/{writeOneSchemaFile.mjs → writeOneSegmentSchemaFile.mjs} +11 -8
  32. package/dist/generate/ensureClient.d.mts +2 -4
  33. package/dist/generate/ensureClient.mjs +26 -28
  34. package/dist/generate/generate.d.mts +13 -0
  35. package/dist/generate/generate.mjs +307 -0
  36. package/dist/generate/getClientTemplateFiles.d.mts +20 -0
  37. package/dist/generate/getClientTemplateFiles.mjs +85 -0
  38. package/dist/generate/getProjectFullSchema.d.mts +9 -0
  39. package/dist/generate/getProjectFullSchema.mjs +64 -0
  40. package/dist/generate/getTemplateClientImports.d.mts +18 -0
  41. package/dist/generate/getTemplateClientImports.mjs +36 -0
  42. package/dist/generate/index.d.mts +31 -11
  43. package/dist/generate/index.mjs +177 -85
  44. package/dist/generate/writeOneClientFile.d.mts +43 -0
  45. package/dist/generate/writeOneClientFile.mjs +150 -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/getConfig/getRelativeSrcRoot.d.mts +3 -0
  49. package/dist/getProjectInfo/{getRelativeSrcRoot.mjs → getConfig/getRelativeSrcRoot.mjs} +3 -3
  50. package/dist/getProjectInfo/getConfig/getTemplateDefs.d.mts +25 -0
  51. package/dist/getProjectInfo/getConfig/getTemplateDefs.mjs +168 -0
  52. package/dist/getProjectInfo/getConfig/getUserConfig.d.mts +9 -0
  53. package/dist/getProjectInfo/getConfig/getUserConfig.mjs +142 -0
  54. package/dist/getProjectInfo/getConfig/index.d.mts +15 -0
  55. package/dist/getProjectInfo/getConfig/index.mjs +92 -0
  56. package/dist/getProjectInfo/getMetaSchema.d.mts +4 -0
  57. package/dist/getProjectInfo/getMetaSchema.mjs +12 -0
  58. package/dist/getProjectInfo/index.d.mts +12 -16
  59. package/dist/getProjectInfo/index.mjs +23 -29
  60. package/dist/index.d.mts +3 -3
  61. package/dist/index.mjs +119 -40
  62. package/dist/init/checkTSConfigForExperimentalDecorators.d.mts +1 -1
  63. package/dist/init/checkTSConfigForExperimentalDecorators.mjs +2 -2
  64. package/dist/init/createConfig.d.mts +5 -3
  65. package/dist/init/createConfig.mjs +77 -27
  66. package/dist/init/index.d.mts +2 -2
  67. package/dist/init/index.mjs +118 -103
  68. package/dist/init/installDependencies.d.mts +8 -6
  69. package/dist/init/installDependencies.mjs +7 -5
  70. package/dist/init/logUpdateDependenciesError.d.mts +6 -6
  71. package/dist/init/logUpdateDependenciesError.mjs +8 -4
  72. package/dist/init/updateDependenciesWithoutInstalling.d.mts +3 -3
  73. package/dist/init/updateDependenciesWithoutInstalling.mjs +41 -11
  74. package/dist/init/updateNPMScripts.d.mts +7 -2
  75. package/dist/init/updateNPMScripts.mjs +9 -5
  76. package/dist/init/updateTypeScriptConfig.d.mts +4 -1
  77. package/dist/init/updateTypeScriptConfig.mjs +12 -8
  78. package/dist/new/addClassToSegmentCode.d.mts +1 -1
  79. package/dist/new/addClassToSegmentCode.mjs +3 -3
  80. package/dist/new/addCommonTerms.d.mts +1 -1
  81. package/dist/new/addCommonTerms.mjs +1 -1
  82. package/dist/new/index.d.mts +2 -1
  83. package/dist/new/index.mjs +6 -5
  84. package/dist/new/newModule.d.mts +5 -3
  85. package/dist/new/newModule.mjs +34 -27
  86. package/dist/new/newSegment.d.mts +5 -2
  87. package/dist/new/newSegment.mjs +23 -16
  88. package/dist/new/render.d.mts +6 -3
  89. package/dist/new/render.mjs +15 -14
  90. package/dist/types.d.mts +64 -61
  91. package/dist/utils/chalkHighlightThing.d.mts +1 -1
  92. package/dist/utils/chalkHighlightThing.mjs +1 -1
  93. package/dist/utils/compileJSONSchemaToTypeScriptType.d.mts +5 -0
  94. package/dist/utils/compileJSONSchemaToTypeScriptType.mjs +9 -0
  95. package/dist/utils/compileTs.d.mts +12 -0
  96. package/dist/utils/compileTs.mjs +263 -0
  97. package/dist/utils/debounceWithArgs.d.mts +3 -2
  98. package/dist/utils/debounceWithArgs.mjs +1 -1
  99. package/dist/utils/formatLoggedSegmentName.d.mts +3 -1
  100. package/dist/utils/formatLoggedSegmentName.mjs +4 -3
  101. package/dist/utils/generateFnName.d.mts +23 -0
  102. package/dist/utils/generateFnName.mjs +78 -0
  103. package/dist/utils/getAvailablePort.d.mts +1 -2
  104. package/dist/utils/getAvailablePort.mjs +1 -2
  105. package/dist/utils/getFileSystemEntryType.d.mts +1 -1
  106. package/dist/utils/getFileSystemEntryType.mjs +1 -1
  107. package/dist/utils/getLogger.d.mts +1 -1
  108. package/dist/utils/getLogger.mjs +1 -1
  109. package/dist/utils/getNPMPackageMetadata.d.mts +2 -3
  110. package/dist/utils/getNPMPackageMetadata.mjs +1 -1
  111. package/dist/utils/getPackageJson.d.mts +3 -0
  112. package/dist/utils/getPackageJson.mjs +23 -0
  113. package/dist/utils/getPublicModuleNameFromPath.d.mts +4 -0
  114. package/dist/utils/getPublicModuleNameFromPath.mjs +9 -0
  115. package/dist/utils/locateSegments.d.mts +12 -0
  116. package/dist/{locateSegments.mjs → utils/locateSegments.mjs} +14 -7
  117. package/dist/utils/normalizeOpenAPIMixin.d.mts +15 -0
  118. package/dist/utils/normalizeOpenAPIMixin.mjs +96 -0
  119. package/dist/utils/pickSegmentFullSchema.d.mts +3 -0
  120. package/dist/utils/pickSegmentFullSchema.mjs +15 -0
  121. package/dist/utils/prettify.d.mts +1 -1
  122. package/dist/utils/prettify.mjs +1 -1
  123. package/dist/utils/removeUnlistedDirectories.d.mts +9 -0
  124. package/dist/utils/removeUnlistedDirectories.mjs +60 -0
  125. package/dist/utils/resolveAbsoluteModulePath.d.mts +2 -0
  126. package/dist/utils/resolveAbsoluteModulePath.mjs +32 -0
  127. package/dist/utils/updateConfigProperty.d.mts +2 -0
  128. package/dist/utils/updateConfigProperty.mjs +132 -0
  129. package/module-templates/arktype/controller.ts.ejs +90 -0
  130. package/module-templates/type/controller.ts.ejs +80 -0
  131. package/module-templates/type/service.ts.ejs +43 -0
  132. package/module-templates/valibot/controller.ts.ejs +91 -0
  133. package/module-templates/zod/controller.ts.ejs +98 -0
  134. package/package.json +50 -25
  135. package/client-templates/main/main.cjs.ejs +0 -15
  136. package/client-templates/main/main.d.cts.ejs +0 -14
  137. package/client-templates/module/module.d.mts.ejs +0 -14
  138. package/client-templates/module/module.mjs.ejs +0 -24
  139. package/client-templates/python/__init__.py +0 -276
  140. package/client-templates/ts/index.ts.ejs +0 -25
  141. package/dist/dev/isSchemaEmpty.d.mts +0 -2
  142. package/dist/dev/isSchemaEmpty.mjs +0 -4
  143. package/dist/dev/writeOneSchemaFile.d.mts +0 -12
  144. package/dist/generate/getClientTemplates.d.mts +0 -16
  145. package/dist/generate/getClientTemplates.mjs +0 -42
  146. package/dist/getProjectInfo/getConfig.d.mts +0 -11
  147. package/dist/getProjectInfo/getConfig.mjs +0 -35
  148. package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +0 -4
  149. package/dist/getProjectInfo/getRelativeSrcRoot.d.mts +0 -3
  150. package/dist/getProjectInfo/getUserConfig.d.mts +0 -9
  151. package/dist/getProjectInfo/getUserConfig.mjs +0 -27
  152. package/dist/getProjectInfo/importUncachedModule.d.mts +0 -3
  153. package/dist/getProjectInfo/importUncachedModule.mjs +0 -40
  154. package/dist/getProjectInfo/importUncachedModuleWorker.d.mts +0 -1
  155. package/dist/getProjectInfo/importUncachedModuleWorker.mjs +0 -25
  156. package/dist/init/getTemplateFilesFromPackage.d.mts +0 -7
  157. package/dist/init/getTemplateFilesFromPackage.mjs +0 -59
  158. package/dist/initProgram.d.mts +0 -2
  159. package/dist/initProgram.mjs +0 -22
  160. package/dist/locateSegments.d.mts +0 -11
  161. package/dist/postinstall.d.mts +0 -1
  162. package/dist/postinstall.mjs +0 -21
  163. package/templates/controller.ejs +0 -50
  164. package/templates/service.ejs +0 -27
@@ -1,94 +1,186 @@
1
1
  import path from 'node:path';
2
- import fs from 'node:fs/promises';
3
- import ejs from 'ejs';
4
- import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
5
- import prettify from '../utils/prettify.mjs';
6
- import getClientTemplates from './getClientTemplates.mjs';
7
- import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
8
- import uniq from 'lodash/uniq.js';
9
- export default async function generate({ projectInfo, segments, segmentsSchema, forceNothingWrittenLog, templates, prettify: prettifyClient, fullSchema, }) {
10
- const generateFrom = templates ?? projectInfo.config.generateFrom;
11
- const noClient = templates?.[0] === 'none';
12
- const { config, cwd, log, clientImports, apiRoot } = projectInfo;
13
- const { clientOutDirAbsolutePath, templateFiles } = getClientTemplates({ config, cwd, generateFrom });
14
- // Ensure that each segment has a matching schema if it needs to be emitted:
15
- for (let i = 0; i < segments.length; i++) {
16
- const { segmentName } = segments[i];
17
- const schema = segmentsSchema[segmentName];
18
- if (!schema) {
19
- throw new Error(`Unable to generate client. No schema found for ${formatLoggedSegmentName(segmentName)}`);
2
+ import * as chokidar from 'chokidar';
3
+ import { getProjectFullSchema } from './getProjectFullSchema.mjs';
4
+ import { generate } from './generate.mjs';
5
+ import { locateSegments } from '../utils/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();
20
25
  }
21
- if (!schema.emitSchema)
22
- continue;
23
26
  }
24
- const now = Date.now();
25
- // Data for the EJS templates:
26
- const t = {
27
- apiRoot,
28
- imports: clientImports,
29
- segments,
30
- fullSchema: segmentsSchema,
31
- };
32
- // Process each template in parallel
33
- const processedTemplates = noClient
34
- ? []
35
- : await Promise.all(templateFiles.map(async ({ templatePath, outPath, templateName }) => {
36
- // Read the EJS template
37
- const templateContent = await fs.readFile(templatePath, 'utf-8');
38
- // Render the template
39
- let rendered = templatePath.endsWith('.ejs')
40
- ? ejs.render(templateContent, { t }, {
41
- filename: templatePath,
42
- })
43
- : templateContent;
44
- // Optionally prettify
45
- if (prettifyClient || config.prettifyClient) {
46
- rendered = await prettify(rendered, outPath);
47
- }
48
- // Read existing file content to compare
49
- const existingContent = await fs.readFile(outPath, 'utf-8').catch(() => '');
50
- // Determine if we need to rewrite the file, ignore 1st line
51
- const needsWriting = existingContent.trim().split('\n').slice(1).join('\n') !== rendered.trim().split('\n').slice(1).join('\n');
52
- return {
53
- outPath,
54
- rendered,
55
- needsWriting,
56
- templateName,
57
- };
58
- }));
59
- const usedTemplateNames = uniq(processedTemplates.filter(({ needsWriting }) => needsWriting).map(({ templateName }) => templateName));
60
- let fullSchemaNames = [];
61
- const DEFAULT_NAME = 'full-schema.json';
62
- if (fullSchema) {
63
- fullSchemaNames.push(typeof fullSchema === 'string' ? fullSchema : DEFAULT_NAME);
64
- fullSchemaNames.push(...templateFiles
65
- .filter(({ fullSchema }) => fullSchema)
66
- .map(({ fullSchema }) => (typeof fullSchema === 'string' ? fullSchema : DEFAULT_NAME)));
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
+ });
67
38
  }
68
- fullSchemaNames = uniq(fullSchemaNames);
69
- if (fullSchemaNames.length || usedTemplateNames.length > 0) {
70
- // Make sure the output directory exists
71
- await fs.mkdir(clientOutDirAbsolutePath, { recursive: true });
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
+ config,
47
+ });
48
+ return fullSchema;
72
49
  }
73
- if (fullSchemaNames.length) {
74
- await Promise.all(fullSchemaNames.map(async (name) => {
75
- const fullSchemaOutAbsolutePath = path.resolve(clientOutDirAbsolutePath, name);
76
- await fs.writeFile(fullSchemaOutAbsolutePath, JSON.stringify(segmentsSchema, null, 2));
77
- log.info(`Full schema has ben written to ${fullSchemaOutAbsolutePath}`);
78
- }));
50
+ watch({ throttleDelay }) {
51
+ const { openapiSpec, schemaPath } = this.#cliGenerateOptions;
52
+ const { log, cwd, config } = this.#projectInfo;
53
+ if (openapiSpec) {
54
+ log.debug(`Watching OpenAPI spec: ${openapiSpec}`);
55
+ this.watchOpenApiSpec({ openApiSpec: openapiSpec, throttleDelay });
56
+ }
57
+ else {
58
+ const resolvedSchemaPath = path.resolve(cwd, schemaPath ?? config.schemaOutDir);
59
+ log.debug(`Watching schema directory: ${resolvedSchemaPath}`);
60
+ this.watchSchema({ schemaPath: resolvedSchemaPath, throttleDelay });
61
+ }
79
62
  }
80
- if (usedTemplateNames.length === 0) {
81
- const logOrDebug = forceNothingWrittenLog ? log.info : log.debug;
82
- logOrDebug(`Client is up to date and doesn't need to be regenerated (${Date.now() - now}ms)`);
83
- return { written: false, path: clientOutDirAbsolutePath };
63
+ watchSchema({ schemaPath, throttleDelay }) {
64
+ const { log } = this.#projectInfo;
65
+ let lastGenerationTime = 0;
66
+ chokidar
67
+ .watch(schemaPath, {
68
+ persistent: true,
69
+ ignoreInitial: true,
70
+ })
71
+ .on('all', (event, path) => {
72
+ if (event === 'change' || event === 'add' || event === 'ready' || event === 'unlink') {
73
+ log.debug(`Schema file ${event}: ${path}`);
74
+ const now = Date.now();
75
+ const shouldGenerateImmediately = now - lastGenerationTime > throttleDelay;
76
+ const generateCode = async () => {
77
+ try {
78
+ lastGenerationTime = Date.now();
79
+ await this.generate();
80
+ log.debug(`Regenerated from schema changes`);
81
+ }
82
+ catch (error) {
83
+ log.error(`Failed to regenerate from schema: ${error instanceof Error ? error.message : String(error)}`);
84
+ }
85
+ };
86
+ // Generate immediately if it's been a while since the last generation
87
+ if (shouldGenerateImmediately) {
88
+ generateCode();
89
+ }
90
+ }
91
+ });
84
92
  }
85
- // Write updated files where needed
86
- await Promise.all(processedTemplates.map(({ outPath, rendered, needsWriting }) => {
87
- if (needsWriting) {
88
- return fs.writeFile(outPath, rendered);
93
+ watchOpenApiSpec({ openApiSpec, throttleDelay }) {
94
+ const fileSpecs = openApiSpec.filter((spec) => !spec.startsWith('http://') && !spec.startsWith('https://'));
95
+ const remoteSpecs = openApiSpec.filter((spec) => spec.startsWith('http://') || spec.startsWith('https://'));
96
+ if (fileSpecs.length) {
97
+ this.watchOpenApiSpecLocal({
98
+ openApiSpecPaths: fileSpecs,
99
+ throttleDelay,
100
+ });
101
+ }
102
+ if (remoteSpecs.length) {
103
+ remoteSpecs.forEach((spec) => {
104
+ this.watchOpenApiSpecRemote({
105
+ openApiSpecUrl: spec,
106
+ throttleDelay,
107
+ });
108
+ });
89
109
  }
90
- return null;
91
- }));
92
- log.info(`Client generated from template${usedTemplateNames.length !== 1 ? 's' : ''} ${chalkHighlightThing(usedTemplateNames.map((s) => `"${s}"`).join(', '))} at ${clientOutDirAbsolutePath} in ${Date.now() - now}ms`);
93
- return { written: true, path: clientOutDirAbsolutePath };
110
+ }
111
+ watchOpenApiSpecLocal({ openApiSpecPaths, throttleDelay }) {
112
+ const { log, cwd } = this.#projectInfo;
113
+ let lastGenerationTime = 0;
114
+ chokidar
115
+ .watch(openApiSpecPaths, {
116
+ cwd,
117
+ persistent: true,
118
+ ignoreInitial: false,
119
+ })
120
+ .on('all', (event, path) => {
121
+ if (event === 'change' || event === 'add' || event === 'ready' || event === 'unlink') {
122
+ log.debug(`OpenAPI spec file changed: ${path}`);
123
+ const now = Date.now();
124
+ const shouldGenerateImmediately = now - lastGenerationTime > throttleDelay;
125
+ const generateCode = async () => {
126
+ try {
127
+ lastGenerationTime = Date.now();
128
+ await this.generate();
129
+ log.debug(`Regenerated from OpenAPI spec changes`);
130
+ }
131
+ catch (error) {
132
+ log.error(`Failed to regenerate from OpenAPI spec: ${error instanceof Error ? error.message : String(error)}`);
133
+ }
134
+ };
135
+ // Generate immediately if it's been a while since the last generation
136
+ if (shouldGenerateImmediately) {
137
+ generateCode();
138
+ }
139
+ }
140
+ });
141
+ }
142
+ watchOpenApiSpecRemote({ openApiSpecUrl, throttleDelay }) {
143
+ const { log } = this.#projectInfo;
144
+ let lastContent = null;
145
+ let isPolling = false;
146
+ log.info(`Polling remote OpenAPI spec at ${chalkHighlightThing(openApiSpecUrl)} every ${throttleDelay}ms`);
147
+ const pollRemoteSpec = async () => {
148
+ if (isPolling)
149
+ return;
150
+ isPolling = true;
151
+ try {
152
+ const response = await fetch(openApiSpecUrl, {
153
+ headers: {
154
+ Accept: 'application/json, application/yaml',
155
+ },
156
+ });
157
+ if (!response.ok) {
158
+ log.error(`Failed to fetch OpenAPI spec: ${response.status} ${response.statusText}`);
159
+ return;
160
+ }
161
+ const content = await response.text();
162
+ if (content !== lastContent) {
163
+ log.info(`Remote OpenAPI spec changed at ${chalkHighlightThing(openApiSpecUrl)}`);
164
+ lastContent = content;
165
+ try {
166
+ await this.generate();
167
+ log.debug(`Regenerated from remote OpenAPI spec changes`);
168
+ }
169
+ catch (error) {
170
+ log.error(`Failed to regenerate from OpenAPI spec: ${error instanceof Error ? error.message : String(error)}`);
171
+ }
172
+ }
173
+ }
174
+ catch (error) {
175
+ log.error(`Error polling OpenAPI spec: ${error instanceof Error ? error.message : String(error)}`);
176
+ }
177
+ finally {
178
+ isPolling = false;
179
+ }
180
+ };
181
+ // Initial fetch
182
+ pollRemoteSpec();
183
+ // Set up polling
184
+ setInterval(pollRemoteSpec, throttleDelay);
185
+ }
94
186
  }
@@ -0,0 +1,43 @@
1
+ import type { VovkSchema } from 'vovk';
2
+ import { type VovkReadmeConfig, type VovkSamplesConfig, type VovkStrictConfig } from 'vovk/internal';
3
+ import type { PackageJson } from 'type-fest';
4
+ import type { OpenAPIObject } from 'openapi3-ts/oas31';
5
+ import type { ProjectInfo } from '../getProjectInfo/index.mjs';
6
+ import type { ClientTemplateFile } from './getClientTemplateFiles.mjs';
7
+ import type { Segment } from '../utils/locateSegments.mjs';
8
+ export declare function normalizeOutTemplatePath(out: string, packageJson: PackageJson): string;
9
+ export declare function writeOneClientFile({ cwd, projectInfo, clientTemplateFile, fullSchema, prettifyClient, segmentName, templateContent, matterResult: { data, content }, openAPIObject, package: packageJson, readme, samples, reExports, isEnsuringClient, outCwdRelativeDir, templateDef, locatedSegments, isNodeNextResolution, hasMixins, isVovkProject, vovkCliPackage, isBundle, origin, configKey, cliSchemaPath, projectConfig, }: {
10
+ cwd: string;
11
+ projectInfo: ProjectInfo;
12
+ clientTemplateFile: ClientTemplateFile;
13
+ fullSchema: VovkSchema;
14
+ prettifyClient: boolean;
15
+ segmentName: string | null;
16
+ templateContent: string;
17
+ matterResult: {
18
+ data: {
19
+ imports?: string[];
20
+ };
21
+ content: string;
22
+ };
23
+ openAPIObject: OpenAPIObject;
24
+ package: PackageJson;
25
+ readme: VovkReadmeConfig;
26
+ samples: VovkSamplesConfig;
27
+ reExports: VovkStrictConfig['outputConfig']['reExports'];
28
+ isEnsuringClient: boolean;
29
+ outCwdRelativeDir: string;
30
+ templateDef: VovkStrictConfig['clientTemplateDefs'][string];
31
+ locatedSegments: Segment[];
32
+ isNodeNextResolution: boolean;
33
+ hasMixins: boolean;
34
+ isVovkProject: boolean;
35
+ vovkCliPackage: PackageJson;
36
+ isBundle: boolean;
37
+ origin: string | null;
38
+ configKey: 'composedClient' | 'segmentedClient';
39
+ cliSchemaPath: string | null;
40
+ projectConfig: VovkStrictConfig;
41
+ }): Promise<{
42
+ written: boolean;
43
+ }>;
@@ -0,0 +1,150 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import ejs from 'ejs';
4
+ import _ from 'lodash';
5
+ import * as YAML from 'yaml';
6
+ import TOML from '@iarna/toml';
7
+ import { createCodeSamples, VovkSchemaIdEnum, } from 'vovk/internal';
8
+ import { compileJSONSchemaToTypeScriptType } from '../utils/compileJSONSchemaToTypeScriptType.mjs';
9
+ import { getTemplateClientImports } from './getTemplateClientImports.mjs';
10
+ import { chalkHighlightThing } from '../utils/chalkHighlightThing.mjs';
11
+ import { ROOT_SEGMENT_FILE_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
12
+ import { prettify } from '../utils/prettify.mjs';
13
+ export function normalizeOutTemplatePath(out, packageJson) {
14
+ return out.replace('[package_name]', packageJson.name?.replace(/-/g, '_') ?? 'my_package_name');
15
+ }
16
+ export async function writeOneClientFile({ cwd, projectInfo, clientTemplateFile, fullSchema, prettifyClient, segmentName,
17
+ // imports,
18
+ templateContent, matterResult: { data, content }, openAPIObject, package: packageJson, readme, samples, reExports, isEnsuringClient, outCwdRelativeDir, templateDef, locatedSegments, isNodeNextResolution, hasMixins, isVovkProject, vovkCliPackage, isBundle, origin, configKey, cliSchemaPath, projectConfig, }) {
19
+ const { config, log } = projectInfo;
20
+ const { templateFilePath, relativeDir } = clientTemplateFile;
21
+ const locatedSegmentsByName = _.keyBy(locatedSegments, 'segmentName');
22
+ const outPath = normalizeOutTemplatePath(path.resolve(cwd, outCwdRelativeDir, typeof segmentName === 'string' ? segmentName || ROOT_SEGMENT_FILE_NAME : '', relativeDir, path.basename(templateFilePath).replace('.ejs', '')), packageJson);
23
+ let placeholder = templateFilePath.endsWith('.json.ejs')
24
+ ? ''
25
+ : `// This is a temporary placeholder to avoid compilation errors if client is imported before it's generated.
26
+ // If you still see this text, the client is not generated yet because of an unknown problem.
27
+ // Feel free to report an issue at https://github.com/finom/vovk/issues`;
28
+ placeholder = outPath.endsWith('.py') ? placeholder.replace(/\/\//g, '#') : placeholder;
29
+ const getFirstLineBanner = (type = 'c') => {
30
+ const text = `Generated by vovk-cli v${vovkCliPackage.version} at ${new Date().toISOString()}`;
31
+ switch (type) {
32
+ case 'html':
33
+ return `<!-- ${text} -->`;
34
+ case 'sh':
35
+ return `# ${text}`;
36
+ case 'c':
37
+ return `// ${text}`;
38
+ }
39
+ };
40
+ reExports = _.mapValues(reExports ?? {}, (p) => p.startsWith('.')
41
+ ? path.relative(path.join(outCwdRelativeDir, typeof segmentName === 'string' ? segmentName || ROOT_SEGMENT_FILE_NAME : '.'), path.resolve(cwd, p))
42
+ : p);
43
+ // Data for the EJS templates:
44
+ const t = {
45
+ _, // lodash
46
+ hasMixins,
47
+ isVovkProject,
48
+ package: packageJson,
49
+ readme,
50
+ samples,
51
+ reExports,
52
+ openapi: openAPIObject,
53
+ ROOT_SEGMENT_FILE_NAME,
54
+ apiRoot: origin ? `${origin}/${config.rootEntry}` : undefined,
55
+ imports: {},
56
+ schema: fullSchema,
57
+ config: projectConfig,
58
+ VovkSchemaIdEnum,
59
+ createCodeSamples,
60
+ compileJSONSchemaToTypeScriptType,
61
+ YAML,
62
+ TOML,
63
+ getFirstLineBanner,
64
+ nodeNextResolutionExt: {
65
+ ts: isNodeNextResolution ? '.ts' : '',
66
+ js: isNodeNextResolution ? '.js' : '',
67
+ mjs: isNodeNextResolution ? '.mjs' : '',
68
+ },
69
+ schemaOutDir: typeof segmentName === 'string'
70
+ ? path.relative(path.join(outCwdRelativeDir, segmentName || ROOT_SEGMENT_FILE_NAME), cliSchemaPath ?? config.schemaOutDir)
71
+ : path.relative(outCwdRelativeDir, cliSchemaPath ?? config.schemaOutDir),
72
+ commonImports: getTemplateClientImports({
73
+ config: projectConfig,
74
+ fullSchema,
75
+ isBundle,
76
+ outCwdRelativeDir,
77
+ segmentName,
78
+ outputConfigs: [templateDef.outputConfig ?? {}],
79
+ }).composedClient,
80
+ segmentImports: Object.fromEntries(Object.values(fullSchema.segments).map(({ segmentName: sName }) => {
81
+ const clientImports = getTemplateClientImports({
82
+ config: projectConfig,
83
+ fullSchema,
84
+ segmentName: sName,
85
+ isBundle,
86
+ outCwdRelativeDir,
87
+ outputConfigs: [projectConfig[configKey].outputConfig ?? {}, templateDef.outputConfig ?? {}],
88
+ });
89
+ const imports = configKey === 'composedClient' ? clientImports.composedClient : clientImports.segmentedClient[sName];
90
+ return [sName, imports];
91
+ })),
92
+ segmentMeta: Object.fromEntries(Object.values(fullSchema.segments).map(({ segmentName: sName, forceApiRoot }) => {
93
+ const { routeFilePath = null } = locatedSegmentsByName[sName] ?? {};
94
+ const segmentImportPath = routeFilePath
95
+ ? path.relative(path.resolve(cwd, outCwdRelativeDir, typeof segmentName === 'string' ? segmentName || ROOT_SEGMENT_FILE_NAME : '.'), path.resolve(cwd, routeFilePath))
96
+ : null;
97
+ const segmentConfig = {
98
+ ...config.outputConfig.segments?.[sName],
99
+ // ...templateDef.outputConfig?.segments?.[sName],
100
+ };
101
+ const { origin: segmentConfigOrigin, rootEntry: segmentConfigRootEntry, segmentNameOverride } = segmentConfig;
102
+ return [
103
+ sName,
104
+ {
105
+ forceApiRoot: forceApiRoot ??
106
+ (segmentConfigOrigin || segmentConfigRootEntry
107
+ ? `${segmentConfigOrigin ?? origin}/${segmentConfigRootEntry ?? config.rootEntry}`
108
+ : null),
109
+ routeFilePath,
110
+ segmentImportPath,
111
+ segmentNameOverride,
112
+ },
113
+ ];
114
+ })),
115
+ };
116
+ if (Array.isArray(data.imports)) {
117
+ for (const imp of data.imports) {
118
+ t.imports = {
119
+ [imp]: await import(imp),
120
+ };
121
+ }
122
+ }
123
+ // Render the template
124
+ let rendered = templateFilePath.endsWith('.ejs')
125
+ ? await ejs.render(content, { t }, {
126
+ filename: templateFilePath,
127
+ async: true,
128
+ })
129
+ : templateContent;
130
+ // Optionally prettify
131
+ if (prettifyClient) {
132
+ rendered = await prettify(rendered, outPath);
133
+ }
134
+ if (isEnsuringClient) {
135
+ rendered = `${rendered}\n\n${placeholder}`;
136
+ }
137
+ // Read existing file content to compare
138
+ const existingContent = await fs.readFile(outPath, 'utf-8').catch(() => null);
139
+ // Determine if we need to rewrite the file, ignore 1st line
140
+ const needsWriting = isEnsuringClient
141
+ ? !existingContent
142
+ : !existingContent ||
143
+ existingContent.trim().split('\n').slice(1).join('\n') !== rendered.trim().split('\n').slice(1).join('\n');
144
+ if (needsWriting) {
145
+ log.debug(`Writing file: ${chalkHighlightThing(outPath)} ${existingContent ? '(updated)' : '(new)'}`);
146
+ await fs.mkdir(path.dirname(outPath), { recursive: true });
147
+ await fs.writeFile(outPath, rendered, 'utf-8');
148
+ }
149
+ return { written: needsWriting };
150
+ }
@@ -0,0 +1,5 @@
1
+ export declare 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 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'];
@@ -0,0 +1,3 @@
1
+ export declare function getRelativeSrcRoot({ cwd }: {
2
+ cwd: string;
3
+ }): Promise<"." | "./src" | null>;
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path';
2
- import getFileSystemEntryType, { FileSystemEntryType } from '../utils/getFileSystemEntryType.mjs';
3
- export default async function getRelativeSrcRoot({ cwd }) {
2
+ import { getFileSystemEntryType, FileSystemEntryType } from '../../utils/getFileSystemEntryType.mjs';
3
+ export 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) {
6
6
  return '.';
@@ -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,25 @@
1
+ import type { VovkStrictConfig } from 'vovk/internal';
2
+ export declare enum BuiltInTemplateName {
3
+ tsBase = "tsBase",
4
+ jsBase = "jsBase",
5
+ ts = "ts",
6
+ js = "js",
7
+ schemaTs = "schemaTs",
8
+ schemaJs = "schemaJs",
9
+ schemaJson = "schemaJson",
10
+ openapiTs = "openapiTs",
11
+ openapiJs = "openapiJs",
12
+ openapiJson = "openapiJson",
13
+ readme = "readme",
14
+ packageJson = "packageJson",
15
+ mixins = "mixins",
16
+ rsSrc = "rsSrc",
17
+ rsPkg = "rsPkg",
18
+ rsReadme = "rsReadme",
19
+ rs = "rs",
20
+ pySrc = "pySrc",
21
+ pyPkg = "pyPkg",
22
+ pyReadme = "pyReadme",
23
+ py = "py"
24
+ }
25
+ export declare function getTemplateDefs(userTemplateDefs?: VovkStrictConfig['clientTemplateDefs']): VovkStrictConfig['clientTemplateDefs'];