vovk-cli 0.0.1-draft.36 → 0.0.1-draft.360
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.
- package/LICENSE +1 -1
- package/README.md +29 -1
- package/client-templates/cjs/index.cjs.ejs +19 -0
- package/client-templates/cjs/index.d.cts.ejs +22 -0
- package/client-templates/mixins/mixins.d.ts.ejs +64 -0
- package/client-templates/mixins/mixins.json.ejs +1 -0
- package/client-templates/mjs/index.d.mts.ejs +22 -0
- package/client-templates/mjs/index.mjs.ejs +18 -0
- package/client-templates/openapiCjs/openapi.cjs.ejs +4 -0
- package/client-templates/openapiCjs/openapi.d.cts.ejs +4 -0
- package/client-templates/openapiJson/openapi.json.ejs +1 -0
- package/client-templates/openapiTs/openapi.ts.ejs +4 -0
- package/client-templates/packageJson/package.json.ejs +1 -0
- package/client-templates/readme/README.md.ejs +39 -0
- package/client-templates/schemaCjs/schema.cjs.ejs +24 -0
- package/client-templates/schemaCjs/schema.d.cts.ejs +10 -0
- package/client-templates/schemaJson/schema.json.ejs +1 -0
- package/client-templates/schemaTs/schema.ts.ejs +28 -0
- package/client-templates/ts/index.ts.ejs +27 -0
- package/dist/bundle/index.d.mts +8 -0
- package/dist/bundle/index.mjs +103 -0
- package/dist/dev/diffSegmentSchema.d.mts +36 -0
- package/dist/dev/{diffSchema.mjs → diffSegmentSchema.mjs} +3 -11
- package/dist/dev/ensureSchemaFiles.d.mts +4 -1
- package/dist/dev/ensureSchemaFiles.mjs +15 -31
- package/dist/dev/index.d.mts +5 -1
- package/dist/dev/index.mjs +191 -80
- package/dist/dev/logDiffResult.d.mts +1 -1
- package/dist/dev/logDiffResult.mjs +6 -43
- package/dist/dev/writeMetaJson.d.mts +2 -0
- package/dist/dev/writeMetaJson.mjs +20 -0
- package/dist/dev/writeOneSegmentSchemaFile.d.mts +12 -0
- package/dist/dev/{writeOneSchemaFile.mjs → writeOneSegmentSchemaFile.mjs} +10 -6
- package/dist/generate/ensureClient.d.mts +3 -0
- package/dist/generate/ensureClient.mjs +28 -0
- package/dist/generate/generate.d.mts +13 -0
- package/dist/generate/generate.mjs +306 -0
- package/dist/generate/getClientTemplateFiles.d.mts +20 -0
- package/dist/generate/getClientTemplateFiles.mjs +85 -0
- package/dist/generate/getProjectFullSchema.d.mts +8 -0
- package/dist/generate/getProjectFullSchema.mjs +66 -0
- package/dist/generate/getTemplateClientImports.d.mts +19 -0
- package/dist/generate/getTemplateClientImports.mjs +49 -0
- package/dist/generate/index.d.mts +33 -0
- package/dist/generate/index.mjs +186 -0
- package/dist/generate/writeOneClientFile.d.mts +42 -0
- package/dist/generate/writeOneClientFile.mjs +139 -0
- package/dist/getProjectInfo/getConfig/getConfigAbsolutePaths.d.mts +5 -0
- package/dist/getProjectInfo/{getConfigAbsolutePaths.mjs → getConfig/getConfigAbsolutePaths.mjs} +4 -1
- package/dist/getProjectInfo/{getRelativeSrcRoot.d.mts → getConfig/getRelativeSrcRoot.d.mts} +1 -1
- package/dist/getProjectInfo/{getRelativeSrcRoot.mjs → getConfig/getRelativeSrcRoot.mjs} +2 -2
- package/dist/getProjectInfo/getConfig/getTemplateDefs.d.mts +24 -0
- package/dist/getProjectInfo/getConfig/getTemplateDefs.mjs +165 -0
- package/dist/getProjectInfo/{getUserConfig.d.mts → getConfig/getUserConfig.d.mts} +3 -2
- package/dist/getProjectInfo/{getUserConfig.mjs → getConfig/getUserConfig.mjs} +3 -3
- package/dist/getProjectInfo/{importUncachedModule.mjs → getConfig/importUncachedModule.mjs} +1 -4
- package/dist/getProjectInfo/getConfig/index.d.mts +78 -0
- package/dist/getProjectInfo/getConfig/index.mjs +91 -0
- package/dist/getProjectInfo/getMetaSchema.d.mts +8 -0
- package/dist/getProjectInfo/getMetaSchema.mjs +14 -0
- package/dist/getProjectInfo/index.d.mts +14 -9
- package/dist/getProjectInfo/index.mjs +24 -22
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +118 -36
- package/dist/init/createConfig.d.mts +2 -2
- package/dist/init/createConfig.mjs +39 -13
- package/dist/init/createStandardSchemaValidatorFile.d.mts +4 -0
- package/dist/init/createStandardSchemaValidatorFile.mjs +39 -0
- package/dist/init/getTemplateFilesFromPackage.mjs +10 -5
- package/dist/init/index.d.mts +2 -2
- package/dist/init/index.mjs +122 -72
- package/dist/init/installDependencies.mjs +4 -2
- package/dist/init/logUpdateDependenciesError.d.mts +3 -1
- package/dist/init/logUpdateDependenciesError.mjs +7 -1
- package/dist/init/updateDependenciesWithoutInstalling.mjs +39 -9
- package/dist/init/updateNPMScripts.d.mts +3 -1
- package/dist/init/updateNPMScripts.mjs +10 -7
- package/dist/init/updateTypeScriptConfig.d.mts +4 -1
- package/dist/init/updateTypeScriptConfig.mjs +11 -7
- package/dist/initProgram.d.mts +1 -1
- package/dist/initProgram.mjs +17 -17
- package/dist/locateSegments.d.mts +8 -1
- package/dist/locateSegments.mjs +13 -3
- package/dist/new/addClassToSegmentCode.d.mts +1 -2
- package/dist/new/addClassToSegmentCode.mjs +3 -3
- package/dist/new/index.d.mts +2 -1
- package/dist/new/index.mjs +4 -2
- package/dist/new/newModule.d.mts +4 -1
- package/dist/new/newModule.mjs +18 -17
- package/dist/new/newSegment.d.mts +4 -1
- package/dist/new/newSegment.mjs +19 -11
- package/dist/new/render.d.mts +7 -3
- package/dist/new/render.mjs +29 -8
- package/dist/types.d.mts +64 -42
- package/dist/utils/compileJSONSchemaToTypeScriptType.d.mts +5 -0
- package/dist/utils/compileJSONSchemaToTypeScriptType.mjs +9 -0
- package/dist/utils/compileTs.d.mts +12 -0
- package/dist/utils/compileTs.mjs +261 -0
- package/dist/utils/debounceWithArgs.d.mts +2 -2
- package/dist/utils/debounceWithArgs.mjs +24 -6
- package/dist/utils/deepExtend.d.mts +54 -0
- package/dist/utils/deepExtend.mjs +129 -0
- package/dist/utils/formatLoggedSegmentName.d.mts +3 -1
- package/dist/utils/formatLoggedSegmentName.mjs +3 -2
- package/dist/utils/generateFnName.d.mts +23 -0
- package/dist/utils/generateFnName.mjs +76 -0
- package/dist/utils/getPackageJson.d.mts +3 -0
- package/dist/utils/getPackageJson.mjs +22 -0
- package/dist/utils/getPublicModuleNameFromPath.d.mts +4 -0
- package/dist/utils/getPublicModuleNameFromPath.mjs +9 -0
- package/dist/utils/normalizeOpenAPIMixin.d.mts +14 -0
- package/dist/utils/normalizeOpenAPIMixin.mjs +114 -0
- package/dist/utils/pickSegmentFullSchema.d.mts +3 -0
- package/dist/utils/pickSegmentFullSchema.mjs +15 -0
- package/dist/utils/removeUnlistedDirectories.d.mts +10 -0
- package/dist/utils/removeUnlistedDirectories.mjs +61 -0
- package/dist/utils/resolveAbsoluteModulePath.d.mts +2 -0
- package/dist/utils/resolveAbsoluteModulePath.mjs +32 -0
- package/module-templates/arktype/controller.ts.ejs +68 -0
- package/module-templates/type/controller.ts.ejs +56 -0
- package/module-templates/type/service.ts.ejs +28 -0
- package/module-templates/valibot/controller.ts.ejs +68 -0
- package/package.json +40 -22
- package/dist/dev/diffSchema.d.mts +0 -43
- package/dist/dev/ensureClient.d.mts +0 -5
- package/dist/dev/ensureClient.mjs +0 -31
- package/dist/dev/isMetadataEmpty.d.mts +0 -2
- package/dist/dev/isMetadataEmpty.mjs +0 -4
- package/dist/dev/writeOneSchemaFile.d.mts +0 -11
- package/dist/generateClient.d.mts +0 -7
- package/dist/generateClient.mjs +0 -97
- package/dist/getProjectInfo/getConfig.d.mts +0 -11
- package/dist/getProjectInfo/getConfig.mjs +0 -29
- package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +0 -4
- package/dist/postinstall.d.mts +0 -1
- package/dist/postinstall.mjs +0 -24
- package/templates/controller.ejs +0 -52
- package/templates/service.ejs +0 -27
- package/templates/worker.ejs +0 -24
- /package/dist/getProjectInfo/{importUncachedModule.d.mts → getConfig/importUncachedModule.d.mts} +0 -0
- /package/dist/getProjectInfo/{importUncachedModuleWorker.d.mts → getConfig/importUncachedModuleWorker.d.mts} +0 -0
- /package/dist/getProjectInfo/{importUncachedModuleWorker.mjs → getConfig/importUncachedModuleWorker.mjs} +0 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import matter from 'gray-matter';
|
|
4
|
+
import _ from 'lodash';
|
|
5
|
+
import { resolveGeneratorConfigValues, openAPIToVovkSchema, vovkSchemaToOpenAPI, } from 'vovk';
|
|
6
|
+
import getClientTemplateFiles from './getClientTemplateFiles.mjs';
|
|
7
|
+
import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
|
|
8
|
+
import pickSegmentFullSchema from '../utils/pickSegmentFullSchema.mjs';
|
|
9
|
+
import removeUnlistedDirectories from '../utils/removeUnlistedDirectories.mjs';
|
|
10
|
+
import writeOneClientFile, { normalizeOutTemplatePath } from './writeOneClientFile.mjs';
|
|
11
|
+
import { ROOT_SEGMENT_FILE_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
|
|
12
|
+
import { getTsconfig } from 'get-tsconfig';
|
|
13
|
+
import { normalizeOpenAPIMixin } from '../utils/normalizeOpenAPIMixin.mjs';
|
|
14
|
+
import { BuiltInTemplateName } from '../getProjectInfo/getConfig/getTemplateDefs.mjs';
|
|
15
|
+
const getIncludedSegmentNames = (config, fullSchema, configKey, cliGenerateOptions) => {
|
|
16
|
+
const segments = Object.values(fullSchema.segments);
|
|
17
|
+
const includeSegments = cliGenerateOptions?.[configKey === 'segmentedClient' ? 'segmentedIncludeSegments' : 'composedIncludeSegments'] ??
|
|
18
|
+
config[configKey].includeSegments;
|
|
19
|
+
const excludeSegments = cliGenerateOptions?.[configKey === 'segmentedClient' ? 'segmentedExcludeSegments' : 'composedExcludeSegments'] ??
|
|
20
|
+
config[configKey].excludeSegments;
|
|
21
|
+
if (includeSegments?.length && excludeSegments?.length) {
|
|
22
|
+
throw new Error(`Both includeSegments and excludeSegments are set in "${configKey}" config. Please use only one of them.`);
|
|
23
|
+
}
|
|
24
|
+
const includedSegmentNames = Array.isArray(includeSegments) && includeSegments.length
|
|
25
|
+
? includeSegments.map((segmentName) => {
|
|
26
|
+
const segment = segments.find(({ segmentName: sName }) => sName === segmentName);
|
|
27
|
+
if (!segment) {
|
|
28
|
+
throw new Error(`Segment "${segmentName}" not found in the config for "${configKey}"`);
|
|
29
|
+
}
|
|
30
|
+
return segment.segmentName;
|
|
31
|
+
})
|
|
32
|
+
: Array.isArray(excludeSegments) && excludeSegments.length // TODO: Warn if excludeSegments includes a segment name that is not listed at segments
|
|
33
|
+
? segments
|
|
34
|
+
.filter(({ segmentName }) => !excludeSegments?.includes(segmentName))
|
|
35
|
+
.map(({ segmentName }) => segmentName)
|
|
36
|
+
: segments.map(({ segmentName }) => segmentName);
|
|
37
|
+
return includedSegmentNames;
|
|
38
|
+
};
|
|
39
|
+
function logClientGenerationResults({ results, log, isEnsuringClient = false, forceNothingWrittenLog = false, clientType = 'Composed', startTime, fromTemplates, }) {
|
|
40
|
+
const writtenResults = results.filter(({ written }) => written);
|
|
41
|
+
const duration = Date.now() - startTime;
|
|
42
|
+
const groupedByDir = _.groupBy(writtenResults, ({ outAbsoluteDir }) => outAbsoluteDir);
|
|
43
|
+
const logOrDebug = forceNothingWrittenLog ? log.info : log.debug;
|
|
44
|
+
if (writtenResults.length) {
|
|
45
|
+
for (const [outAbsoluteDir, dirResults] of Object.entries(groupedByDir)) {
|
|
46
|
+
const templateNames = _.uniq(dirResults.map(({ templateName }) => templateName));
|
|
47
|
+
log.info(`${clientType} client${isEnsuringClient ? ' placeholder' : ''} is generated to ${chalkHighlightThing(normalizeOutTemplatePath(outAbsoluteDir, dirResults[0].package))} from template${templateNames.length !== 1 ? 's' : ''} ${chalkHighlightThing(templateNames.map((s) => `"${s}"`).join(', '))} in ${duration}ms`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else if (fromTemplates.length) {
|
|
51
|
+
if (!writtenResults.length) {
|
|
52
|
+
logOrDebug(`${clientType} client${isEnsuringClient ? ' placeholder' : ''} is up to date (${duration}ms)`);
|
|
53
|
+
}
|
|
54
|
+
else if (!isEnsuringClient) {
|
|
55
|
+
for (const [outAbsoluteDir, dirResults] of Object.entries(groupedByDir)) {
|
|
56
|
+
const templateNames = _.uniq(dirResults.map(({ templateName }) => templateName));
|
|
57
|
+
logOrDebug(`${clientType} client that was generated to ${chalkHighlightThing(normalizeOutTemplatePath(outAbsoluteDir, dirResults[0].package))} from template${templateNames.length !== 1 ? 's' : ''} ${chalkHighlightThing(templateNames.map((s) => `"${s}"`).join(', '))} is up to date and doesn't need to be regenerated (${duration}ms)`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
logOrDebug(`${clientType} client${isEnsuringClient ? ' placeholder' : ''} is not generated because no files were written (${duration}ms)`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const cliOptionsToOpenAPIMixins = ({ openapiGetMethodName, openapiGetModuleName, openapiRootUrl, openapiSpec, openapiFallback, openapiMixinName, }) => {
|
|
66
|
+
return Object.fromEntries((openapiSpec?.map((spec, i) => {
|
|
67
|
+
return {
|
|
68
|
+
source: spec.startsWith('http://') || spec.startsWith('https://')
|
|
69
|
+
? { url: spec, fallback: openapiFallback?.[i] }
|
|
70
|
+
: { file: spec },
|
|
71
|
+
apiRoot: openapiRootUrl?.[i] ?? '',
|
|
72
|
+
getModuleName: openapiGetModuleName?.[i] ?? 'api',
|
|
73
|
+
getMethodName: openapiGetMethodName?.[i] ?? 'auto',
|
|
74
|
+
mixinName: openapiMixinName?.[i] ?? 'mixin' + (i > 0 ? i + 1 : ''),
|
|
75
|
+
};
|
|
76
|
+
}) || []).map(({ source, apiRoot, getModuleName, getMethodName, mixinName }) => [
|
|
77
|
+
mixinName,
|
|
78
|
+
{
|
|
79
|
+
source,
|
|
80
|
+
apiRoot,
|
|
81
|
+
getModuleName,
|
|
82
|
+
getMethodName,
|
|
83
|
+
mixinName,
|
|
84
|
+
},
|
|
85
|
+
]));
|
|
86
|
+
};
|
|
87
|
+
export async function generate({ isEnsuringClient = false, isBundle = false, projectInfo, forceNothingWrittenLog, fullSchema, locatedSegments, cliGenerateOptions, }) {
|
|
88
|
+
fullSchema = {
|
|
89
|
+
...fullSchema,
|
|
90
|
+
// sort segments by name to avoid unnecessary rendering
|
|
91
|
+
segments: Object.fromEntries(Object.entries(fullSchema.segments)
|
|
92
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
93
|
+
// preserve original object, so segments can be extended
|
|
94
|
+
.map((segment) => ({ ...segment }))),
|
|
95
|
+
};
|
|
96
|
+
const { config, cwd, log, srcRoot, vovkCliPackage, packageJson: projectPackageJson } = projectInfo;
|
|
97
|
+
Object.entries(config.generatorConfig.segments ?? {})
|
|
98
|
+
.filter(([, segmentConfig]) => segmentConfig.openAPIMixin)
|
|
99
|
+
.forEach(([segmentName, segmentConfig]) => {
|
|
100
|
+
fullSchema.segments = {
|
|
101
|
+
...fullSchema.segments,
|
|
102
|
+
[segmentName]: openAPIToVovkSchema({ ...segmentConfig.openAPIMixin, segmentName }).segments[segmentName],
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
const cliMixins = cliOptionsToOpenAPIMixins(cliGenerateOptions ?? {});
|
|
106
|
+
fullSchema.segments = {
|
|
107
|
+
...fullSchema.segments,
|
|
108
|
+
...Object.fromEntries(await Promise.all(Object.entries(cliMixins).map(async ([mixinName, mixinModule]) => {
|
|
109
|
+
return [
|
|
110
|
+
mixinName,
|
|
111
|
+
openAPIToVovkSchema({
|
|
112
|
+
segmentName: mixinName,
|
|
113
|
+
...(await normalizeOpenAPIMixin({ mixinModule, log })),
|
|
114
|
+
}).segments[mixinName],
|
|
115
|
+
];
|
|
116
|
+
}))),
|
|
117
|
+
};
|
|
118
|
+
const moduleResolution = await getTsconfig(cwd)?.config?.compilerOptions?.moduleResolution?.toLowerCase();
|
|
119
|
+
const isNodeNextResolution = !moduleResolution || ['node16', 'nodenext'].includes(moduleResolution ?? '');
|
|
120
|
+
const isVovkProject = !!srcRoot;
|
|
121
|
+
const isComposedEnabled = cliGenerateOptions?.composedOnly ||
|
|
122
|
+
!!cliGenerateOptions?.composedFrom ||
|
|
123
|
+
!!cliGenerateOptions?.composedOut ||
|
|
124
|
+
(config.composedClient?.enabled && !cliGenerateOptions?.segmentedOnly);
|
|
125
|
+
const isSegmentedEnabled = cliGenerateOptions?.segmentedOnly ||
|
|
126
|
+
!!cliGenerateOptions?.segmentedFrom ||
|
|
127
|
+
!!cliGenerateOptions?.segmentedOut ||
|
|
128
|
+
(config.segmentedClient?.enabled && !cliGenerateOptions?.composedOnly);
|
|
129
|
+
if (isComposedEnabled) {
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
const segmentNames = getIncludedSegmentNames(config, fullSchema, 'composedClient', cliGenerateOptions);
|
|
132
|
+
const { templateFiles: composedClientTemplateFiles, fromTemplates } = await getClientTemplateFiles({
|
|
133
|
+
config,
|
|
134
|
+
cwd,
|
|
135
|
+
log,
|
|
136
|
+
cliGenerateOptions,
|
|
137
|
+
configKey: 'composedClient',
|
|
138
|
+
});
|
|
139
|
+
const composedClientResults = await Promise.all(composedClientTemplateFiles.map(async (clientTemplateFile) => {
|
|
140
|
+
const { templateFilePath, templateName, templateDef, outCwdRelativeDir } = clientTemplateFile;
|
|
141
|
+
const templateContent = await fs.readFile(templateFilePath, 'utf-8');
|
|
142
|
+
const matterResult = templateFilePath.endsWith('.ejs')
|
|
143
|
+
? matter(templateContent)
|
|
144
|
+
: { data: { imports: [] }, content: templateContent };
|
|
145
|
+
const { package: packageJson, readme, origin, samples, reExports, } = resolveGeneratorConfigValues({
|
|
146
|
+
schema: fullSchema,
|
|
147
|
+
configs: [templateDef.generatorConfig ?? {}],
|
|
148
|
+
projectPackageJson,
|
|
149
|
+
isBundle,
|
|
150
|
+
segmentName: null,
|
|
151
|
+
});
|
|
152
|
+
// console.log('reExports', fullSchema.meta)
|
|
153
|
+
const openapi = vovkSchemaToOpenAPI({
|
|
154
|
+
schema: fullSchema,
|
|
155
|
+
rootEntry: config.rootEntry,
|
|
156
|
+
});
|
|
157
|
+
const composedFullSchema = pickSegmentFullSchema(fullSchema, segmentNames);
|
|
158
|
+
const hasMixins = Object.values(composedFullSchema.segments).some((segment) => segment.segmentType === 'mixin');
|
|
159
|
+
if (templateName === BuiltInTemplateName.mixins && !hasMixins) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const { written } = await writeOneClientFile({
|
|
163
|
+
cwd,
|
|
164
|
+
projectInfo,
|
|
165
|
+
clientTemplateFile,
|
|
166
|
+
fullSchema: composedFullSchema,
|
|
167
|
+
prettifyClient: cliGenerateOptions?.prettify ?? config.composedClient.prettifyClient,
|
|
168
|
+
segmentName: null,
|
|
169
|
+
templateContent,
|
|
170
|
+
matterResult,
|
|
171
|
+
openapi,
|
|
172
|
+
package: packageJson,
|
|
173
|
+
readme,
|
|
174
|
+
samples,
|
|
175
|
+
reExports,
|
|
176
|
+
isEnsuringClient,
|
|
177
|
+
outCwdRelativeDir,
|
|
178
|
+
templateDef,
|
|
179
|
+
locatedSegments,
|
|
180
|
+
isNodeNextResolution,
|
|
181
|
+
hasMixins,
|
|
182
|
+
isVovkProject,
|
|
183
|
+
vovkCliPackage,
|
|
184
|
+
isBundle,
|
|
185
|
+
origin: cliGenerateOptions?.origin ?? origin,
|
|
186
|
+
configKey: 'composedClient',
|
|
187
|
+
cliSchemaPath: cliGenerateOptions?.schemaPath ?? null,
|
|
188
|
+
projectConfig: config,
|
|
189
|
+
});
|
|
190
|
+
const outAbsoluteDir = path.resolve(cwd, outCwdRelativeDir);
|
|
191
|
+
return {
|
|
192
|
+
written,
|
|
193
|
+
templateName,
|
|
194
|
+
outAbsoluteDir,
|
|
195
|
+
package: packageJson,
|
|
196
|
+
};
|
|
197
|
+
}));
|
|
198
|
+
if (composedClientTemplateFiles.length) {
|
|
199
|
+
logClientGenerationResults({
|
|
200
|
+
results: composedClientResults.filter((result) => !!result),
|
|
201
|
+
log,
|
|
202
|
+
isEnsuringClient,
|
|
203
|
+
forceNothingWrittenLog,
|
|
204
|
+
clientType: 'Composed',
|
|
205
|
+
startTime: now,
|
|
206
|
+
fromTemplates,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
log.warn('No composed client template files found. Skipping composed client generation.');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (isSegmentedEnabled) {
|
|
214
|
+
const now = Date.now();
|
|
215
|
+
const segmentNames = getIncludedSegmentNames(config, fullSchema, 'segmentedClient', cliGenerateOptions);
|
|
216
|
+
const { templateFiles: segmentedClientTemplateFiles, fromTemplates } = await getClientTemplateFiles({
|
|
217
|
+
config,
|
|
218
|
+
cwd,
|
|
219
|
+
log,
|
|
220
|
+
cliGenerateOptions,
|
|
221
|
+
configKey: 'segmentedClient',
|
|
222
|
+
});
|
|
223
|
+
const segmentedClientResults = await Promise.all(segmentedClientTemplateFiles.map(async (clientTemplateFile) => {
|
|
224
|
+
const { templateFilePath, templateName, templateDef, outCwdRelativeDir } = clientTemplateFile;
|
|
225
|
+
const templateContent = await fs.readFile(templateFilePath, 'utf-8');
|
|
226
|
+
const matterResult = templateFilePath.endsWith('.ejs')
|
|
227
|
+
? matter(templateContent)
|
|
228
|
+
: { data: { imports: [] }, content: templateContent };
|
|
229
|
+
const results = await Promise.all(segmentNames.map(async (segmentName) => {
|
|
230
|
+
const { package: packageJson, readme, origin, samples, reExports, } = resolveGeneratorConfigValues({
|
|
231
|
+
schema: fullSchema,
|
|
232
|
+
configs: [templateDef.generatorConfig ?? {}],
|
|
233
|
+
projectPackageJson,
|
|
234
|
+
segmentName,
|
|
235
|
+
isBundle,
|
|
236
|
+
});
|
|
237
|
+
const segmentedFullSchema = pickSegmentFullSchema(fullSchema, [segmentName]);
|
|
238
|
+
const hasMixins = Object.values(segmentedFullSchema.segments).some((segment) => segment.segmentType === 'mixin');
|
|
239
|
+
if (templateName === BuiltInTemplateName.mixins && !hasMixins) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
const openapi = vovkSchemaToOpenAPI({
|
|
243
|
+
schema: fullSchema,
|
|
244
|
+
rootEntry: config.rootEntry,
|
|
245
|
+
segmentName,
|
|
246
|
+
});
|
|
247
|
+
const { written } = await writeOneClientFile({
|
|
248
|
+
cwd,
|
|
249
|
+
projectInfo,
|
|
250
|
+
clientTemplateFile,
|
|
251
|
+
fullSchema: segmentedFullSchema,
|
|
252
|
+
prettifyClient: cliGenerateOptions?.prettify ?? config.segmentedClient.prettifyClient,
|
|
253
|
+
segmentName,
|
|
254
|
+
templateContent,
|
|
255
|
+
matterResult,
|
|
256
|
+
openapi,
|
|
257
|
+
package: packageJson,
|
|
258
|
+
readme,
|
|
259
|
+
samples,
|
|
260
|
+
reExports,
|
|
261
|
+
isEnsuringClient,
|
|
262
|
+
outCwdRelativeDir,
|
|
263
|
+
templateDef,
|
|
264
|
+
locatedSegments,
|
|
265
|
+
isNodeNextResolution,
|
|
266
|
+
hasMixins,
|
|
267
|
+
isVovkProject,
|
|
268
|
+
vovkCliPackage,
|
|
269
|
+
isBundle,
|
|
270
|
+
origin: cliGenerateOptions?.origin ?? origin,
|
|
271
|
+
configKey: 'segmentedClient',
|
|
272
|
+
cliSchemaPath: cliGenerateOptions?.schemaPath ?? null,
|
|
273
|
+
projectConfig: config,
|
|
274
|
+
});
|
|
275
|
+
return {
|
|
276
|
+
written,
|
|
277
|
+
templateName,
|
|
278
|
+
package: packageJson,
|
|
279
|
+
};
|
|
280
|
+
}));
|
|
281
|
+
const outAbsoluteDir = path.resolve(cwd, outCwdRelativeDir);
|
|
282
|
+
// Remove unlisted directories in the output directory
|
|
283
|
+
await removeUnlistedDirectories(outAbsoluteDir, segmentNames.map((s) => s || ROOT_SEGMENT_FILE_NAME));
|
|
284
|
+
return {
|
|
285
|
+
written: results.filter((result) => !!result).some(({ written }) => written),
|
|
286
|
+
templateName,
|
|
287
|
+
outAbsoluteDir,
|
|
288
|
+
package: results[0]?.package || {}, // TODO: Might be wrong in Python segmented client (unknown use case)
|
|
289
|
+
};
|
|
290
|
+
}));
|
|
291
|
+
if (segmentedClientTemplateFiles.length) {
|
|
292
|
+
logClientGenerationResults({
|
|
293
|
+
results: segmentedClientResults,
|
|
294
|
+
log,
|
|
295
|
+
isEnsuringClient,
|
|
296
|
+
forceNothingWrittenLog,
|
|
297
|
+
clientType: 'Segmented',
|
|
298
|
+
startTime: now,
|
|
299
|
+
fromTemplates,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
log.warn('No segmented client template files found. Skipping segmented client generation.');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { VovkStrictConfig } from 'vovk';
|
|
2
|
+
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
3
|
+
import type { GenerateOptions } from '../types.mjs';
|
|
4
|
+
export interface ClientTemplateFile {
|
|
5
|
+
templateName: string;
|
|
6
|
+
templateFilePath: string;
|
|
7
|
+
relativeDir: string;
|
|
8
|
+
outCwdRelativeDir: string;
|
|
9
|
+
templateDef: VovkStrictConfig['clientTemplateDefs'][string];
|
|
10
|
+
}
|
|
11
|
+
export default function getClientTemplateFiles({ config, cwd, log, configKey, cliGenerateOptions, }: {
|
|
12
|
+
config: VovkStrictConfig;
|
|
13
|
+
cwd: string;
|
|
14
|
+
log: ProjectInfo['log'];
|
|
15
|
+
configKey: 'composedClient' | 'segmentedClient';
|
|
16
|
+
cliGenerateOptions?: GenerateOptions;
|
|
17
|
+
}): Promise<{
|
|
18
|
+
fromTemplates: string[];
|
|
19
|
+
templateFiles: ClientTemplateFile[];
|
|
20
|
+
}>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { glob } from 'glob';
|
|
3
|
+
import resolveAbsoluteModulePath from '../utils/resolveAbsoluteModulePath.mjs';
|
|
4
|
+
import getFileSystemEntryType, { FileSystemEntryType } from '../utils/getFileSystemEntryType.mjs';
|
|
5
|
+
import getPublicModuleNameFromPath from '../utils/getPublicModuleNameFromPath.mjs';
|
|
6
|
+
import omit from 'lodash/omit.js';
|
|
7
|
+
import merge from 'lodash/merge.js';
|
|
8
|
+
export default async function getClientTemplateFiles({ config, cwd, log, configKey, cliGenerateOptions, }) {
|
|
9
|
+
const usedTemplateDefs = {};
|
|
10
|
+
const fromTemplates = configKey === 'composedClient'
|
|
11
|
+
? cliGenerateOptions?.composedFrom || cliGenerateOptions?.segmentedFrom
|
|
12
|
+
? (cliGenerateOptions?.composedFrom ?? [])
|
|
13
|
+
: config.composedClient.fromTemplates
|
|
14
|
+
: cliGenerateOptions?.composedFrom || cliGenerateOptions?.segmentedFrom
|
|
15
|
+
? (cliGenerateOptions?.segmentedFrom ?? [])
|
|
16
|
+
: config.segmentedClient.fromTemplates;
|
|
17
|
+
const cliOutDir = configKey === 'composedClient' ? cliGenerateOptions?.composedOut : cliGenerateOptions?.segmentedOut;
|
|
18
|
+
const configOutDir = config[configKey].outDir;
|
|
19
|
+
for (const templateName of fromTemplates) {
|
|
20
|
+
if (!(templateName in config.clientTemplateDefs)) {
|
|
21
|
+
throw new Error(`Unknown template name: ${templateName}`);
|
|
22
|
+
}
|
|
23
|
+
usedTemplateDefs[templateName] = config.clientTemplateDefs[templateName];
|
|
24
|
+
}
|
|
25
|
+
const templateFiles = [];
|
|
26
|
+
const entries = Object.entries(usedTemplateDefs);
|
|
27
|
+
for (let i = 0; i < entries.length; i++) {
|
|
28
|
+
const [templateName, templateDef, forceOutCwdRelativeDir] = entries[i];
|
|
29
|
+
const templateAbsolutePath = templateDef.templatePath
|
|
30
|
+
? resolveAbsoluteModulePath(templateDef.templatePath, cwd)
|
|
31
|
+
: null;
|
|
32
|
+
const entryType = templateAbsolutePath ? await getFileSystemEntryType(templateAbsolutePath) : null;
|
|
33
|
+
if (templateAbsolutePath && !entryType) {
|
|
34
|
+
const { moduleName } = templateDef.templatePath ? getPublicModuleNameFromPath(templateDef.templatePath) : {};
|
|
35
|
+
if (moduleName) {
|
|
36
|
+
throw new Error(`Unable to locate template path "${templateDef.templatePath}" resolved as "${templateAbsolutePath}". You may need to install the package "${moduleName}" first.`);
|
|
37
|
+
}
|
|
38
|
+
throw new Error(`Unable to locate template path "${templateDef.templatePath}" resolved as "${templateAbsolutePath}"`);
|
|
39
|
+
}
|
|
40
|
+
const defOutDir = configKey === 'composedClient' ? templateDef.composedClient?.outDir : templateDef.segmentedClient?.outDir;
|
|
41
|
+
let files = [];
|
|
42
|
+
const outCwdRelativeDir = forceOutCwdRelativeDir ?? cliOutDir ?? defOutDir ?? configOutDir;
|
|
43
|
+
if (templateAbsolutePath) {
|
|
44
|
+
if (entryType === FileSystemEntryType.FILE) {
|
|
45
|
+
files = [{ filePath: templateAbsolutePath, isSingleFileTemplate: true }];
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const globPath = path.join(templateAbsolutePath, '**/*.*');
|
|
49
|
+
files = (await glob(globPath)).map((filePath) => ({
|
|
50
|
+
filePath,
|
|
51
|
+
isSingleFileTemplate: false,
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
if (files.length === 0) {
|
|
55
|
+
log.error(`Template "${templateAbsolutePath}" not found`);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
for (const { filePath, isSingleFileTemplate } of files) {
|
|
59
|
+
templateFiles.push({
|
|
60
|
+
templateName,
|
|
61
|
+
templateFilePath: filePath,
|
|
62
|
+
relativeDir: path.relative(isSingleFileTemplate ? path.dirname(templateAbsolutePath) : templateAbsolutePath, path.dirname(filePath) + '/'),
|
|
63
|
+
outCwdRelativeDir,
|
|
64
|
+
templateDef,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (templateDef.requires) {
|
|
69
|
+
for (const [tName, reqRelativeDir] of Object.entries(templateDef.requires)) {
|
|
70
|
+
let def = config.clientTemplateDefs[tName];
|
|
71
|
+
if (!def) {
|
|
72
|
+
throw new Error(`Template "${tName}" required by "${templateName}" not found`);
|
|
73
|
+
}
|
|
74
|
+
def = {
|
|
75
|
+
...def,
|
|
76
|
+
generatorConfig: merge({}, templateDef?.generatorConfig, def.generatorConfig),
|
|
77
|
+
composedClient: merge(omit(templateDef?.composedClient ?? {}, ['outDir']), def.composedClient),
|
|
78
|
+
segmentedClient: merge(omit(templateDef?.segmentedClient ?? {}, ['outDir']), def.segmentedClient),
|
|
79
|
+
};
|
|
80
|
+
entries.push([tName, def, path.join(outCwdRelativeDir, reqRelativeDir)]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { fromTemplates, templateFiles };
|
|
85
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { VovkStrictConfig, type VovkSchema } from 'vovk';
|
|
2
|
+
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
3
|
+
export declare function getProjectFullSchema({ schemaOutAbsolutePath, isNextInstalled, log, config, }: {
|
|
4
|
+
schemaOutAbsolutePath: string;
|
|
5
|
+
isNextInstalled: boolean;
|
|
6
|
+
log: ProjectInfo['log'];
|
|
7
|
+
config: VovkStrictConfig;
|
|
8
|
+
}): Promise<VovkSchema>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { readFile, access } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { glob } from 'glob';
|
|
4
|
+
import { VovkSchemaIdEnum } from 'vovk';
|
|
5
|
+
import { META_FILE_NAME, ROOT_SEGMENT_FILE_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
|
|
6
|
+
import getMetaSchema from '../getProjectInfo/getMetaSchema.mjs';
|
|
7
|
+
import deepExtend from '../utils/deepExtend.mjs';
|
|
8
|
+
export async function getProjectFullSchema({ schemaOutAbsolutePath, isNextInstalled, log, config, }) {
|
|
9
|
+
const result = {
|
|
10
|
+
$schema: VovkSchemaIdEnum.SCHEMA,
|
|
11
|
+
segments: {},
|
|
12
|
+
meta: getMetaSchema({
|
|
13
|
+
config,
|
|
14
|
+
useEmitConfig: false,
|
|
15
|
+
}),
|
|
16
|
+
};
|
|
17
|
+
const isEmptyLogOrWarn = isNextInstalled ? log.warn : log.debug;
|
|
18
|
+
// Handle config.json
|
|
19
|
+
const metaPath = path.join(schemaOutAbsolutePath, `${META_FILE_NAME}.json`);
|
|
20
|
+
try {
|
|
21
|
+
const metaContent = await readFile(metaPath, 'utf-8');
|
|
22
|
+
const fromFileMeta = JSON.parse(metaContent);
|
|
23
|
+
result.meta = deepExtend({}, result.meta, fromFileMeta);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
isEmptyLogOrWarn(`${META_FILE_NAME}.json not found at ${metaPath}. Using empty meta as fallback.`);
|
|
27
|
+
}
|
|
28
|
+
// Handle segments directory
|
|
29
|
+
const segmentsDir = path.join(schemaOutAbsolutePath);
|
|
30
|
+
try {
|
|
31
|
+
await access(segmentsDir); // Check if directory exists
|
|
32
|
+
// Use glob to get all JSON files recursively
|
|
33
|
+
const files = await glob(`${segmentsDir}/**/*.json`);
|
|
34
|
+
const filePaths = [];
|
|
35
|
+
for await (const filePath of files) {
|
|
36
|
+
if (path.basename(filePath) === `${META_FILE_NAME}.json`)
|
|
37
|
+
continue; // Skip _meta.json
|
|
38
|
+
filePaths.push(filePath);
|
|
39
|
+
}
|
|
40
|
+
// Process each JSON file
|
|
41
|
+
for (const filePath of filePaths.toSorted()) {
|
|
42
|
+
try {
|
|
43
|
+
const content = await readFile(filePath, 'utf-8');
|
|
44
|
+
const jsonData = JSON.parse(content);
|
|
45
|
+
// Get relative path from segments directory and convert to key
|
|
46
|
+
let relativePath = path
|
|
47
|
+
.relative(segmentsDir, filePath)
|
|
48
|
+
.replace(/\.json$/, '') // Remove .json extension
|
|
49
|
+
.replace(/\\/g, '/'); // Normalize to forward slashes
|
|
50
|
+
// Special case for _root.json
|
|
51
|
+
if (path.basename(filePath) === `${ROOT_SEGMENT_FILE_NAME}.json` && path.dirname(filePath) === segmentsDir) {
|
|
52
|
+
relativePath = '';
|
|
53
|
+
}
|
|
54
|
+
result.segments[relativePath] = jsonData;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
log.warn(`Failed to process file ${filePath}: ${error}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
isEmptyLogOrWarn(`Segments directory not found at ${segmentsDir}. Using empty segments as fallback.`);
|
|
63
|
+
result.segments = {};
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type VovkSchema } from 'vovk';
|
|
2
|
+
export type ClientImports = {
|
|
3
|
+
fetcher: string;
|
|
4
|
+
validateOnClient: string | null;
|
|
5
|
+
createRPC: string;
|
|
6
|
+
};
|
|
7
|
+
export default function getTemplateClientImports({ fullSchema, outCwdRelativeDir, segmentName, isBundle, }: {
|
|
8
|
+
fullSchema: VovkSchema;
|
|
9
|
+
outCwdRelativeDir: string;
|
|
10
|
+
segmentName: string | null;
|
|
11
|
+
isBundle: boolean;
|
|
12
|
+
}): {
|
|
13
|
+
composedClient: ClientImports & {
|
|
14
|
+
module: ClientImports;
|
|
15
|
+
};
|
|
16
|
+
segmentedClient: Record<string, ClientImports & {
|
|
17
|
+
module: ClientImports;
|
|
18
|
+
}>;
|
|
19
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { resolveGeneratorConfigValues } from 'vovk';
|
|
3
|
+
import { ROOT_SEGMENT_FILE_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
|
|
4
|
+
export default function getTemplateClientImports({ fullSchema, outCwdRelativeDir, segmentName, isBundle, }) {
|
|
5
|
+
const { imports: configImports } = resolveGeneratorConfigValues({ schema: fullSchema, segmentName, isBundle });
|
|
6
|
+
const validateOnClientImport = configImports?.validateOnClient ?? null;
|
|
7
|
+
const fetcherImport = configImports?.fetcher ?? 'vovk';
|
|
8
|
+
const createRPCImport = configImports?.createRPC ?? 'vovk';
|
|
9
|
+
const imports = {
|
|
10
|
+
fetcher: typeof fetcherImport === 'string' ? [fetcherImport] : fetcherImport,
|
|
11
|
+
validateOnClient: typeof validateOnClientImport === 'string'
|
|
12
|
+
? [validateOnClientImport]
|
|
13
|
+
: (validateOnClientImport ?? null),
|
|
14
|
+
createRPC: typeof createRPCImport === 'string' ? [createRPCImport] : createRPCImport,
|
|
15
|
+
};
|
|
16
|
+
const getImportPath = (p, s = '') => p.startsWith('.') ? path.relative(path.join(outCwdRelativeDir, s), p) : p;
|
|
17
|
+
const clientImports = {
|
|
18
|
+
composedClient: {
|
|
19
|
+
fetcher: getImportPath(imports.fetcher[0]),
|
|
20
|
+
createRPC: getImportPath(imports.createRPC[0]),
|
|
21
|
+
validateOnClient: imports.validateOnClient ? getImportPath(imports.validateOnClient[0]) : null,
|
|
22
|
+
module: {
|
|
23
|
+
fetcher: getImportPath(imports.fetcher[1] ?? imports.fetcher[0]),
|
|
24
|
+
createRPC: getImportPath(imports.createRPC[1] ?? imports.createRPC[0]),
|
|
25
|
+
validateOnClient: imports.validateOnClient
|
|
26
|
+
? getImportPath(imports.validateOnClient[1] ?? imports.validateOnClient[0])
|
|
27
|
+
: null,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
segmentedClient: Object.fromEntries(Object.values(fullSchema.segments).map((segment) => [
|
|
31
|
+
segment.segmentName,
|
|
32
|
+
{
|
|
33
|
+
fetcher: getImportPath(imports.fetcher[0], segment.segmentName || ROOT_SEGMENT_FILE_NAME),
|
|
34
|
+
createRPC: getImportPath(imports.createRPC[0], segment.segmentName || ROOT_SEGMENT_FILE_NAME),
|
|
35
|
+
validateOnClient: imports.validateOnClient
|
|
36
|
+
? getImportPath(imports.validateOnClient[0], segment.segmentName || ROOT_SEGMENT_FILE_NAME)
|
|
37
|
+
: null,
|
|
38
|
+
module: {
|
|
39
|
+
fetcher: getImportPath(imports.fetcher[1] ?? imports.fetcher[0], segment.segmentName || ROOT_SEGMENT_FILE_NAME),
|
|
40
|
+
createRPC: getImportPath(imports.createRPC[1] ?? imports.createRPC[0], segment.segmentName || ROOT_SEGMENT_FILE_NAME),
|
|
41
|
+
validateOnClient: imports.validateOnClient
|
|
42
|
+
? getImportPath(imports.validateOnClient[1] ?? imports.validateOnClient[0], segment.segmentName || ROOT_SEGMENT_FILE_NAME)
|
|
43
|
+
: null,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
])),
|
|
47
|
+
};
|
|
48
|
+
return clientImports;
|
|
49
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type VovkSchema } from 'vovk';
|
|
2
|
+
import type { GenerateOptions } from '../types.mjs';
|
|
3
|
+
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
4
|
+
export declare class VovkGenerate {
|
|
5
|
+
#private;
|
|
6
|
+
constructor({ cliGenerateOptions, projectInfo, forceNothingWrittenLog, }: {
|
|
7
|
+
cliGenerateOptions: GenerateOptions;
|
|
8
|
+
projectInfo: ProjectInfo;
|
|
9
|
+
forceNothingWrittenLog?: boolean;
|
|
10
|
+
});
|
|
11
|
+
start(): void;
|
|
12
|
+
generate(): Promise<void>;
|
|
13
|
+
getFullSchema(): Promise<VovkSchema>;
|
|
14
|
+
watch({ throttleDelay }: {
|
|
15
|
+
throttleDelay: number;
|
|
16
|
+
}): void;
|
|
17
|
+
watchSchema({ schemaPath, throttleDelay }: {
|
|
18
|
+
schemaPath: string;
|
|
19
|
+
throttleDelay: number;
|
|
20
|
+
}): void;
|
|
21
|
+
watchOpenApiSpec({ openApiSpec, throttleDelay }: {
|
|
22
|
+
openApiSpec: string[];
|
|
23
|
+
throttleDelay: number;
|
|
24
|
+
}): void;
|
|
25
|
+
watchOpenApiSpecLocal({ openApiSpecPaths, throttleDelay }: {
|
|
26
|
+
openApiSpecPaths: string[];
|
|
27
|
+
throttleDelay: number;
|
|
28
|
+
}): void;
|
|
29
|
+
watchOpenApiSpecRemote({ openApiSpecUrl, throttleDelay }: {
|
|
30
|
+
openApiSpecUrl: string;
|
|
31
|
+
throttleDelay: number;
|
|
32
|
+
}): void;
|
|
33
|
+
}
|