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.
@@ -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.segmentApiRoot ?? t.apiRoot}'` %> }
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.segmentApiRoot ?? t.apiRoot}'` %> }
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.segmentApiRoot ?? t.apiRoot}'` %> }
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: pickSegmentFullSchema(fullSchema, segmentNames),
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: pickSegmentFullSchema(fullSchema, [segmentName]),
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, hasMixins, }: {
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, hasMixins, }) {
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 === 'composedClient' ? config.composedClient.outDir : config.segmentedClient.outDir;
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 && hasMixins) {
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
- segmentApiRoot: forceApiRoot ??
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?: Record<string, import("vovk").KnownAny>;
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
- cache?: string;
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: await normalizeOpenAPIMixins({
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
- export declare function normalizeOpenAPIMixins({ mixinModules, cwd, }: {
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(openApiSpecUrl) {
14
- const resp = await fetch(openApiSpecUrl);
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
- throw new Error(`Failed to fetch OpenAPI spec from ${openApiSpecUrl}: ${resp.status} ${resp.statusText}`);
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.277",
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.297"
38
+ "vovk": "^3.0.0-draft.298"
39
39
  },
40
40
  "optionalDependencies": {
41
41
  "vovk-python": "^0.0.1-draft.41"