vovk-cli 0.0.1-draft.334 → 0.0.1-draft.335
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/client-templates/readme/README.md.ejs +3 -2
- package/dist/bundle/index.mjs +2 -6
- package/dist/dev/ensureSchemaFiles.d.mts +1 -1
- package/dist/dev/index.mjs +20 -14
- package/dist/dev/writeMetaJson.d.mts +1 -1
- package/dist/dev/writeMetaJson.mjs +5 -2
- package/dist/generate/generate.d.mts +2 -5
- package/dist/generate/generate.mjs +38 -40
- package/dist/generate/getClientTemplateFiles.mjs +1 -1
- package/dist/generate/getProjectFullSchema.d.mts +5 -2
- package/dist/generate/getProjectFullSchema.mjs +7 -7
- package/dist/generate/index.mjs +3 -1
- package/dist/generate/writeOneClientFile.d.mts +4 -3
- package/dist/generate/writeOneClientFile.mjs +11 -5
- package/dist/getProjectInfo/getConfig/getTemplateDefs.mjs +3 -3
- package/dist/getProjectInfo/getConfig/index.d.mts +5 -49
- package/dist/getProjectInfo/getConfig/index.mjs +16 -14
- package/dist/getProjectInfo/getMetaSchema.d.mts +10 -0
- package/dist/getProjectInfo/getMetaSchema.mjs +13 -0
- package/dist/getProjectInfo/index.mjs +1 -1
- package/dist/index.mjs +3 -1
- package/dist/init/index.mjs +1 -10
- package/dist/utils/generateFnName.d.mts +23 -0
- package/dist/utils/generateFnName.mjs +76 -0
- package/dist/utils/normalizeOpenAPIMixin.d.mts +14 -0
- package/dist/utils/normalizeOpenAPIMixin.mjs +114 -0
- package/package.json +2 -2
- package/dist/generate/mergePackages.d.mts +0 -7
- package/dist/generate/mergePackages.mjs +0 -55
- package/dist/utils/normalizeOpenAPIMixins.d.mts +0 -7
- package/dist/utils/normalizeOpenAPIMixins.mjs +0 -67
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
# <%= t.package.name %> v<%= t.package.version %> [](https://www.typescriptlang.org/) [](https://vovk.dev)
|
|
5
5
|
|
|
6
|
-
<%- t.package.description ? `> ${t.package.description}` : '' %>
|
|
6
|
+
<%- t.readme.description ?? (`${t.package.description ? `> ${t.package.description}` : ''}`) %>
|
|
7
7
|
|
|
8
8
|
<%- t.package.license ? `License: **${t.package.license}**` : '' %>
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
# Install the package
|
|
12
|
-
npm install
|
|
12
|
+
<%= t.readme.installCommand ?? `npm install ${t.package.name}` %>
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
<% Object.entries(t.schema.segments).forEach(([segmentName, segment]) => {
|
|
@@ -31,6 +31,7 @@ npm install <%= t.package.name %>
|
|
|
31
31
|
handlerName,
|
|
32
32
|
controllerSchema,
|
|
33
33
|
package: t.package,
|
|
34
|
+
config: t.snippets,
|
|
34
35
|
}).ts %>
|
|
35
36
|
```
|
|
36
37
|
<% }) %>
|
package/dist/bundle/index.mjs
CHANGED
|
@@ -29,10 +29,8 @@ export async function bundle({ projectInfo, fullSchema, cliBundleOptions, }) {
|
|
|
29
29
|
forceNothingWrittenLog: true,
|
|
30
30
|
fullSchema,
|
|
31
31
|
locatedSegments,
|
|
32
|
-
package: bundleConfig.package,
|
|
33
|
-
readme: bundleConfig.readme,
|
|
34
32
|
cliGenerateOptions: {
|
|
35
|
-
origin: cliBundleOptions?.origin
|
|
33
|
+
origin: cliBundleOptions?.origin,
|
|
36
34
|
openapiSpec: cliBundleOptions?.openapiSpec,
|
|
37
35
|
openapiGetModuleName: cliBundleOptions?.openapiGetModuleName,
|
|
38
36
|
openapiGetMethodName: cliBundleOptions?.openapiGetMethodName,
|
|
@@ -73,10 +71,8 @@ export async function bundle({ projectInfo, fullSchema, cliBundleOptions, }) {
|
|
|
73
71
|
forceNothingWrittenLog: true,
|
|
74
72
|
fullSchema,
|
|
75
73
|
locatedSegments,
|
|
76
|
-
package: bundleConfig.package,
|
|
77
|
-
readme: bundleConfig.readme,
|
|
78
74
|
cliGenerateOptions: {
|
|
79
|
-
origin: cliBundleOptions?.origin
|
|
75
|
+
origin: cliBundleOptions?.origin,
|
|
80
76
|
composedFrom: group.map(([templateName]) => templateName),
|
|
81
77
|
composedOut: path.resolve(outDirAbsolute, relativePath),
|
|
82
78
|
composedOnly: true,
|
|
@@ -2,5 +2,5 @@ import { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
|
2
2
|
/**
|
|
3
3
|
* Ensure that the schema files are created to avoid any import errors.
|
|
4
4
|
*/
|
|
5
|
-
export default function ensureSchemaFiles(projectInfo: ProjectInfo
|
|
5
|
+
export default function ensureSchemaFiles(projectInfo: ProjectInfo, schemaOutAbsolutePath: string, segmentNames: string[]): Promise<void>;
|
|
6
6
|
export declare const debouncedEnsureSchemaFiles: import("lodash").DebouncedFunc<typeof ensureSchemaFiles>;
|
package/dist/dev/index.mjs
CHANGED
|
@@ -19,19 +19,11 @@ import debounceWithArgs from '../utils/debounceWithArgs.mjs';
|
|
|
19
19
|
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
20
20
|
import writeMetaJson from './writeMetaJson.mjs';
|
|
21
21
|
import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
|
|
22
|
+
import getMetaSchema from '../getProjectInfo/getMetaSchema.mjs';
|
|
22
23
|
export class VovkDev {
|
|
23
24
|
#projectInfo;
|
|
24
25
|
#segments = [];
|
|
25
|
-
#
|
|
26
|
-
$schema: VovkSchemaIdEnum.SCHEMA,
|
|
27
|
-
segments: {},
|
|
28
|
-
meta: {
|
|
29
|
-
$schema: VovkSchemaIdEnum.META,
|
|
30
|
-
config: {
|
|
31
|
-
$schema: VovkSchemaIdEnum.CONFIG,
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
};
|
|
26
|
+
#schemaSegments = {};
|
|
35
27
|
#isWatching = false;
|
|
36
28
|
#modulesWatcher = null;
|
|
37
29
|
#segmentWatcher = null;
|
|
@@ -235,7 +227,7 @@ export class VovkDev {
|
|
|
235
227
|
const importRegex = /import\s*{[^}]*\b(get|post|put|del|head|options)\b[^}]*}\s*from\s*['"]vovk['"]/;
|
|
236
228
|
if (importRegex.test(code) && namesOfClasses.length) {
|
|
237
229
|
const affectedSegments = this.#segments.filter((s) => {
|
|
238
|
-
const segmentSchema = this.#
|
|
230
|
+
const segmentSchema = this.#schemaSegments[s.segmentName];
|
|
239
231
|
if (!segmentSchema)
|
|
240
232
|
return false;
|
|
241
233
|
const controllersByOriginalName = keyBy(segmentSchema.controllers, 'originalControllerName');
|
|
@@ -287,7 +279,21 @@ export class VovkDev {
|
|
|
287
279
|
}
|
|
288
280
|
return { isError: false };
|
|
289
281
|
}, 500);
|
|
290
|
-
#generate = debounce(() =>
|
|
282
|
+
#generate = debounce(() => {
|
|
283
|
+
const fullSchema = {
|
|
284
|
+
$schema: VovkSchemaIdEnum.SCHEMA,
|
|
285
|
+
segments: this.#schemaSegments,
|
|
286
|
+
meta: getMetaSchema({
|
|
287
|
+
config: this.#projectInfo.config,
|
|
288
|
+
package: this.#projectInfo.packageJson,
|
|
289
|
+
}),
|
|
290
|
+
};
|
|
291
|
+
return generate({
|
|
292
|
+
projectInfo: this.#projectInfo,
|
|
293
|
+
fullSchema,
|
|
294
|
+
locatedSegments: this.#segments,
|
|
295
|
+
}).then(this.#onFirstTimeGenerate);
|
|
296
|
+
}, 1000);
|
|
291
297
|
async #handleSegmentSchema(segmentName, segmentSchema) {
|
|
292
298
|
const { log, config, cwd } = this.#projectInfo;
|
|
293
299
|
if (!segmentSchema) {
|
|
@@ -301,7 +307,7 @@ export class VovkDev {
|
|
|
301
307
|
log.warn(`${formatLoggedSegmentName(segmentName)} not found`);
|
|
302
308
|
return;
|
|
303
309
|
}
|
|
304
|
-
this.#
|
|
310
|
+
this.#schemaSegments[segmentName] = segmentSchema;
|
|
305
311
|
if (segmentSchema.emitSchema) {
|
|
306
312
|
const now = Date.now();
|
|
307
313
|
const { diffResult } = await writeOneSegmentSchemaFile({
|
|
@@ -318,7 +324,7 @@ export class VovkDev {
|
|
|
318
324
|
else if (segmentSchema && !isEmpty(segmentSchema.controllers)) {
|
|
319
325
|
log.error(`Non-empty schema provided for ${formatLoggedSegmentName(segment.segmentName)} but "emitSchema" is false`);
|
|
320
326
|
}
|
|
321
|
-
if (this.#segments.every((s) => this.#
|
|
327
|
+
if (this.#segments.every((s) => this.#schemaSegments[s.segmentName])) {
|
|
322
328
|
log.debug(`All segments with "emitSchema" have schema.`);
|
|
323
329
|
this.#generate();
|
|
324
330
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
2
|
-
export default function writeMetaJson(schemaOutAbsolutePath: string, projectInfo: ProjectInfo
|
|
2
|
+
export default function writeMetaJson(schemaOutAbsolutePath: string, projectInfo: ProjectInfo): Promise<void>;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import pick from 'lodash/pick.js';
|
|
4
3
|
import { META_FILE_NAME } from './writeOneSegmentSchemaFile.mjs';
|
|
5
4
|
import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
|
|
5
|
+
import getMetaSchema from '../getProjectInfo/getMetaSchema.mjs';
|
|
6
6
|
export default async function writeMetaJson(schemaOutAbsolutePath, projectInfo) {
|
|
7
7
|
const metaJsonPath = path.join(schemaOutAbsolutePath, META_FILE_NAME + '.json');
|
|
8
|
-
const metaStr = JSON.stringify(
|
|
8
|
+
const metaStr = JSON.stringify(getMetaSchema({
|
|
9
|
+
config: projectInfo.config,
|
|
10
|
+
package: projectInfo.packageJson,
|
|
11
|
+
}), null, 2);
|
|
9
12
|
const existingStr = await fs.readFile(metaJsonPath, 'utf-8').catch(() => null);
|
|
10
13
|
if (existingStr !== metaStr) {
|
|
11
14
|
await fs.writeFile(metaJsonPath, metaStr);
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { type VovkSchema
|
|
2
|
-
import type { PackageJson } from 'type-fest';
|
|
1
|
+
import { type VovkSchema } from 'vovk';
|
|
3
2
|
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
4
3
|
import type { GenerateOptions } from '../types.mjs';
|
|
5
4
|
import type { Segment } from '../locateSegments.mjs';
|
|
6
|
-
export declare function generate({ isEnsuringClient, isBundle, projectInfo, forceNothingWrittenLog, fullSchema, locatedSegments, cliGenerateOptions,
|
|
5
|
+
export declare function generate({ isEnsuringClient, isBundle, projectInfo, forceNothingWrittenLog, fullSchema, locatedSegments, cliGenerateOptions, }: {
|
|
7
6
|
isEnsuringClient?: boolean;
|
|
8
7
|
isBundle?: boolean;
|
|
9
8
|
projectInfo: ProjectInfo;
|
|
@@ -11,6 +10,4 @@ export declare function generate({ isEnsuringClient, isBundle, projectInfo, forc
|
|
|
11
10
|
fullSchema: VovkSchema;
|
|
12
11
|
locatedSegments: Segment[];
|
|
13
12
|
cliGenerateOptions?: GenerateOptions;
|
|
14
|
-
package?: PackageJson;
|
|
15
|
-
readme?: VovkStrictConfig['bundle']['readme'];
|
|
16
13
|
}): Promise<void>;
|
|
@@ -2,17 +2,16 @@ import path from 'node:path';
|
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import matter from 'gray-matter';
|
|
4
4
|
import _ from 'lodash';
|
|
5
|
-
import { openAPIToVovkSchema } from 'vovk';
|
|
5
|
+
import { getGeneratorConfig, openAPIToVovkSchema, } from 'vovk';
|
|
6
6
|
import getClientTemplateFiles from './getClientTemplateFiles.mjs';
|
|
7
7
|
import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
|
|
8
8
|
import pickSegmentFullSchema from '../utils/pickSegmentFullSchema.mjs';
|
|
9
9
|
import removeUnlistedDirectories from '../utils/removeUnlistedDirectories.mjs';
|
|
10
10
|
import getTemplateClientImports from './getTemplateClientImports.mjs';
|
|
11
|
-
import mergePackages from './mergePackages.mjs';
|
|
12
11
|
import writeOneClientFile, { normalizeOutTemplatePath } from './writeOneClientFile.mjs';
|
|
13
12
|
import { ROOT_SEGMENT_FILE_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
|
|
14
13
|
import { getTsconfig } from 'get-tsconfig';
|
|
15
|
-
import {
|
|
14
|
+
import { normalizeOpenAPIMixin } from '../utils/normalizeOpenAPIMixin.mjs';
|
|
16
15
|
import { BuiltInTemplateName } from '../getProjectInfo/getConfig/getTemplateDefs.mjs';
|
|
17
16
|
const getIncludedSegmentNames = (config, fullSchema, configKey, cliGenerateOptions) => {
|
|
18
17
|
const segments = Object.values(fullSchema.segments);
|
|
@@ -86,30 +85,35 @@ const cliOptionsToOpenAPIMixins = ({ openapiGetMethodName, openapiGetModuleName,
|
|
|
86
85
|
},
|
|
87
86
|
]));
|
|
88
87
|
};
|
|
89
|
-
export async function generate({ isEnsuringClient = false, isBundle = false, projectInfo, forceNothingWrittenLog, fullSchema, locatedSegments, cliGenerateOptions,
|
|
88
|
+
export async function generate({ isEnsuringClient = false, isBundle = false, projectInfo, forceNothingWrittenLog, fullSchema, locatedSegments, cliGenerateOptions, }) {
|
|
90
89
|
fullSchema = {
|
|
91
90
|
...fullSchema,
|
|
92
91
|
// sort segments by name to avoid unnecessary rendering
|
|
93
|
-
segments: Object.fromEntries(Object.entries(fullSchema.segments)
|
|
92
|
+
segments: Object.fromEntries(Object.entries(fullSchema.segments)
|
|
93
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
94
|
+
// preserve original object, so segments can be extended
|
|
95
|
+
.map((segment) => ({ ...segment }))),
|
|
94
96
|
};
|
|
95
|
-
const { config, cwd, log, srcRoot,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
mixinName,
|
|
103
|
-
openAPIToVovkSchema({ ...conf, mixinName }).segments[mixinName],
|
|
104
|
-
]));
|
|
105
|
-
fullSchema = {
|
|
106
|
-
...fullSchema,
|
|
107
|
-
segments: {
|
|
108
|
-
...fullSchema.segments,
|
|
109
|
-
...mixins,
|
|
97
|
+
const { config, cwd, log, srcRoot, vovkCliPackage } = projectInfo;
|
|
98
|
+
Object.entries(config.projectConfig.segments ?? {}).forEach(([segmentName, segmentConfig]) => {
|
|
99
|
+
fullSchema.segments = {
|
|
100
|
+
...fullSchema.segments,
|
|
101
|
+
[segmentName]: {
|
|
102
|
+
...fullSchema.segments[segmentName],
|
|
103
|
+
...openAPIToVovkSchema({ ...segmentConfig.openAPIMixin, segmentName }).segments[segmentName],
|
|
110
104
|
},
|
|
111
105
|
};
|
|
112
|
-
}
|
|
106
|
+
});
|
|
107
|
+
const cliMixins = cliOptionsToOpenAPIMixins(cliGenerateOptions ?? {});
|
|
108
|
+
await Promise.all(Object.entries(cliMixins).map(async ([mixinName, mixinModule]) => {
|
|
109
|
+
fullSchema.segments = {
|
|
110
|
+
...fullSchema.segments,
|
|
111
|
+
[mixinName]: {
|
|
112
|
+
...fullSchema.segments[mixinName],
|
|
113
|
+
...openAPIToVovkSchema(await normalizeOpenAPIMixin({ mixinModule, log })).segments[mixinName],
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}));
|
|
113
117
|
const isNodeNextResolution = ['node16', 'nodenext'].includes((await getTsconfig(cwd)?.config?.compilerOptions?.moduleResolution?.toLowerCase()) ?? '');
|
|
114
118
|
const isVovkProject = !!srcRoot;
|
|
115
119
|
const isComposedEnabled = cliGenerateOptions?.composedOnly ||
|
|
@@ -141,12 +145,11 @@ export async function generate({ isEnsuringClient = false, isBundle = false, pro
|
|
|
141
145
|
fullSchema,
|
|
142
146
|
outCwdRelativeDir,
|
|
143
147
|
});
|
|
144
|
-
const packageJson =
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
+
const { package: packageJson, readme, origin, snippets, } = getGeneratorConfig({
|
|
149
|
+
schema: fullSchema,
|
|
150
|
+
config: templateDef.projectConfig,
|
|
151
|
+
isBundle,
|
|
148
152
|
});
|
|
149
|
-
const readme = Object.assign({}, config.composedClient.readme, templateDef.composedClient?.readme, argReadme);
|
|
150
153
|
const composedFullSchema = pickSegmentFullSchema(fullSchema, segmentNames);
|
|
151
154
|
const hasMixins = Object.values(composedFullSchema.segments).some((segment) => segment.segmentType === 'mixin');
|
|
152
155
|
if (templateName === BuiltInTemplateName.mixins && !hasMixins) {
|
|
@@ -164,6 +167,7 @@ export async function generate({ isEnsuringClient = false, isBundle = false, pro
|
|
|
164
167
|
matterResult,
|
|
165
168
|
package: packageJson,
|
|
166
169
|
readme,
|
|
170
|
+
snippets,
|
|
167
171
|
isEnsuringClient,
|
|
168
172
|
outCwdRelativeDir,
|
|
169
173
|
templateDef,
|
|
@@ -173,7 +177,7 @@ export async function generate({ isEnsuringClient = false, isBundle = false, pro
|
|
|
173
177
|
isVovkProject,
|
|
174
178
|
vovkCliPackage,
|
|
175
179
|
isBundle,
|
|
176
|
-
origin: cliGenerateOptions?.origin ??
|
|
180
|
+
origin: cliGenerateOptions?.origin ?? origin,
|
|
177
181
|
});
|
|
178
182
|
const outAbsoluteDir = path.resolve(cwd, outCwdRelativeDir);
|
|
179
183
|
return {
|
|
@@ -220,19 +224,12 @@ export async function generate({ isEnsuringClient = false, isBundle = false, pro
|
|
|
220
224
|
outCwdRelativeDir,
|
|
221
225
|
});
|
|
222
226
|
const results = await Promise.all(segmentNames.map(async (segmentName) => {
|
|
223
|
-
const packageJson =
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
config.segmentedClient.packages?.[segmentName],
|
|
229
|
-
templateDef.segmentedClient?.packages?.[segmentName],
|
|
230
|
-
argPackageJson,
|
|
231
|
-
],
|
|
227
|
+
const { package: packageJson, readme, origin, snippets, } = getGeneratorConfig({
|
|
228
|
+
schema: fullSchema,
|
|
229
|
+
config: templateDef.projectConfig,
|
|
230
|
+
segmentName,
|
|
231
|
+
isBundle,
|
|
232
232
|
});
|
|
233
|
-
const readme = Object.assign({},
|
|
234
|
-
// TODO fullSchema.segments[segmentName]?.meta?.readme,
|
|
235
|
-
config.segmentedClient.readmes?.[segmentName], templateDef.segmentedClient?.readmes?.[segmentName], argReadme);
|
|
236
233
|
const segmentedFullSchema = pickSegmentFullSchema(fullSchema, [segmentName]);
|
|
237
234
|
const hasMixins = Object.values(segmentedFullSchema.segments).some((segment) => segment.segmentType === 'mixin');
|
|
238
235
|
if (templateName === BuiltInTemplateName.mixins && !hasMixins) {
|
|
@@ -250,6 +247,7 @@ export async function generate({ isEnsuringClient = false, isBundle = false, pro
|
|
|
250
247
|
matterResult,
|
|
251
248
|
package: packageJson,
|
|
252
249
|
readme,
|
|
250
|
+
snippets,
|
|
253
251
|
isEnsuringClient,
|
|
254
252
|
outCwdRelativeDir,
|
|
255
253
|
templateDef,
|
|
@@ -259,7 +257,7 @@ export async function generate({ isEnsuringClient = false, isBundle = false, pro
|
|
|
259
257
|
isVovkProject,
|
|
260
258
|
vovkCliPackage,
|
|
261
259
|
isBundle,
|
|
262
|
-
origin: cliGenerateOptions?.origin ??
|
|
260
|
+
origin: cliGenerateOptions?.origin ?? origin,
|
|
263
261
|
});
|
|
264
262
|
return {
|
|
265
263
|
written,
|
|
@@ -73,7 +73,7 @@ export default async function getClientTemplateFiles({ config, cwd, log, configK
|
|
|
73
73
|
}
|
|
74
74
|
def = {
|
|
75
75
|
...def,
|
|
76
|
-
|
|
76
|
+
projectConfig: merge({}, templateDef?.projectConfig, def.projectConfig),
|
|
77
77
|
composedClient: merge(omit(templateDef?.composedClient ?? {}, ['outDir']), def.composedClient),
|
|
78
78
|
segmentedClient: merge(omit(templateDef?.segmentedClient ?? {}, ['outDir']), def.segmentedClient),
|
|
79
79
|
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { type VovkSchema } from 'vovk';
|
|
1
|
+
import { VovkStrictConfig, type VovkSchema } from 'vovk';
|
|
2
2
|
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
3
|
-
|
|
3
|
+
import { PackageJson } from 'type-fest';
|
|
4
|
+
export declare function getProjectFullSchema({ schemaOutAbsolutePath, isNextInstalled, log, package: packageJson, config, }: {
|
|
4
5
|
schemaOutAbsolutePath: string;
|
|
5
6
|
isNextInstalled: boolean;
|
|
6
7
|
log: ProjectInfo['log'];
|
|
8
|
+
package: PackageJson;
|
|
9
|
+
config: VovkStrictConfig;
|
|
7
10
|
}): Promise<VovkSchema>;
|
|
@@ -3,17 +3,17 @@ import path from 'node:path';
|
|
|
3
3
|
import { glob } from 'glob';
|
|
4
4
|
import { VovkSchemaIdEnum } from 'vovk';
|
|
5
5
|
import { META_FILE_NAME, ROOT_SEGMENT_FILE_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
|
|
6
|
-
|
|
6
|
+
import getMetaSchema from '../getProjectInfo/getMetaSchema.mjs';
|
|
7
|
+
export async function getProjectFullSchema({ schemaOutAbsolutePath, isNextInstalled, log, package: packageJson, config, }) {
|
|
7
8
|
const result = {
|
|
8
9
|
$schema: VovkSchemaIdEnum.SCHEMA,
|
|
9
10
|
segments: {},
|
|
10
|
-
meta: {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
},
|
|
15
|
-
},
|
|
11
|
+
meta: getMetaSchema({
|
|
12
|
+
config,
|
|
13
|
+
package: packageJson,
|
|
14
|
+
}),
|
|
16
15
|
};
|
|
16
|
+
console.log('result', result);
|
|
17
17
|
const isEmptyLogOrWarn = isNextInstalled ? log.warn : log.debug;
|
|
18
18
|
// Handle config.json
|
|
19
19
|
const metaPath = path.join(schemaOutAbsolutePath, `${META_FILE_NAME}.json`);
|
package/dist/generate/index.mjs
CHANGED
|
@@ -37,12 +37,14 @@ export class VovkGenerate {
|
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
39
|
async getFullSchema() {
|
|
40
|
-
const { log, config, cwd, isNextInstalled } = this.#projectInfo;
|
|
40
|
+
const { log, config, cwd, isNextInstalled, packageJson } = this.#projectInfo;
|
|
41
41
|
const { schemaPath } = this.#cliGenerateOptions;
|
|
42
42
|
const fullSchema = await getProjectFullSchema({
|
|
43
43
|
schemaOutAbsolutePath: path.resolve(cwd, schemaPath ?? config.schemaOutDir),
|
|
44
44
|
isNextInstalled,
|
|
45
45
|
log,
|
|
46
|
+
package: packageJson,
|
|
47
|
+
config,
|
|
46
48
|
});
|
|
47
49
|
return fullSchema;
|
|
48
50
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { type VovkSchema, type VovkStrictConfig } from 'vovk';
|
|
1
|
+
import { VovkReadmeConfig, VovkSnippetsConfig, type VovkSchema, type VovkStrictConfig } from 'vovk';
|
|
2
2
|
import type { PackageJson } from 'type-fest';
|
|
3
3
|
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
4
4
|
import type { ClientTemplateFile } from './getClientTemplateFiles.mjs';
|
|
5
5
|
import type { ClientImports } from './getTemplateClientImports.mjs';
|
|
6
6
|
import type { Segment } from '../locateSegments.mjs';
|
|
7
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,
|
|
8
|
+
export default function writeOneClientFile({ cwd, projectInfo, clientTemplateFile, fullSchema, prettifyClient, segmentName, imports, templateContent, matterResult: { data, content }, package: packageJson, readme, snippets, isEnsuringClient, outCwdRelativeDir, locatedSegments, isNodeNextResolution, hasMixins, isVovkProject, vovkCliPackage, isBundle, origin, }: {
|
|
9
9
|
cwd: string;
|
|
10
10
|
projectInfo: ProjectInfo;
|
|
11
11
|
clientTemplateFile: ClientTemplateFile;
|
|
@@ -21,7 +21,8 @@ export default function writeOneClientFile({ cwd, projectInfo, clientTemplateFil
|
|
|
21
21
|
content: string;
|
|
22
22
|
};
|
|
23
23
|
package: PackageJson;
|
|
24
|
-
readme:
|
|
24
|
+
readme: VovkReadmeConfig;
|
|
25
|
+
snippets: VovkSnippetsConfig;
|
|
25
26
|
isEnsuringClient: boolean;
|
|
26
27
|
outCwdRelativeDir: string;
|
|
27
28
|
templateDef: VovkStrictConfig['clientTemplateDefs'][string];
|
|
@@ -2,7 +2,7 @@ import fs from 'fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import ejs from 'ejs';
|
|
4
4
|
import _ from 'lodash';
|
|
5
|
-
import { createCodeExamples, VovkSchemaIdEnum } from 'vovk';
|
|
5
|
+
import { createCodeExamples, VovkSchemaIdEnum, } from 'vovk';
|
|
6
6
|
import * as YAML from 'yaml';
|
|
7
7
|
import TOML from '@iarna/toml';
|
|
8
8
|
import prettify from '../utils/prettify.mjs';
|
|
@@ -11,7 +11,9 @@ import { compileJSONSchemaToTypeScriptType } from '../utils/compileJSONSchemaToT
|
|
|
11
11
|
export function normalizeOutTemplatePath(out, packageJson) {
|
|
12
12
|
return out.replace('[package_name]', packageJson.name?.replace(/-/g, '_') ?? 'my_package_name');
|
|
13
13
|
}
|
|
14
|
-
export default async function writeOneClientFile({ cwd, projectInfo, clientTemplateFile, fullSchema, prettifyClient, segmentName, imports, templateContent, matterResult: { data, content }, package: packageJson, readme, isEnsuringClient, outCwdRelativeDir,
|
|
14
|
+
export default async function writeOneClientFile({ cwd, projectInfo, clientTemplateFile, fullSchema, prettifyClient, segmentName, imports, templateContent, matterResult: { data, content }, package: packageJson, readme, snippets, isEnsuringClient, outCwdRelativeDir,
|
|
15
|
+
// templateDef,
|
|
16
|
+
locatedSegments, isNodeNextResolution, hasMixins, isVovkProject, vovkCliPackage, isBundle, origin, }) {
|
|
15
17
|
const { config, apiRoot } = projectInfo;
|
|
16
18
|
const { templateFilePath, relativeDir } = clientTemplateFile;
|
|
17
19
|
const locatedSegmentsByName = _.keyBy(locatedSegments, 'segmentName');
|
|
@@ -38,6 +40,7 @@ export default async function writeOneClientFile({ cwd, projectInfo, clientTempl
|
|
|
38
40
|
isVovkProject,
|
|
39
41
|
package: packageJson,
|
|
40
42
|
readme,
|
|
43
|
+
snippets,
|
|
41
44
|
ROOT_SEGMENT_FILE_NAME,
|
|
42
45
|
apiRoot: origin ? `${origin}/${config.rootEntry}` : apiRoot,
|
|
43
46
|
imports,
|
|
@@ -63,11 +66,14 @@ export default async function writeOneClientFile({ cwd, projectInfo, clientTempl
|
|
|
63
66
|
? path.relative(path.resolve(cwd, outCwdRelativeDir, typeof segmentName === 'string' ? segmentName || ROOT_SEGMENT_FILE_NAME : '.'), path.resolve(cwd, routeFilePath))
|
|
64
67
|
: null;
|
|
65
68
|
const segmentConfig = {
|
|
66
|
-
...
|
|
67
|
-
...
|
|
69
|
+
...config.projectConfig.segments?.[sName],
|
|
70
|
+
// ...templateDef.projectConfig?.segments?.[sName],
|
|
68
71
|
};
|
|
69
72
|
const { origin: segmentConfigOrigin, rootEntry: segmentConfigRootEntry, segmentNameOverride } = segmentConfig;
|
|
70
|
-
const reExports = {
|
|
73
|
+
const reExports = {
|
|
74
|
+
...segmentConfig.reExports,
|
|
75
|
+
...(isBundle ? projectInfo.config.projectConfig?.bundle?.reExports : {}),
|
|
76
|
+
};
|
|
71
77
|
return [
|
|
72
78
|
sName,
|
|
73
79
|
{
|
|
@@ -129,9 +129,9 @@ export default function getTemplateDefs(userTemplateDefs = {}) {
|
|
|
129
129
|
...builtIn.segmentedClient,
|
|
130
130
|
...templateDef.segmentedClient,
|
|
131
131
|
},
|
|
132
|
-
|
|
133
|
-
...builtIn.
|
|
134
|
-
...templateDef.
|
|
132
|
+
projectConfig: {
|
|
133
|
+
...builtIn.projectConfig,
|
|
134
|
+
...templateDef.projectConfig,
|
|
135
135
|
},
|
|
136
136
|
// 'requires' and other props will be overridden
|
|
137
137
|
};
|
|
@@ -14,14 +14,13 @@ export default function getConfig({ configPath, cwd, logLevel, }: {
|
|
|
14
14
|
schemaOutDir?: string;
|
|
15
15
|
modulesDir?: string;
|
|
16
16
|
rootEntry?: string;
|
|
17
|
-
origin?: string;
|
|
18
17
|
logLevel?: "error" | "trace" | "debug" | "info" | "warn" | (string & {});
|
|
19
18
|
libs?: {
|
|
20
19
|
ajv: import("vovk").KnownAny;
|
|
21
20
|
[key: string]: import("vovk").KnownAny;
|
|
22
21
|
};
|
|
23
22
|
devHttps?: boolean;
|
|
24
|
-
composedClient?:
|
|
23
|
+
composedClient?: {
|
|
25
24
|
enabled?: boolean;
|
|
26
25
|
outDir?: string;
|
|
27
26
|
fromTemplates?: string[];
|
|
@@ -32,13 +31,8 @@ export default function getConfig({ configPath, cwd, logLevel, }: {
|
|
|
32
31
|
} | {
|
|
33
32
|
excludeSegments?: string[];
|
|
34
33
|
includeSegments?: never;
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
readme?: {
|
|
38
|
-
banner?: string;
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
segmentedClient?: ({
|
|
34
|
+
});
|
|
35
|
+
segmentedClient?: {
|
|
42
36
|
enabled?: boolean;
|
|
43
37
|
outDir?: string;
|
|
44
38
|
fromTemplates?: string[];
|
|
@@ -49,22 +43,11 @@ export default function getConfig({ configPath, cwd, logLevel, }: {
|
|
|
49
43
|
} | {
|
|
50
44
|
excludeSegments?: string[];
|
|
51
45
|
includeSegments?: never;
|
|
52
|
-
})
|
|
53
|
-
packages?: Record<string, import("type-fest").PackageJson>;
|
|
54
|
-
readmes?: Record<string, {
|
|
55
|
-
banner?: string;
|
|
56
|
-
}>;
|
|
57
|
-
};
|
|
46
|
+
});
|
|
58
47
|
bundle?: {
|
|
59
48
|
requires?: Record<string, string>;
|
|
60
49
|
prebundleOutDir?: string;
|
|
61
50
|
keepPrebundleDir?: boolean;
|
|
62
|
-
origin?: string;
|
|
63
|
-
package?: import("type-fest").PackageJson;
|
|
64
|
-
readme?: {
|
|
65
|
-
banner?: string;
|
|
66
|
-
};
|
|
67
|
-
reExports?: Record<string, string>;
|
|
68
51
|
tsdownBuildOptions?: Parameters<typeof import("tsdown/config-9hj-APNF.mjs").build>[0];
|
|
69
52
|
} & ({
|
|
70
53
|
excludeSegments?: never;
|
|
@@ -85,34 +68,7 @@ export default function getConfig({ configPath, cwd, logLevel, }: {
|
|
|
85
68
|
controller?: string;
|
|
86
69
|
[key: string]: string | undefined;
|
|
87
70
|
};
|
|
88
|
-
|
|
89
|
-
[x: string]: {
|
|
90
|
-
origin?: string;
|
|
91
|
-
rootEntry?: string;
|
|
92
|
-
segmentNameOverride?: string;
|
|
93
|
-
reExports?: Record<string, string>;
|
|
94
|
-
};
|
|
95
|
-
};
|
|
96
|
-
openApiMixins?: {
|
|
97
|
-
[mixinName: string]: {
|
|
98
|
-
source: {
|
|
99
|
-
file: string;
|
|
100
|
-
} | {
|
|
101
|
-
url: string;
|
|
102
|
-
fallback?: string;
|
|
103
|
-
} | {
|
|
104
|
-
object: import("openapi3-ts/oas31").OpenAPIObject;
|
|
105
|
-
};
|
|
106
|
-
package?: import("type-fest").PackageJson;
|
|
107
|
-
readme?: {
|
|
108
|
-
banner?: string;
|
|
109
|
-
};
|
|
110
|
-
apiRoot?: string;
|
|
111
|
-
getModuleName?: "nestjs-operation-id" | (string & {}) | "api" | import("vovk/types").GetOpenAPINameFn;
|
|
112
|
-
getMethodName?: "nestjs-operation-id" | "camel-case-operation-id" | "auto" | import("vovk/types").GetOpenAPINameFn;
|
|
113
|
-
errorMessageKey?: string;
|
|
114
|
-
};
|
|
115
|
-
};
|
|
71
|
+
projectConfig?: import("vovk").VovkProjectConfig;
|
|
116
72
|
} | null;
|
|
117
73
|
log: {
|
|
118
74
|
info: (msg: string) => void;
|
|
@@ -4,7 +4,7 @@ import getLogger from '../../utils/getLogger.mjs';
|
|
|
4
4
|
import getUserConfig from './getUserConfig.mjs';
|
|
5
5
|
import getRelativeSrcRoot from './getRelativeSrcRoot.mjs';
|
|
6
6
|
import getTemplateDefs, { BuiltInTemplateName } from './getTemplateDefs.mjs';
|
|
7
|
-
import {
|
|
7
|
+
import { normalizeOpenAPIMixin } from '../../utils/normalizeOpenAPIMixin.mjs';
|
|
8
8
|
import chalkHighlightThing from '../../utils/chalkHighlightThing.mjs';
|
|
9
9
|
export default async function getConfig({ configPath, cwd, logLevel, }) {
|
|
10
10
|
const { configAbsolutePaths, error, userConfig } = await getUserConfig({
|
|
@@ -12,6 +12,7 @@ export default async function getConfig({ configPath, cwd, logLevel, }) {
|
|
|
12
12
|
cwd,
|
|
13
13
|
});
|
|
14
14
|
const conf = userConfig ?? {};
|
|
15
|
+
const log = getLogger(logLevel ?? conf.logLevel);
|
|
15
16
|
const env = process.env;
|
|
16
17
|
const clientTemplateDefs = getTemplateDefs(conf.clientTemplateDefs);
|
|
17
18
|
const srcRoot = await getRelativeSrcRoot({ cwd });
|
|
@@ -51,9 +52,6 @@ export default async function getConfig({ configPath, cwd, logLevel, }) {
|
|
|
51
52
|
[BuiltInTemplateName.readme]: '.',
|
|
52
53
|
[BuiltInTemplateName.packageJson]: '.',
|
|
53
54
|
},
|
|
54
|
-
package: {},
|
|
55
|
-
readme: {},
|
|
56
|
-
reExports: {},
|
|
57
55
|
...conf.bundle,
|
|
58
56
|
tsdownBuildOptions: {
|
|
59
57
|
outDir: conf.bundle?.tsdownBuildOptions?.outDir ?? 'dist',
|
|
@@ -62,7 +60,6 @@ export default async function getConfig({ configPath, cwd, logLevel, }) {
|
|
|
62
60
|
},
|
|
63
61
|
modulesDir: conf.modulesDir ?? path.join(srcRoot ?? '.', 'modules'),
|
|
64
62
|
schemaOutDir: env.VOVK_SCHEMA_OUT_DIR ?? conf.schemaOutDir ?? './.vovk-schema',
|
|
65
|
-
origin: (env.VOVK_ORIGIN ?? conf.origin ?? '').replace(/\/$/, ''), // Remove trailing slash
|
|
66
63
|
rootEntry: env.VOVK_ROOT_ENTRY ?? conf.rootEntry ?? 'api',
|
|
67
64
|
rootSegmentModulesDirName: conf.rootSegmentModulesDirName ?? '',
|
|
68
65
|
logLevel: conf.logLevel ?? 'info',
|
|
@@ -73,11 +70,22 @@ export default async function getConfig({ configPath, cwd, logLevel, }) {
|
|
|
73
70
|
...conf.moduleTemplates,
|
|
74
71
|
},
|
|
75
72
|
libs: conf.libs ?? {},
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
projectConfig: {
|
|
74
|
+
...conf.projectConfig,
|
|
75
|
+
origin: (env.VOVK_ORIGIN ?? conf?.projectConfig?.origin ?? '').replace(/\/$/, ''), // Remove trailing slash
|
|
76
|
+
segments: Object.fromEntries(await Promise.all(Object.entries(conf.projectConfig?.segments ?? {}).map(async ([key, value]) => [
|
|
77
|
+
key,
|
|
78
|
+
{
|
|
79
|
+
...value,
|
|
80
|
+
openAPIMixin: value.openAPIMixin
|
|
81
|
+
? await normalizeOpenAPIMixin({ mixinModule: value.openAPIMixin, log, cwd })
|
|
82
|
+
: undefined,
|
|
83
|
+
},
|
|
84
|
+
]))),
|
|
85
|
+
},
|
|
78
86
|
};
|
|
79
87
|
if (typeof conf.emitConfig === 'undefined') {
|
|
80
|
-
config.emitConfig = ['libs'];
|
|
88
|
+
config.emitConfig = ['libs', 'projectConfig'];
|
|
81
89
|
}
|
|
82
90
|
else if (conf.emitConfig === true) {
|
|
83
91
|
config.emitConfig = Object.keys(config);
|
|
@@ -85,12 +93,6 @@ export default async function getConfig({ configPath, cwd, logLevel, }) {
|
|
|
85
93
|
else if (Array.isArray(conf.emitConfig)) {
|
|
86
94
|
config.emitConfig = conf.emitConfig;
|
|
87
95
|
} // else it's false and emitConfig already is []
|
|
88
|
-
const log = getLogger(logLevel ?? config.logLevel);
|
|
89
|
-
config.openApiMixins = await normalizeOpenAPIMixins({
|
|
90
|
-
mixinModules: conf.openApiMixins ?? {},
|
|
91
|
-
cwd,
|
|
92
|
-
log,
|
|
93
|
-
});
|
|
94
96
|
if (!userConfig) {
|
|
95
97
|
log.warn(`Unable to load config at ${chalkHighlightThing(cwd)}. Using default values. ${error ?? ''}`);
|
|
96
98
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { VovkSchemaIdEnum, VovkStrictConfig } from 'vovk';
|
|
2
|
+
import { PackageJson } from 'type-fest';
|
|
3
|
+
export default function getMetaSchema({ config, package: packageJson, }: {
|
|
4
|
+
config: VovkStrictConfig;
|
|
5
|
+
package: PackageJson;
|
|
6
|
+
}): {
|
|
7
|
+
config: VovkStrictConfig;
|
|
8
|
+
$schema: VovkSchemaIdEnum;
|
|
9
|
+
package: Pick<PackageJson, "name" | "version" | "description" | "author" | "license">;
|
|
10
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { VovkSchemaIdEnum } from 'vovk';
|
|
2
|
+
import pick from 'lodash/pick.js';
|
|
3
|
+
export default function getMetaSchema({ config, package: packageJson, }) {
|
|
4
|
+
return {
|
|
5
|
+
$schema: VovkSchemaIdEnum.META,
|
|
6
|
+
package: pick(packageJson, ['name', 'version', 'description', 'author', 'license']),
|
|
7
|
+
...{
|
|
8
|
+
config: (config
|
|
9
|
+
? pick(config, [...config.emitConfig, '$schema'])
|
|
10
|
+
: {}),
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -18,7 +18,7 @@ export default async function getProjectInfo({ port: givenPort, cwd = process.cw
|
|
|
18
18
|
if (srcRootRequired && !srcRoot) {
|
|
19
19
|
throw new Error(`Could not find app router directory at ${cwd}. Check Next.js docs for more info.`);
|
|
20
20
|
}
|
|
21
|
-
const apiRoot = `${config.origin ?? ''}/${config.rootEntry}`;
|
|
21
|
+
const apiRoot = `${config.projectConfig.origin ?? ''}/${config.rootEntry}`;
|
|
22
22
|
const apiDirAbsolutePath = srcRoot ? path.resolve(cwd, srcRoot, 'app', config.rootEntry) : null;
|
|
23
23
|
if (configAbsolutePaths.length > 1) {
|
|
24
24
|
log.warn(`Multiple config files found. Using the first one: ${configAbsolutePaths[0]}`);
|
package/dist/index.mjs
CHANGED
|
@@ -138,11 +138,13 @@ program
|
|
|
138
138
|
srcRootRequired: false,
|
|
139
139
|
logLevel: cliBundleOptions.logLevel,
|
|
140
140
|
});
|
|
141
|
-
const { cwd, config, log, isNextInstalled } = projectInfo;
|
|
141
|
+
const { cwd, config, log, isNextInstalled, packageJson } = projectInfo;
|
|
142
142
|
const fullSchema = await getProjectFullSchema({
|
|
143
143
|
schemaOutAbsolutePath: path.resolve(cwd, cliBundleOptions?.schema ?? config.schemaOutDir),
|
|
144
144
|
log,
|
|
145
145
|
isNextInstalled,
|
|
146
|
+
package: packageJson,
|
|
147
|
+
config,
|
|
146
148
|
});
|
|
147
149
|
await bundle({
|
|
148
150
|
projectInfo,
|
package/dist/init/index.mjs
CHANGED
|
@@ -20,16 +20,7 @@ export class Init {
|
|
|
20
20
|
log;
|
|
21
21
|
async #init({ configPaths, pkgJson, cwd = process.cwd(), }, { useNpm, useYarn, usePnpm, useBun, skipInstall, updateTsConfig, updateScripts, validationLibrary, lang, dryRun, channel, }) {
|
|
22
22
|
const { log, root } = this;
|
|
23
|
-
const dependencies = [
|
|
24
|
-
'vovk',
|
|
25
|
-
'vovk-client',
|
|
26
|
-
'openapi3-ts',
|
|
27
|
-
'vovk-ajv',
|
|
28
|
-
'ajv',
|
|
29
|
-
'ajv-errors',
|
|
30
|
-
'ajv-formats',
|
|
31
|
-
'ajv-i18n',
|
|
32
|
-
];
|
|
23
|
+
const dependencies = ['vovk', 'vovk-client', 'openapi3-ts', 'vovk-ajv', 'ajv'];
|
|
33
24
|
const devDependencies = ['vovk-cli'];
|
|
34
25
|
if (lang?.includes('py')) {
|
|
35
26
|
devDependencies.push('vovk-python');
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { HttpMethod } from 'vovk';
|
|
2
|
+
export interface VerbMapEntry {
|
|
3
|
+
noParams?: string;
|
|
4
|
+
withParams?: string;
|
|
5
|
+
default?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const VERB_MAP: Record<HttpMethod, VerbMapEntry>;
|
|
8
|
+
export declare function capitalize(str: string): string;
|
|
9
|
+
interface GenerateFnNameOptions {
|
|
10
|
+
/** Segments to strip out (e.g. ['api','v1']) */
|
|
11
|
+
ignoreSegments?: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Turn an HTTP method + OpenAPI path into a camelCased function name.
|
|
15
|
+
*
|
|
16
|
+
* Examples:
|
|
17
|
+
* generateFnName('GET', '/users') // "listUsers"
|
|
18
|
+
* generateFnName('GET', '/users/{id}') // "getUsersById"
|
|
19
|
+
* generateFnName('POST', '/v1/api/orders') // "createOrders"
|
|
20
|
+
* generateFnName('PATCH', '/users/{userId}/profile') // "patchUsersProfileByUserId"
|
|
21
|
+
*/
|
|
22
|
+
export declare function generateFnName(method: HttpMethod, rawPath: string, opts?: GenerateFnNameOptions): string;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const VERB_MAP = {
|
|
2
|
+
GET: { noParams: 'list', withParams: 'get' },
|
|
3
|
+
POST: { default: 'create' },
|
|
4
|
+
PUT: { default: 'update' },
|
|
5
|
+
PATCH: { default: 'patch' },
|
|
6
|
+
DELETE: { default: 'delete' },
|
|
7
|
+
HEAD: { default: 'head' },
|
|
8
|
+
OPTIONS: { default: 'options' },
|
|
9
|
+
};
|
|
10
|
+
export function capitalize(str) {
|
|
11
|
+
if (str.length === 0)
|
|
12
|
+
return '';
|
|
13
|
+
return str[0].toUpperCase() + str.slice(1);
|
|
14
|
+
}
|
|
15
|
+
const DEFAULT_OPTIONS = {
|
|
16
|
+
ignoreSegments: ['api'],
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Turn an HTTP method + OpenAPI path into a camelCased function name.
|
|
20
|
+
*
|
|
21
|
+
* Examples:
|
|
22
|
+
* generateFnName('GET', '/users') // "listUsers"
|
|
23
|
+
* generateFnName('GET', '/users/{id}') // "getUsersById"
|
|
24
|
+
* generateFnName('POST', '/v1/api/orders') // "createOrders"
|
|
25
|
+
* generateFnName('PATCH', '/users/{userId}/profile') // "patchUsersProfileByUserId"
|
|
26
|
+
*/
|
|
27
|
+
export function generateFnName(method, rawPath, opts = {}) {
|
|
28
|
+
const { ignoreSegments } = {
|
|
29
|
+
...DEFAULT_OPTIONS,
|
|
30
|
+
...opts,
|
|
31
|
+
};
|
|
32
|
+
// 1. Clean & split path
|
|
33
|
+
const parts = rawPath
|
|
34
|
+
.replace(/^\/|\/$/g, '') // strip leading/trailing slash
|
|
35
|
+
.split('/')
|
|
36
|
+
.filter((seg) => !ignoreSegments?.includes(seg.toLowerCase()))
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
// 2. Separate resource tokens from path-params
|
|
39
|
+
const resources = [];
|
|
40
|
+
const params = [];
|
|
41
|
+
parts.forEach((seg) => {
|
|
42
|
+
const match = seg.match(/^{?([^}]+)}?$/);
|
|
43
|
+
if (match) {
|
|
44
|
+
params.push(match[1]);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
resources.push(seg);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
// 3. Pick base verb from VERB_MAP
|
|
51
|
+
let baseVerb;
|
|
52
|
+
if (method === 'GET') {
|
|
53
|
+
baseVerb = params.length ? VERB_MAP.GET.withParams : VERB_MAP.GET.noParams;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
baseVerb = VERB_MAP[method].default;
|
|
57
|
+
}
|
|
58
|
+
// 4. Build the “resource” part
|
|
59
|
+
const resourcePart = resources.map(capitalize).join('');
|
|
60
|
+
// 5. Build the “ByParam” suffix
|
|
61
|
+
const byParams = params.length ? 'By' + params.map(capitalize).join('') : '';
|
|
62
|
+
// 6. Combine and ensure camelCase
|
|
63
|
+
const rawName = `${baseVerb}${resourcePart}${byParams}`;
|
|
64
|
+
return rawName[0].toLowerCase() + rawName.slice(1);
|
|
65
|
+
}
|
|
66
|
+
/*
|
|
67
|
+
// --- Example usage ---
|
|
68
|
+
console.log(generateFnName('GET', '/users')); // listUsers
|
|
69
|
+
console.log(generateFnName('GET', '/users/{id}')); // getUsersById
|
|
70
|
+
console.log(generateFnName('POST', '/users')); // createUsers
|
|
71
|
+
console.log(generateFnName('PATCH', '/users/{userId}/profile')); // patchUsersProfileByUserId
|
|
72
|
+
console.log(generateFnName('DELETE', '/v1/api/orders/{orderId}')); // deleteOrdersByOrderId
|
|
73
|
+
|
|
74
|
+
// You can also enable singularization:
|
|
75
|
+
console.log(generateFnName('GET', '/users/{userId}/orders', { singularizeResources: true })); // getUserOrderByUserId
|
|
76
|
+
*/
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { HttpMethod, VovkOperationObject, VovkStrictConfig, type VovkConfig } from 'vovk';
|
|
2
|
+
import { OpenAPIObject } from 'openapi3-ts/oas31';
|
|
3
|
+
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
4
|
+
export type GetOpenAPINameFn = (config: {
|
|
5
|
+
operationObject: VovkOperationObject;
|
|
6
|
+
method: HttpMethod;
|
|
7
|
+
path: string;
|
|
8
|
+
openAPIObject: OpenAPIObject;
|
|
9
|
+
}) => string;
|
|
10
|
+
export declare function normalizeOpenAPIMixin({ mixinModule, log, cwd, }: {
|
|
11
|
+
mixinModule: NonNullable<NonNullable<NonNullable<NonNullable<VovkConfig['projectConfig']>['segments']>[string]>['openAPIMixin']>;
|
|
12
|
+
log: ProjectInfo['log'];
|
|
13
|
+
cwd?: string;
|
|
14
|
+
}): Promise<NonNullable<NonNullable<NonNullable<VovkStrictConfig['projectConfig']>['segments']>[string]>['openAPIMixin']>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import * as YAML from 'yaml';
|
|
4
|
+
import chalkHighlightThing from './chalkHighlightThing.mjs';
|
|
5
|
+
import camelCase from 'lodash/camelCase.js';
|
|
6
|
+
import { generateFnName } from './generateFnName.mjs';
|
|
7
|
+
const getNamesNestJS = (operationObject) => {
|
|
8
|
+
const operationId = operationObject.operationId;
|
|
9
|
+
if (!operationId) {
|
|
10
|
+
throw new Error('Operation ID is required for NestJS module name generation');
|
|
11
|
+
}
|
|
12
|
+
const controllerHandlerMatch = operationId?.match(/^([A-Z][a-zA-Z0-9]*)_([a-zA-Z0-9_]+)/);
|
|
13
|
+
if (!controllerHandlerMatch) {
|
|
14
|
+
throw new Error(`Invalid operationId format for NestJS: ${operationId}`);
|
|
15
|
+
}
|
|
16
|
+
const [controllerName, handlerName] = controllerHandlerMatch.slice(1, 3);
|
|
17
|
+
return [controllerName.replace(/Controller$/, 'RPC'), handlerName];
|
|
18
|
+
};
|
|
19
|
+
const normalizeGetModuleName = (getModuleName) => {
|
|
20
|
+
if (getModuleName === 'nestjs-operation-id') {
|
|
21
|
+
getModuleName = ({ operationObject }) => getNamesNestJS(operationObject)[0];
|
|
22
|
+
}
|
|
23
|
+
else if (typeof getModuleName === 'string') {
|
|
24
|
+
const moduleName = getModuleName;
|
|
25
|
+
getModuleName = () => moduleName;
|
|
26
|
+
}
|
|
27
|
+
else if (typeof getModuleName !== 'function') {
|
|
28
|
+
throw new Error('getModuleName must be a function or one of the predefined strings');
|
|
29
|
+
}
|
|
30
|
+
return getModuleName;
|
|
31
|
+
};
|
|
32
|
+
const normalizeGetMethodName = (getMethodName) => {
|
|
33
|
+
if (getMethodName === 'nestjs-operation-id') {
|
|
34
|
+
getMethodName = ({ operationObject }) => getNamesNestJS(operationObject)[1];
|
|
35
|
+
}
|
|
36
|
+
else if (getMethodName === 'camel-case-operation-id') {
|
|
37
|
+
getMethodName = ({ operationObject }) => {
|
|
38
|
+
const operationId = operationObject.operationId;
|
|
39
|
+
if (!operationId) {
|
|
40
|
+
throw new Error('Operation ID is required for camel-case method name generation');
|
|
41
|
+
}
|
|
42
|
+
return camelCase(operationId);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
else if (getMethodName === 'auto') {
|
|
46
|
+
getMethodName = ({ operationObject, method, path }) => {
|
|
47
|
+
const operationId = operationObject.operationId;
|
|
48
|
+
const isCamelCase = operationId && /^[a-z][a-zA-Z0-9]*$/.test(operationId);
|
|
49
|
+
const isSnakeCase = operationId && /^[a-z][a-z0-9_]+$/.test(operationId);
|
|
50
|
+
return isCamelCase ? operationId : isSnakeCase ? camelCase(operationId) : generateFnName(method, path);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
else if (typeof getMethodName !== 'function') {
|
|
54
|
+
throw new Error('getMethodName must be a function or one of the predefined strings');
|
|
55
|
+
}
|
|
56
|
+
return getMethodName;
|
|
57
|
+
};
|
|
58
|
+
async function getOpenApiSpecLocal(openApiSpecFilePath, cwd) {
|
|
59
|
+
const openApiSpecAbsolutePath = path.resolve(cwd, openApiSpecFilePath);
|
|
60
|
+
const fileName = path.basename(openApiSpecAbsolutePath);
|
|
61
|
+
if (!fileName.endsWith('.json') && !fileName.endsWith('.yaml') && !fileName.endsWith('.yml')) {
|
|
62
|
+
throw new Error(`Invalid OpenAPI spec file format: ${fileName}. Please provide a JSON or YAML file.`);
|
|
63
|
+
}
|
|
64
|
+
const openApiSpecContent = await fs.readFile(openApiSpecAbsolutePath, 'utf8');
|
|
65
|
+
return (fileName.endsWith('.json') ? JSON.parse(openApiSpecContent) : YAML.parse(openApiSpecContent));
|
|
66
|
+
}
|
|
67
|
+
async function getOpenApiSpecRemote({ cwd, url, fallback, log, }) {
|
|
68
|
+
const resp = await fetch(url);
|
|
69
|
+
const text = await resp.text();
|
|
70
|
+
if (!resp.ok) {
|
|
71
|
+
if (fallback) {
|
|
72
|
+
log.warn(`Failed to fetch OpenAPI spec from ${chalkHighlightThing(url)}: ${resp.status} ${resp.statusText}. Falling back to ${chalkHighlightThing(fallback)}`);
|
|
73
|
+
return getOpenApiSpecLocal(fallback, cwd);
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`Failed to fetch OpenAPI spec from ${chalkHighlightThing(url)}: ${resp.status} ${resp.statusText}`);
|
|
76
|
+
}
|
|
77
|
+
if (fallback) {
|
|
78
|
+
const fallbackAbsolutePath = path.resolve(cwd, fallback);
|
|
79
|
+
const existingFallback = await fs.readFile(fallbackAbsolutePath, 'utf8').catch(() => null);
|
|
80
|
+
if (existingFallback !== text) {
|
|
81
|
+
await fs.mkdir(path.dirname(fallbackAbsolutePath), { recursive: true });
|
|
82
|
+
await fs.writeFile(fallbackAbsolutePath, text);
|
|
83
|
+
log.info(`Saved OpenAPI spec to fallback file ${chalkHighlightThing(fallbackAbsolutePath)}`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
log.debug(`OpenAPI spec at ${chalkHighlightThing(url)} is unchanged. Skipping write to fallback file ${chalkHighlightThing(fallbackAbsolutePath)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return (text.trim().startsWith('{') || text.trim().startsWith('[') ? JSON.parse(text) : YAML.parse(text));
|
|
90
|
+
}
|
|
91
|
+
export async function normalizeOpenAPIMixin({
|
|
92
|
+
// mixinName,
|
|
93
|
+
mixinModule, log, cwd = process.cwd(), }) {
|
|
94
|
+
const { source, getModuleName, getMethodName } = mixinModule;
|
|
95
|
+
let openAPIObject;
|
|
96
|
+
if ('url' in source) {
|
|
97
|
+
openAPIObject = await getOpenApiSpecRemote({ url: source.url, fallback: source.fallback, log, cwd });
|
|
98
|
+
}
|
|
99
|
+
else if ('file' in source) {
|
|
100
|
+
openAPIObject = await getOpenApiSpecLocal(source.file, cwd);
|
|
101
|
+
}
|
|
102
|
+
else if ('object' in source) {
|
|
103
|
+
openAPIObject = source.object;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
throw new Error('Invalid source type for OpenAPI configuration');
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
...mixinModule,
|
|
110
|
+
source: { object: openAPIObject },
|
|
111
|
+
getModuleName: normalizeGetModuleName(getModuleName),
|
|
112
|
+
getMethodName: normalizeGetMethodName(getMethodName),
|
|
113
|
+
};
|
|
114
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vovk-cli",
|
|
3
|
-
"version": "0.0.1-draft.
|
|
3
|
+
"version": "0.0.1-draft.335",
|
|
4
4
|
"bin": {
|
|
5
5
|
"vovk": "./dist/index.mjs"
|
|
6
6
|
},
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"homepage": "https://vovk.dev",
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"vovk": "^3.0.0-draft.
|
|
38
|
+
"vovk": "^3.0.0-draft.397"
|
|
39
39
|
},
|
|
40
40
|
"optionalDependencies": {
|
|
41
41
|
"@rolldown/binding-linux-x64-gnu": "1.0.0-beta.31"
|
|
@@ -1,7 +0,0 @@
|
|
|
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>;
|
|
@@ -1,55 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { VovkStrictConfig, type VovkConfig } from 'vovk';
|
|
2
|
-
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
3
|
-
export declare function normalizeOpenAPIMixins({ mixinModules, log, cwd, }: {
|
|
4
|
-
mixinModules: NonNullable<VovkConfig['openApiMixins']>;
|
|
5
|
-
log: ProjectInfo['log'];
|
|
6
|
-
cwd?: string;
|
|
7
|
-
}): Promise<VovkStrictConfig['openApiMixins']>;
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import * as YAML from 'yaml';
|
|
4
|
-
import chalkHighlightThing from './chalkHighlightThing.mjs';
|
|
5
|
-
async function getOpenApiSpecLocal(openApiSpecFilePath, cwd) {
|
|
6
|
-
const openApiSpecAbsolutePath = path.resolve(cwd, openApiSpecFilePath);
|
|
7
|
-
const fileName = path.basename(openApiSpecAbsolutePath);
|
|
8
|
-
if (!fileName.endsWith('.json') && !fileName.endsWith('.yaml') && !fileName.endsWith('.yml')) {
|
|
9
|
-
throw new Error(`Invalid OpenAPI spec file format: ${fileName}. Please provide a JSON or YAML file.`);
|
|
10
|
-
}
|
|
11
|
-
const openApiSpecContent = await fs.readFile(openApiSpecAbsolutePath, 'utf8');
|
|
12
|
-
return (fileName.endsWith('.json') ? JSON.parse(openApiSpecContent) : YAML.parse(openApiSpecContent));
|
|
13
|
-
}
|
|
14
|
-
async function getOpenApiSpecRemote({ cwd, url, fallback, log, }) {
|
|
15
|
-
const resp = await fetch(url);
|
|
16
|
-
const text = await resp.text();
|
|
17
|
-
if (!resp.ok) {
|
|
18
|
-
if (fallback) {
|
|
19
|
-
log.warn(`Failed to fetch OpenAPI spec from ${chalkHighlightThing(url)}: ${resp.status} ${resp.statusText}. Falling back to ${chalkHighlightThing(fallback)}`);
|
|
20
|
-
return getOpenApiSpecLocal(fallback, cwd);
|
|
21
|
-
}
|
|
22
|
-
throw new Error(`Failed to fetch OpenAPI spec from ${chalkHighlightThing(url)}: ${resp.status} ${resp.statusText}`);
|
|
23
|
-
}
|
|
24
|
-
if (fallback) {
|
|
25
|
-
const fallbackAbsolutePath = path.resolve(cwd, fallback);
|
|
26
|
-
const existingFallback = await fs.readFile(fallbackAbsolutePath, 'utf8').catch(() => null);
|
|
27
|
-
if (existingFallback !== text) {
|
|
28
|
-
await fs.mkdir(path.dirname(fallbackAbsolutePath), { recursive: true });
|
|
29
|
-
await fs.writeFile(fallbackAbsolutePath, text);
|
|
30
|
-
log.info(`Saved OpenAPI spec to fallback file ${chalkHighlightThing(fallbackAbsolutePath)}`);
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
log.debug(`OpenAPI spec at ${chalkHighlightThing(url)} is unchanged. Skipping write to fallback file ${chalkHighlightThing(fallbackAbsolutePath)}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return (text.trim().startsWith('{') || text.trim().startsWith('[') ? JSON.parse(text) : YAML.parse(text));
|
|
37
|
-
}
|
|
38
|
-
export async function normalizeOpenAPIMixins({ mixinModules, log, cwd = process.cwd(), }) {
|
|
39
|
-
if (mixinModules) {
|
|
40
|
-
const modules = await Promise.all(Object.entries(mixinModules).map(async ([mixinName, { source, apiRoot, getModuleName, getMethodName, errorMessageKey, package: packageJson },]) => {
|
|
41
|
-
let openAPIObject;
|
|
42
|
-
if ('url' in source) {
|
|
43
|
-
openAPIObject = await getOpenApiSpecRemote({ url: source.url, fallback: source.fallback, log, cwd });
|
|
44
|
-
}
|
|
45
|
-
else if ('file' in source) {
|
|
46
|
-
openAPIObject = await getOpenApiSpecLocal(source.file, cwd);
|
|
47
|
-
}
|
|
48
|
-
else if ('object' in source) {
|
|
49
|
-
openAPIObject = source.object;
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
throw new Error('Invalid source type for OpenAPI configuration');
|
|
53
|
-
}
|
|
54
|
-
return {
|
|
55
|
-
source: { object: openAPIObject },
|
|
56
|
-
apiRoot,
|
|
57
|
-
getModuleName,
|
|
58
|
-
getMethodName,
|
|
59
|
-
errorMessageKey,
|
|
60
|
-
package: packageJson,
|
|
61
|
-
mixinName,
|
|
62
|
-
};
|
|
63
|
-
}));
|
|
64
|
-
return Object.fromEntries(modules.map((module) => [module.mixinName, module]));
|
|
65
|
-
}
|
|
66
|
-
return {};
|
|
67
|
-
}
|