vovk-cli 0.0.1-draft.277 → 0.0.1-draft.278
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/cjs/index.cjs.ejs +1 -1
- package/client-templates/mjs/index.mjs.ejs +1 -1
- package/client-templates/ts/index.ts.ejs +1 -1
- package/dist/generate/generate.mjs +16 -10
- package/dist/generate/getClientTemplateFiles.d.mts +1 -3
- package/dist/generate/getClientTemplateFiles.mjs +3 -3
- package/dist/generate/writeOneClientFile.mjs +1 -1
- package/dist/getProjectInfo/getConfig/index.d.mts +6 -2
- package/dist/getProjectInfo/getConfig/index.mjs +6 -4
- package/dist/utils/normalizeOpenAPIMixins.d.mts +3 -1
- package/dist/utils/normalizeOpenAPIMixins.mjs +22 -5
- package/package.json +2 -2
|
@@ -7,7 +7,7 @@ const { validateOnClient = null } = <%- t.imports.validateOnClient ? `require('$
|
|
|
7
7
|
Object.keys(segment.controllers).forEach((rpcModuleName) => { %>
|
|
8
8
|
exports.<%= rpcModuleName %> = createRPC(
|
|
9
9
|
schema, '<%= segment.segmentName %>', '<%= rpcModuleName %>', fetcher,
|
|
10
|
-
{ validateOnClient, <%- typeof t.segmentMeta[segment.segmentName].segmentNameOverride === 'string' ? `segmentNameOverride: '${t.segmentMeta[segment.segmentName].segmentNameOverride}', ` : '' %><%- segment.segmentType === 'mixin' ? '' : `apiRoot: '${segment.
|
|
10
|
+
{ validateOnClient, <%- typeof t.segmentMeta[segment.segmentName].segmentNameOverride === 'string' ? `segmentNameOverride: '${t.segmentMeta[segment.segmentName].segmentNameOverride}', ` : '' %><%- segment.segmentType === 'mixin' ? '' : `apiRoot: '${t.segmentMeta[segment.segmentName].forceApiRoot ?? t.apiRoot}'` %> }
|
|
11
11
|
);
|
|
12
12
|
<% })
|
|
13
13
|
}) %>
|
|
@@ -11,7 +11,7 @@ Object.values(t.schema.segments).filter((segment) => segment.emitSchema).forEach
|
|
|
11
11
|
Object.keys(segment.controllers).forEach((rpcModuleName) => { %>
|
|
12
12
|
export const <%= rpcModuleName %> = createRPC(
|
|
13
13
|
schema, '<%= segment.segmentName %>', '<%= rpcModuleName %>', fetcher,
|
|
14
|
-
{ validateOnClient, <%- typeof t.segmentMeta[segment.segmentName].segmentNameOverride === 'string' ? `segmentNameOverride: '${t.segmentMeta[segment.segmentName].segmentNameOverride}', ` : '' %><%- segment.segmentType === 'mixin' ? '' : `apiRoot: '${segment.
|
|
14
|
+
{ validateOnClient, <%- typeof t.segmentMeta[segment.segmentName].segmentNameOverride === 'string' ? `segmentNameOverride: '${t.segmentMeta[segment.segmentName].segmentNameOverride}', ` : '' %><%- segment.segmentType === 'mixin' ? '' : `apiRoot: '${t.segmentMeta[segment.segmentName].forceApiRoot ?? t.apiRoot}'` %> }
|
|
15
15
|
);
|
|
16
16
|
<%
|
|
17
17
|
});
|
|
@@ -20,7 +20,7 @@ type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
|
|
|
20
20
|
Object.keys(segment.controllers).forEach((rpcModuleName) => { %>
|
|
21
21
|
export const <%= rpcModuleName %> = createRPC<<%= segment.segmentType === 'mixin' ? `MixinControllers` : `Controllers${i}` %>["<%= rpcModuleName %>"], Options>(
|
|
22
22
|
schema, '<%= segment.segmentName %>', '<%= rpcModuleName %>', fetcher,
|
|
23
|
-
{ validateOnClient, <%- typeof t.segmentMeta[segment.segmentName].segmentNameOverride === 'string' ? `segmentNameOverride: '${t.segmentMeta[segment.segmentName].segmentNameOverride}', ` : '' %><%- segment.segmentType === 'mixin' ? '' : `apiRoot: '${segment.
|
|
23
|
+
{ validateOnClient, <%- typeof t.segmentMeta[segment.segmentName].segmentNameOverride === 'string' ? `segmentNameOverride: '${t.segmentMeta[segment.segmentName].segmentNameOverride}', ` : '' %><%- segment.segmentType === 'mixin' ? '' : `apiRoot: '${t.segmentMeta[segment.segmentName].forceApiRoot ?? t.apiRoot}'` %> }
|
|
24
24
|
);
|
|
25
25
|
<% })
|
|
26
26
|
}) %>
|
|
@@ -13,6 +13,7 @@ import writeOneClientFile from './writeOneClientFile.mjs';
|
|
|
13
13
|
import { ROOT_SEGMENT_FILE_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
|
|
14
14
|
import { getTsconfig } from 'get-tsconfig';
|
|
15
15
|
import { normalizeOpenAPIMixins } from '../utils/normalizeOpenAPIMixins.mjs';
|
|
16
|
+
import { BuiltInTemplateName } from '../getProjectInfo/getConfig/getTemplateDefs.mjs';
|
|
16
17
|
const getIncludedSegmentNames = (config, fullSchema, configKey, cliGenerateOptions) => {
|
|
17
18
|
const segments = Object.values(fullSchema.segments);
|
|
18
19
|
const includeSegments = cliGenerateOptions?.[configKey === 'segmentedClient' ? 'segmentedIncludeSegments' : 'composedIncludeSegments'] ??
|
|
@@ -86,14 +87,11 @@ export async function generate({ isEnsuringClient = false, projectInfo, forceNot
|
|
|
86
87
|
...config.openApiMixins,
|
|
87
88
|
...cliOptionsToOpenAPIMixins(cliGenerateOptions ?? {}),
|
|
88
89
|
};
|
|
89
|
-
/** @deprecated */
|
|
90
|
-
let hasMixins = false;
|
|
91
90
|
if (Object.keys(allOpenAPIMixins).length) {
|
|
92
|
-
const mixins = Object.fromEntries(Object.entries(await normalizeOpenAPIMixins({ mixinModules: allOpenAPIMixins })).map(([mixinName, conf]) => [
|
|
91
|
+
const mixins = Object.fromEntries(Object.entries(await normalizeOpenAPIMixins({ mixinModules: allOpenAPIMixins, log })).map(([mixinName, conf]) => [
|
|
93
92
|
mixinName,
|
|
94
93
|
openAPIToVovkSchema({ ...conf, mixinName }).segments[mixinName],
|
|
95
94
|
]));
|
|
96
|
-
hasMixins = true;
|
|
97
95
|
fullSchema = {
|
|
98
96
|
...fullSchema,
|
|
99
97
|
segments: {
|
|
@@ -119,7 +117,6 @@ export async function generate({ isEnsuringClient = false, projectInfo, forceNot
|
|
|
119
117
|
config,
|
|
120
118
|
cwd,
|
|
121
119
|
log,
|
|
122
|
-
hasMixins,
|
|
123
120
|
cliGenerateOptions,
|
|
124
121
|
configKey: 'composedClient',
|
|
125
122
|
});
|
|
@@ -140,11 +137,16 @@ export async function generate({ isEnsuringClient = false, projectInfo, forceNot
|
|
|
140
137
|
config,
|
|
141
138
|
packages: [config.composedClient.package, templateDef.composedClient?.package],
|
|
142
139
|
});
|
|
140
|
+
const composedFullSchema = pickSegmentFullSchema(fullSchema, segmentNames);
|
|
141
|
+
const hasMixins = Object.values(composedFullSchema.segments).some((segment) => segment.segmentType === 'mixin');
|
|
142
|
+
if (templateName === BuiltInTemplateName.mixins && !hasMixins) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
143
145
|
const { written } = await writeOneClientFile({
|
|
144
146
|
cwd,
|
|
145
147
|
projectInfo,
|
|
146
148
|
clientTemplateFile,
|
|
147
|
-
fullSchema:
|
|
149
|
+
fullSchema: composedFullSchema,
|
|
148
150
|
prettifyClient: config.prettifyClient,
|
|
149
151
|
segmentName: null,
|
|
150
152
|
imports: clientImports.composedClient,
|
|
@@ -169,7 +171,7 @@ export async function generate({ isEnsuringClient = false, projectInfo, forceNot
|
|
|
169
171
|
}));
|
|
170
172
|
if (composedClientTemplateFiles.length) {
|
|
171
173
|
logClientGenerationResults({
|
|
172
|
-
results: composedClientResults,
|
|
174
|
+
results: composedClientResults.filter((result) => !!result),
|
|
173
175
|
log,
|
|
174
176
|
isEnsuringClient,
|
|
175
177
|
forceNothingWrittenLog,
|
|
@@ -189,7 +191,6 @@ export async function generate({ isEnsuringClient = false, projectInfo, forceNot
|
|
|
189
191
|
config,
|
|
190
192
|
cwd,
|
|
191
193
|
log,
|
|
192
|
-
hasMixins,
|
|
193
194
|
cliGenerateOptions,
|
|
194
195
|
configKey: 'segmentedClient',
|
|
195
196
|
});
|
|
@@ -214,11 +215,16 @@ export async function generate({ isEnsuringClient = false, projectInfo, forceNot
|
|
|
214
215
|
templateDef.segmentedClient?.packages?.[segmentName],
|
|
215
216
|
],
|
|
216
217
|
});
|
|
218
|
+
const segmentedFullSchema = pickSegmentFullSchema(fullSchema, [segmentName]);
|
|
219
|
+
const hasMixins = Object.values(segmentedFullSchema.segments).some((segment) => segment.segmentType === 'mixin');
|
|
220
|
+
if (templateName === BuiltInTemplateName.mixins && !hasMixins) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
217
223
|
const { written } = await writeOneClientFile({
|
|
218
224
|
cwd,
|
|
219
225
|
projectInfo,
|
|
220
226
|
clientTemplateFile,
|
|
221
|
-
fullSchema:
|
|
227
|
+
fullSchema: segmentedFullSchema,
|
|
222
228
|
prettifyClient: config.prettifyClient,
|
|
223
229
|
segmentName,
|
|
224
230
|
imports: clientImports.segmentedClient[segmentName],
|
|
@@ -243,7 +249,7 @@ export async function generate({ isEnsuringClient = false, projectInfo, forceNot
|
|
|
243
249
|
// Remove unlisted directories in the output directory
|
|
244
250
|
await removeUnlistedDirectories(outAbsoluteDir, segmentNames.map((s) => s || ROOT_SEGMENT_FILE_NAME));
|
|
245
251
|
return {
|
|
246
|
-
written: results.some(({ written }) => written),
|
|
252
|
+
written: results.filter((result) => !!result).some(({ written }) => written),
|
|
247
253
|
templateName,
|
|
248
254
|
outAbsoluteDir,
|
|
249
255
|
};
|
|
@@ -8,14 +8,12 @@ export interface ClientTemplateFile {
|
|
|
8
8
|
outCwdRelativeDir: string;
|
|
9
9
|
templateDef: VovkStrictConfig['clientTemplateDefs'][string];
|
|
10
10
|
}
|
|
11
|
-
export default function getClientTemplateFiles({ config, cwd, log, configKey, cliGenerateOptions,
|
|
11
|
+
export default function getClientTemplateFiles({ config, cwd, log, configKey, cliGenerateOptions, }: {
|
|
12
12
|
config: VovkStrictConfig;
|
|
13
13
|
cwd: string;
|
|
14
14
|
log: ProjectInfo['log'];
|
|
15
15
|
configKey: 'composedClient' | 'segmentedClient';
|
|
16
16
|
cliGenerateOptions?: GenerateOptions;
|
|
17
|
-
/** @deprecated */
|
|
18
|
-
hasMixins: boolean;
|
|
19
17
|
}): Promise<{
|
|
20
18
|
fromTemplates: string[];
|
|
21
19
|
templateFiles: ClientTemplateFile[];
|
|
@@ -4,7 +4,7 @@ import resolveAbsoluteModulePath from '../utils/resolveAbsoluteModulePath.mjs';
|
|
|
4
4
|
import getFileSystemEntryType, { FileSystemEntryType } from '../utils/getFileSystemEntryType.mjs';
|
|
5
5
|
import getPublicModuleNameFromPath from '../utils/getPublicModuleNameFromPath.mjs';
|
|
6
6
|
import { BuiltInTemplateName } from '../getProjectInfo/getConfig/getTemplateDefs.mjs';
|
|
7
|
-
export default async function getClientTemplateFiles({ config, cwd, log, configKey, cliGenerateOptions,
|
|
7
|
+
export default async function getClientTemplateFiles({ config, cwd, log, configKey, cliGenerateOptions, }) {
|
|
8
8
|
const usedTemplateDefs = {};
|
|
9
9
|
const fromTemplates = configKey === 'composedClient'
|
|
10
10
|
? cliGenerateOptions?.composedFrom || cliGenerateOptions?.segmentedFrom
|
|
@@ -14,13 +14,13 @@ export default async function getClientTemplateFiles({ config, cwd, log, configK
|
|
|
14
14
|
? (cliGenerateOptions?.segmentedFrom ?? [])
|
|
15
15
|
: config.segmentedClient.fromTemplates;
|
|
16
16
|
const cliOutDir = configKey === 'composedClient' ? cliGenerateOptions?.composedOut : cliGenerateOptions?.segmentedOut;
|
|
17
|
-
const configOutDir = configKey
|
|
17
|
+
const configOutDir = config[configKey].outDir;
|
|
18
18
|
for (const templateName of fromTemplates) {
|
|
19
19
|
if (!(templateName in config.clientTemplateDefs)) {
|
|
20
20
|
throw new Error(`Unknown template name: ${templateName}`);
|
|
21
21
|
}
|
|
22
22
|
let usedDef = config.clientTemplateDefs[templateName];
|
|
23
|
-
if (usedDef.isTsClient
|
|
23
|
+
if (usedDef.isTsClient) {
|
|
24
24
|
usedDef = {
|
|
25
25
|
...usedDef,
|
|
26
26
|
requires: {
|
|
@@ -53,7 +53,7 @@ export default async function writeOneClientFile({ cwd, projectInfo, clientTempl
|
|
|
53
53
|
return [
|
|
54
54
|
sName,
|
|
55
55
|
{
|
|
56
|
-
|
|
56
|
+
forceApiRoot: forceApiRoot ??
|
|
57
57
|
(segmentConfigOrigin || segmentConfigRootEntry
|
|
58
58
|
? `${segmentConfigOrigin ?? origin}/${segmentConfigRootEntry ?? config.rootEntry}`
|
|
59
59
|
: null),
|
|
@@ -67,7 +67,10 @@ export default function getConfig({ configPath, cwd }: {
|
|
|
67
67
|
controller?: string;
|
|
68
68
|
[key: string]: string | undefined;
|
|
69
69
|
};
|
|
70
|
-
libs?:
|
|
70
|
+
libs?: {
|
|
71
|
+
ajv: import("vovk").KnownAny;
|
|
72
|
+
[key: string]: import("vovk").KnownAny;
|
|
73
|
+
};
|
|
71
74
|
segmentConfig?: false | {
|
|
72
75
|
[x: string]: {
|
|
73
76
|
origin?: string;
|
|
@@ -81,10 +84,11 @@ export default function getConfig({ configPath, cwd }: {
|
|
|
81
84
|
file: string;
|
|
82
85
|
} | {
|
|
83
86
|
url: string;
|
|
84
|
-
|
|
87
|
+
fallback?: string;
|
|
85
88
|
} | {
|
|
86
89
|
object: import("openapi3-ts/oas31").OpenAPIObject;
|
|
87
90
|
};
|
|
91
|
+
package?: import("type-fest").PackageJson;
|
|
88
92
|
apiRoot?: string;
|
|
89
93
|
getModuleName?: "nestjs-operation-id" | (string & {}) | "api" | import("vovk/mjs/types").GetOpenAPINameFn;
|
|
90
94
|
getMethodName?: "nestjs-operation-id" | "camel-case-operation-id" | "auto" | import("vovk/mjs/types").GetOpenAPINameFn;
|
|
@@ -68,10 +68,7 @@ export default async function getConfig({ configPath, cwd }) {
|
|
|
68
68
|
},
|
|
69
69
|
libs: conf.libs ?? {},
|
|
70
70
|
segmentConfig: conf.segmentConfig ?? {},
|
|
71
|
-
openApiMixins:
|
|
72
|
-
mixinModules: conf.openApiMixins ?? {},
|
|
73
|
-
cwd,
|
|
74
|
-
}),
|
|
71
|
+
openApiMixins: {},
|
|
75
72
|
};
|
|
76
73
|
if (typeof conf.emitConfig === 'undefined') {
|
|
77
74
|
config.emitConfig = ['$schema', 'libs'];
|
|
@@ -83,6 +80,11 @@ export default async function getConfig({ configPath, cwd }) {
|
|
|
83
80
|
config.emitConfig = conf.emitConfig;
|
|
84
81
|
} // else it's false and emitConfig already is []
|
|
85
82
|
const log = getLogger(config.logLevel);
|
|
83
|
+
config.openApiMixins = await normalizeOpenAPIMixins({
|
|
84
|
+
mixinModules: conf.openApiMixins ?? {},
|
|
85
|
+
cwd,
|
|
86
|
+
log,
|
|
87
|
+
});
|
|
86
88
|
if (!userConfig) {
|
|
87
89
|
log.warn(`Unable to load config at ${chalkHighlightThing(cwd)}. Using default values. ${error ?? ''}`);
|
|
88
90
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { VovkStrictConfig, type VovkConfig } from 'vovk';
|
|
2
|
-
|
|
2
|
+
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
3
|
+
export declare function normalizeOpenAPIMixins({ mixinModules, log, cwd, }: {
|
|
3
4
|
mixinModules: NonNullable<VovkConfig['openApiMixins']>;
|
|
5
|
+
log: ProjectInfo['log'];
|
|
4
6
|
cwd?: string;
|
|
5
7
|
}): Promise<VovkStrictConfig['openApiMixins']>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import * as YAML from 'yaml';
|
|
4
|
+
import chalkHighlightThing from './chalkHighlightThing.mjs';
|
|
4
5
|
async function getOpenApiSpecLocal(openApiSpecFilePath, cwd) {
|
|
5
6
|
const openApiSpecAbsolutePath = path.resolve(cwd, openApiSpecFilePath);
|
|
6
7
|
const fileName = path.basename(openApiSpecAbsolutePath);
|
|
@@ -10,20 +11,36 @@ async function getOpenApiSpecLocal(openApiSpecFilePath, cwd) {
|
|
|
10
11
|
const openApiSpecContent = await fs.readFile(openApiSpecAbsolutePath, 'utf8');
|
|
11
12
|
return (fileName.endsWith('.json') ? JSON.parse(openApiSpecContent) : YAML.parse(openApiSpecContent));
|
|
12
13
|
}
|
|
13
|
-
async function getOpenApiSpecRemote(
|
|
14
|
-
const resp = await fetch(
|
|
14
|
+
async function getOpenApiSpecRemote({ cwd, url, fallback, log, }) {
|
|
15
|
+
const resp = await fetch(url);
|
|
15
16
|
const text = await resp.text();
|
|
16
17
|
if (!resp.ok) {
|
|
17
|
-
|
|
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
|
+
}
|
|
18
35
|
}
|
|
19
36
|
return (text.trim().startsWith('{') || text.trim().startsWith('[') ? JSON.parse(text) : YAML.parse(text));
|
|
20
37
|
}
|
|
21
|
-
export async function normalizeOpenAPIMixins({ mixinModules, cwd = process.cwd(), }) {
|
|
38
|
+
export async function normalizeOpenAPIMixins({ mixinModules, log, cwd = process.cwd(), }) {
|
|
22
39
|
if (mixinModules) {
|
|
23
40
|
const modules = await Promise.all(Object.entries(mixinModules).map(async ([mixinName, { source, apiRoot, getModuleName, getMethodName, errorMessageKey }]) => {
|
|
24
41
|
let openAPIObject;
|
|
25
42
|
if ('url' in source) {
|
|
26
|
-
openAPIObject = await getOpenApiSpecRemote(source.url);
|
|
43
|
+
openAPIObject = await getOpenApiSpecRemote({ url: source.url, fallback: source.fallback, log, cwd });
|
|
27
44
|
}
|
|
28
45
|
else if ('file' in source) {
|
|
29
46
|
openAPIObject = await getOpenApiSpecLocal(source.file, cwd);
|
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.278",
|
|
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.298"
|
|
39
39
|
},
|
|
40
40
|
"optionalDependencies": {
|
|
41
41
|
"vovk-python": "^0.0.1-draft.41"
|