vovk-cli 0.0.1-draft.143 → 0.0.1-draft.144

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/client-templates/{fullSchema → fullSchemaCjs}/fullSchema.d.cts.ejs +1 -1
  2. package/client-templates/fullSchemaJson/full-schema.json.ejs +1 -0
  3. package/client-templates/fullSchemaTs/fullSchema.ts.ejs +18 -0
  4. package/client-templates/package/package.json.ejs +1 -0
  5. package/client-templates/ts/index.ts.ejs +1 -1
  6. package/dist/dev/index.mjs +0 -1
  7. package/dist/generate/ensureClient.d.mts +1 -3
  8. package/dist/generate/ensureClient.mjs +19 -66
  9. package/dist/generate/getClientTemplateFiles.d.mts +17 -0
  10. package/dist/generate/getClientTemplateFiles.mjs +61 -0
  11. package/dist/generate/getTemplateClientImports.d.mts +19 -0
  12. package/dist/generate/getTemplateClientImports.mjs +38 -0
  13. package/dist/generate/index.d.mts +4 -7
  14. package/dist/generate/index.mjs +155 -157
  15. package/dist/generate/mergePackages.d.mts +7 -0
  16. package/dist/generate/mergePackages.mjs +74 -0
  17. package/dist/generate/writeOneClientFile.d.mts +27 -0
  18. package/dist/generate/writeOneClientFile.mjs +71 -0
  19. package/dist/getProjectInfo/getConfig/getTemplateDefs.d.mts +11 -0
  20. package/dist/getProjectInfo/getConfig/getTemplateDefs.mjs +66 -0
  21. package/dist/getProjectInfo/{getConfig.d.mts → getConfig/index.d.mts} +3 -3
  22. package/dist/getProjectInfo/{getConfig.mjs → getConfig/index.mjs} +31 -21
  23. package/dist/getProjectInfo/index.d.mts +3 -12
  24. package/dist/getProjectInfo/index.mjs +3 -39
  25. package/dist/index.mjs +7 -9
  26. package/dist/init/createConfig.mjs +7 -6
  27. package/dist/init/getTemplateFilesFromPackage.mjs +4 -4
  28. package/dist/init/index.mjs +16 -4
  29. package/dist/init/updateTypeScriptConfig.d.mts +4 -1
  30. package/dist/init/updateTypeScriptConfig.mjs +11 -7
  31. package/dist/locateSegments.d.mts +0 -1
  32. package/dist/locateSegments.mjs +1 -2
  33. package/dist/new/newModule.mjs +1 -1
  34. package/dist/types.d.mts +4 -3
  35. package/dist/utils/pickSegmentFullSchema.d.mts +2 -1
  36. package/dist/utils/pickSegmentFullSchema.mjs +8 -2
  37. package/package.json +10 -10
  38. package/dist/generate/getClientTemplates.d.mts +0 -25
  39. package/dist/generate/getClientTemplates.mjs +0 -113
  40. /package/client-templates/{fullSchema → fullSchemaCjs}/fullSchema.cjs.ejs +0 -0
  41. /package/{templates → module-templates}/controller.ts.ejs +0 -0
  42. /package/{templates → module-templates}/service.ts.ejs +0 -0
@@ -1,5 +1,5 @@
1
1
  <%- `// auto-generated ${new Date().toISOString()}\n/* eslint-disable */` %>
2
- import { VovkStrictConfig, VovkSegmentSchema } from 'vovk';
2
+ import type { VovkStrictConfig, VovkSegmentSchema } from 'vovk';
3
3
 
4
4
  export const fullSchema: {
5
5
  config: Partial<VovkStrictConfig>;
@@ -0,0 +1 @@
1
+ <%- JSON.stringify(t.fullSchema, null, 2) %>
@@ -0,0 +1,18 @@
1
+ <%- `// auto-generated ${new Date().toISOString()}\n/* eslint-disable */` %>
2
+ import type { VovkStrictConfig, VovkSegmentSchema } from 'vovk';
3
+ import config from './<%= t.schemaOutDir %>/config.json';
4
+
5
+ <% Object.values(t.fullSchema.segments).forEach((segment, i) => { if(Object.keys(segment.controllers).length) { %>
6
+ import segment<%= i %> from './<%= t.schemaOutDir %>/<%= t.SEGMENTS_SCHEMA_DIR_NAME %>/<%= segment.segmentName || t.ROOT_SEGMENT_SCHEMA_NAME %>.json';
7
+ <% }}) %>
8
+
9
+ const segments = {
10
+ <% Object.values(t.fullSchema.segments).forEach((segment, i) => { %>
11
+ '<%= segment.segmentName %>': segment<%= i %>,
12
+ <% }) %>
13
+ };
14
+
15
+ export const fullSchema = {
16
+ config,
17
+ segments,
18
+ };
@@ -0,0 +1 @@
1
+ <%- JSON.stringify(t.package, null, 2) %>
@@ -2,7 +2,7 @@
2
2
  import type { VovkClientFetcher } from 'vovk';
3
3
  import { fetcher } from '<%= t.imports.fetcher %>';
4
4
  import { createRPC } from '<%= t.imports.createRPC %>';
5
- import { fullSchema } from './fullSchema.cjs';
5
+ import { fullSchema } from './fullSchema';
6
6
  <% Object.values(t.fullSchema.segments).forEach((segment, i) => { if(Object.keys(segment.controllers).length) { %>
7
7
  import type { Controllers as Controllers<%= i %> } from "<%= t.segmentMeta[segment.segmentName].segmentImportPath %>";
8
8
  <% }}) %>
@@ -50,7 +50,6 @@ export class VovkDev {
50
50
  {
51
51
  routeFilePath: filePath,
52
52
  segmentName,
53
- segmentImportPath: path.relative(config.clientOutDir, filePath), // TODO DRY locateSegments
54
53
  },
55
54
  ];
56
55
  log.info(`${capitalize(formatLoggedSegmentName(segmentName))} has been added`);
@@ -1,4 +1,2 @@
1
1
  import type { ProjectInfo } from '../getProjectInfo/index.mjs';
2
- export default function ensureClient({ config, cwd, log, segments }: ProjectInfo): Promise<{
3
- written: boolean;
4
- }>;
2
+ export default function ensureClient(projectInfo: ProjectInfo): Promise<void>;
@@ -1,69 +1,22 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import getClientTemplates, { BuiltInTemplateName } from './getClientTemplates.mjs';
4
- import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
5
- import { ROOT_SEGMENT_SCHEMA_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
6
- async function writeOnePlaceholder({ outPath, defaultText, templateName, usedTemplateNames, }) {
7
- const existing = await fs.readFile(outPath, 'utf-8').catch(() => null);
8
- if (!existing) {
9
- let text = defaultText;
10
- await fs.mkdir(path.dirname(outPath), { recursive: true });
11
- // a workaround that prevents compilation error when client is not yet generated but back-end imports fullSchema
12
- if (Object.keys(BuiltInTemplateName).includes(templateName)) {
13
- if (outPath.endsWith('.cjs')) {
14
- text += '\nmodule.exports.fullSchema = {};';
15
- }
16
- else {
17
- text += '\nexport const fullSchema = {};';
18
- }
19
- }
20
- await fs.writeFile(outPath, outPath.endsWith('.py') ? text.replace(/\/\//g, '#') : text);
21
- usedTemplateNames.add(templateName);
1
+ import generate from './index.mjs';
2
+ const getEmptySegmentRecordSchema = (segments) => {
3
+ const result = {};
4
+ for (const { segmentName } of segments) {
5
+ result[segmentName] = {
6
+ segmentName,
7
+ emitSchema: false,
8
+ controllers: {},
9
+ };
22
10
  }
23
- }
24
- export default async function ensureClient({ config, cwd, log, segments }) {
25
- const now = Date.now();
26
- const { clientOutDirAbsolutePath, templateFiles } = await getClientTemplates({
27
- config,
28
- cwd,
29
- generateFrom: config.generateFrom,
30
- log,
11
+ return result;
12
+ };
13
+ export default async function ensureClient(projectInfo) {
14
+ return generate({
15
+ isEnsuringClient: true,
16
+ projectInfo,
17
+ fullSchema: {
18
+ config: {},
19
+ segments: getEmptySegmentRecordSchema(projectInfo.segments),
20
+ },
31
21
  });
32
- const usedTemplateNames = new Set();
33
- const defaultText = `// auto-generated ${new Date().toISOString()}
34
- // This is a temporary placeholder to avoid compilation errors if client is imported before it's generated.
35
- // If you still see this text, the client is not generated yet because of an unknown problem.
36
- // Feel free to report an issue at https://github.com/finom/vovk/issues`;
37
- await Promise.all(templateFiles.map(async (clientTemplate) => {
38
- const { templatePath, templateName, outDir } = clientTemplate;
39
- if (!templatePath)
40
- return;
41
- const outPath = path.join(outDir, path.basename(templatePath).replace('.ejs', ''));
42
- if (config.generateFullClient) {
43
- await writeOnePlaceholder({
44
- outPath,
45
- defaultText,
46
- templateName,
47
- usedTemplateNames,
48
- });
49
- }
50
- if (config.generateSegmentClient) {
51
- // Generate client files for each segment
52
- await Promise.all(segments.map(async ({ segmentName }) => {
53
- const outPath = path.join(outDir, segmentName || ROOT_SEGMENT_SCHEMA_NAME, path.basename(templatePath).replace('.ejs', ''));
54
- return writeOnePlaceholder({
55
- outPath,
56
- defaultText,
57
- templateName,
58
- usedTemplateNames,
59
- });
60
- }));
61
- }
62
- }));
63
- if (usedTemplateNames.size) {
64
- log.info(`Placeholder client files from template${usedTemplateNames.size !== 1 ? 's' : ''} ${chalkHighlightThing(Array.from(usedTemplateNames)
65
- .map((s) => `"${s}"`)
66
- .join(', '))} are generated at ${clientOutDirAbsolutePath} in ${Date.now() - now}ms`);
67
- }
68
- return { written: !!usedTemplateNames.size };
69
22
  }
@@ -0,0 +1,17 @@
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, cliOptions, }: {
12
+ config: VovkStrictConfig;
13
+ cwd: string;
14
+ log: ProjectInfo['log'];
15
+ configKey: 'fullClient' | 'segmentedClient';
16
+ cliOptions?: GenerateOptions;
17
+ }): Promise<ClientTemplateFile[]>;
@@ -0,0 +1,61 @@
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
+ export default async function getClientTemplateFiles({ config, cwd, log, configKey, cliOptions, }) {
6
+ const usedTemplateDefs = {};
7
+ const fromTemplates = configKey === 'fullClient'
8
+ ? (cliOptions?.fullClientFrom ?? config.fullClient.fromTemplates)
9
+ : (cliOptions?.segmentedClientFrom ?? config.segmentedClient.fromTemplates);
10
+ const outDir = configKey === 'fullClient'
11
+ ? (cliOptions?.fullClientOut ?? config.fullClient.outDir)
12
+ : (cliOptions?.segmentedClientOut ?? config.segmentedClient.outDir);
13
+ for (const templateName of fromTemplates) {
14
+ if (!(templateName in config.clientTemplateDefs)) {
15
+ throw new Error(`Unknown template name: ${templateName}`);
16
+ }
17
+ usedTemplateDefs[templateName] = config.clientTemplateDefs[templateName];
18
+ }
19
+ const templateFiles = [];
20
+ const entries = Object.entries(usedTemplateDefs);
21
+ for (let i = 0; i < entries.length; i++) {
22
+ const [templateName, templateDef, forceOutCwdRelativeDir] = entries[i];
23
+ const templateAbsolutePath = resolveAbsoluteModulePath(templateDef.templatePath, cwd);
24
+ const entryType = await getFileSystemEntryType(templateDef.templatePath);
25
+ if (!entryType)
26
+ throw new Error(`Unable to locate template path ${templateDef.templatePath}`);
27
+ const defOutDir = configKey === 'fullClient' ? templateDef.fullClient?.outDir : templateDef.segmentedClient?.outDir;
28
+ let files;
29
+ if (entryType === FileSystemEntryType.FILE) {
30
+ files = [templateAbsolutePath];
31
+ }
32
+ else {
33
+ const globPath = path.join(templateAbsolutePath, '**/*.*');
34
+ files = await glob(globPath);
35
+ }
36
+ if (files.length === 0) {
37
+ log.error(`Template "${templateAbsolutePath}" not found`);
38
+ continue;
39
+ }
40
+ const outCwdRelativeDir = forceOutCwdRelativeDir ?? defOutDir ?? outDir;
41
+ for (const filePath of files) {
42
+ templateFiles.push({
43
+ templateName,
44
+ templateFilePath: filePath,
45
+ relativeDir: path.relative(templateAbsolutePath, path.dirname(filePath)),
46
+ outCwdRelativeDir,
47
+ templateDef,
48
+ });
49
+ }
50
+ if (templateDef.requires) {
51
+ for (const [tName, reqRelativeDir] of Object.entries(templateDef.requires)) {
52
+ const def = config.clientTemplateDefs[tName];
53
+ if (!def) {
54
+ throw new Error(`Template "${tName}" required by "${templateName}" not found`);
55
+ }
56
+ entries.push([tName, def, path.join(outCwdRelativeDir, reqRelativeDir)]);
57
+ }
58
+ }
59
+ }
60
+ return templateFiles;
61
+ }
@@ -0,0 +1,19 @@
1
+ import type { VovkStrictConfig } from 'vovk';
2
+ import type { Segment } from '../locateSegments.mjs';
3
+ export type ClientImports = {
4
+ fetcher: string;
5
+ validateOnClient: string | null;
6
+ createRPC: string;
7
+ };
8
+ export default function getTemplateClientImports({ config, segments, outCwdRelativeDir, }: {
9
+ config: VovkStrictConfig;
10
+ segments: Segment[];
11
+ outCwdRelativeDir: string;
12
+ }): {
13
+ fullClient: ClientImports & {
14
+ module: ClientImports;
15
+ };
16
+ segmentedClient: Record<string, ClientImports & {
17
+ module: ClientImports;
18
+ }>;
19
+ };
@@ -0,0 +1,38 @@
1
+ import path from 'node:path';
2
+ import { ROOT_SEGMENT_SCHEMA_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
3
+ export default function getTemplateClientImports({ config, segments, outCwdRelativeDir, }) {
4
+ const { imports } = config;
5
+ const getImportPath = (p, s = '') => p.startsWith('.') ? path.relative(path.join(outCwdRelativeDir, s), p) : p;
6
+ const clientImports = {
7
+ fullClient: {
8
+ fetcher: getImportPath(imports.fetcher[0]),
9
+ createRPC: getImportPath(imports.createRPC[0]),
10
+ validateOnClient: imports.validateOnClient ? getImportPath(imports.validateOnClient[0]) : null,
11
+ module: {
12
+ fetcher: getImportPath(imports.fetcher[1] ?? imports.fetcher[0]),
13
+ createRPC: getImportPath(imports.createRPC[1] ?? imports.createRPC[0]),
14
+ validateOnClient: imports.validateOnClient
15
+ ? getImportPath(imports.validateOnClient[1] ?? imports.validateOnClient[0])
16
+ : null,
17
+ },
18
+ },
19
+ segmentedClient: Object.fromEntries(segments.map((segment) => [
20
+ segment.segmentName,
21
+ {
22
+ fetcher: getImportPath(imports.fetcher[0], segment.segmentName || ROOT_SEGMENT_SCHEMA_NAME),
23
+ createRPC: getImportPath(imports.createRPC[0], segment.segmentName || ROOT_SEGMENT_SCHEMA_NAME),
24
+ validateOnClient: imports.validateOnClient
25
+ ? getImportPath(imports.validateOnClient[0], segment.segmentName || ROOT_SEGMENT_SCHEMA_NAME)
26
+ : null,
27
+ module: {
28
+ fetcher: getImportPath(imports.fetcher[1] ?? imports.fetcher[0], segment.segmentName || ROOT_SEGMENT_SCHEMA_NAME),
29
+ createRPC: getImportPath(imports.createRPC[1] ?? imports.createRPC[0], segment.segmentName || ROOT_SEGMENT_SCHEMA_NAME),
30
+ validateOnClient: imports.validateOnClient
31
+ ? getImportPath(imports.validateOnClient[1] ?? imports.validateOnClient[0], segment.segmentName || ROOT_SEGMENT_SCHEMA_NAME)
32
+ : null,
33
+ },
34
+ },
35
+ ])),
36
+ };
37
+ return clientImports;
38
+ }
@@ -1,13 +1,10 @@
1
1
  import type { VovkFullSchema } from 'vovk';
2
2
  import type { ProjectInfo } from '../getProjectInfo/index.mjs';
3
3
  import type { GenerateOptions } from '../types.mjs';
4
- export type ClientImports = {
5
- fetcher: string;
6
- validateOnClient: string | null;
7
- createRPC: string;
8
- };
9
- export default function generate({ projectInfo, forceNothingWrittenLog, templates, prettify: prettifyClient, fullSchema, fullSchemaJson, }: {
4
+ export default function generate({ isEnsuringClient, projectInfo, forceNothingWrittenLog, fullSchema, cliOptions, }: {
5
+ isEnsuringClient?: boolean;
10
6
  projectInfo: ProjectInfo;
11
7
  forceNothingWrittenLog?: boolean;
12
8
  fullSchema: VovkFullSchema;
13
- } & Pick<GenerateOptions, 'templates' | 'prettify' | 'fullSchemaJson'>): Promise<void>;
9
+ cliOptions?: GenerateOptions;
10
+ }): Promise<void>;
@@ -1,80 +1,54 @@
1
1
  import path from 'node:path';
2
2
  import fs from 'node:fs/promises';
3
- import ejs from 'ejs';
4
3
  import matter from 'gray-matter';
5
4
  import _ from 'lodash';
6
5
  import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
7
- import prettify from '../utils/prettify.mjs';
8
- import getClientTemplates, { DEFAULT_FULL_SCHEMA_FILE_NAME } from './getClientTemplates.mjs';
6
+ import getClientTemplateFiles from './getClientTemplateFiles.mjs';
9
7
  import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
10
- import { ROOT_SEGMENT_SCHEMA_NAME, SEGMENTS_SCHEMA_DIR_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
11
8
  import pickSegmentFullSchema from '../utils/pickSegmentFullSchema.mjs';
12
9
  import removeUnlistedDirectories from '../utils/removeUnlistedDirectories.mjs';
13
- async function writeOneClientFile({ projectInfo, clientTemplate, fullSchema, prettifyClient, segmentName, imports, templateContent, matterResult: { data, content }, }) {
14
- const { config, apiRoot, segments } = projectInfo;
15
- const { templatePath, outDir, origin } = clientTemplate;
16
- if (!templatePath) {
17
- throw new Error(`Template path is not defined for ${clientTemplate.templateName} but used at writeOneClientFile`);
10
+ import getTemplateClientImports from './getTemplateClientImports.mjs';
11
+ import mergePackages from './mergePackages.mjs';
12
+ import writeOneClientFile from './writeOneClientFile.mjs';
13
+ const getIncludedSegmentNames = (config, segments, configKey) => {
14
+ if ('includeSegments' in config[configKey] &&
15
+ 'excludeSegments' in config[configKey]) {
16
+ throw new Error(`Both includeSegments and excludeSegments are set in ${configKey} config. Please use only one of them.`);
18
17
  }
19
- const outPath = path.join(outDir, typeof segmentName === 'string' ? segmentName || ROOT_SEGMENT_SCHEMA_NAME : '', path.basename(templatePath).replace('.ejs', ''));
20
- // Data for the EJS templates:
21
- const t = {
22
- _, // lodash
23
- ROOT_SEGMENT_SCHEMA_NAME,
24
- SEGMENTS_SCHEMA_DIR_NAME,
25
- apiRoot: origin ? `${origin}/${config.rootEntry}` : apiRoot,
26
- imports,
27
- fullSchema,
28
- schemaOutDir: typeof segmentName === 'string'
29
- ? path.relative(path.join(outDir, segmentName || ROOT_SEGMENT_SCHEMA_NAME), config.schemaOutDir)
30
- : path.relative(outDir, config.schemaOutDir),
31
- segmentMeta: Object.fromEntries(segments.map(({ segmentName: sName, routeFilePath, segmentImportPath }) => [
32
- sName,
33
- {
34
- routeFilePath,
35
- segmentImportPath: typeof segmentName === 'string'
36
- ? `${_.times((segmentName.match(/\//g)?.length ?? 0) + 1)
37
- .map(() => '..')
38
- .join('/')}/${segmentImportPath}`
39
- : segmentImportPath,
40
- },
41
- ])),
42
- };
43
- if (data.imports instanceof Array) {
44
- for (const imp of data.imports) {
45
- t.imports = {
46
- ...t.imports,
47
- [imp]: await import(imp),
48
- };
49
- }
50
- }
51
- // Render the template
52
- let rendered = templatePath.endsWith('.ejs')
53
- ? ejs.render(content, { t }, {
54
- filename: templatePath,
18
+ const includedSegmentNames = 'includeSegments' in config[configKey] &&
19
+ Array.isArray(config[configKey].includeSegments)
20
+ ? config[configKey].includeSegments.map((segmentName) => {
21
+ const segment = segments.find(({ segmentName: sName }) => sName === segmentName);
22
+ if (!segment) {
23
+ throw new Error(`Segment "${segmentName}" not found in the config for ${configKey}`);
24
+ }
25
+ return segment.segmentName;
55
26
  })
56
- : templateContent;
57
- // Optionally prettify
58
- if (prettifyClient || config.prettifyClient) {
59
- rendered = await prettify(rendered, outPath);
27
+ : 'excludeSegments' in config[configKey] &&
28
+ Array.isArray(config[configKey].excludeSegments)
29
+ ? segments
30
+ .filter(({ segmentName }) => !config[configKey].excludeSegments?.includes(segmentName))
31
+ .map(({ segmentName }) => segmentName)
32
+ : segments.map(({ segmentName }) => segmentName);
33
+ return includedSegmentNames;
34
+ };
35
+ function logClientGenerationResults({ results, log, isEnsuringClient = false, forceNothingWrittenLog = false, clientType = 'Full', startTime, }) {
36
+ const writtenResults = results.filter(({ written }) => written);
37
+ const duration = Date.now() - startTime;
38
+ if (writtenResults.length) {
39
+ const groupedByDir = _.groupBy(writtenResults, ({ outAbsoluteDir }) => outAbsoluteDir);
40
+ for (const [outAbsoluteDir, dirResults] of Object.entries(groupedByDir)) {
41
+ const templateNames = _.uniq(dirResults.map(({ templateName }) => templateName));
42
+ log.info(`${clientType} client${isEnsuringClient ? ' placeholder' : ''} is generated to ${chalkHighlightThing(outAbsoluteDir)} from template${templateNames.length !== 1 ? 's' : ''} ${chalkHighlightThing(templateNames.map((s) => `"${s}"`).join(', '))} in ${duration}ms`);
43
+ }
60
44
  }
61
- // Read existing file content to compare
62
- const existingContent = await fs.readFile(outPath, 'utf-8').catch(() => null);
63
- // Determine if we need to rewrite the file, ignore 1st line
64
- const needsWriting = !existingContent ||
65
- existingContent.trim().split('\n').slice(1).join('\n') !== rendered.trim().split('\n').slice(1).join('\n');
66
- if (needsWriting) {
67
- await fs.mkdir(path.dirname(outPath), { recursive: true });
68
- await fs.writeFile(outPath, rendered);
45
+ else if (!isEnsuringClient) {
46
+ const logOrDebug = forceNothingWrittenLog ? log.info : log.debug;
47
+ logOrDebug(`${clientType} client is up to date and doesn't need to be regenerated (${duration}ms)`);
69
48
  }
70
- return { written: needsWriting };
71
49
  }
72
- export default async function generate({ projectInfo, forceNothingWrittenLog, templates, prettify: prettifyClient = false, fullSchema, fullSchemaJson, }) {
73
- const now = Date.now();
74
- const generateFrom = templates ?? projectInfo.config.generateFrom;
75
- const noClient = templates?.[0] === 'none';
76
- const { config, cwd, log, clientImports, segments } = projectInfo;
77
- const { clientOutDirAbsolutePath, templateFiles } = await getClientTemplates({ config, cwd, generateFrom, log });
50
+ export default async function generate({ isEnsuringClient = false, projectInfo, forceNothingWrittenLog, fullSchema, cliOptions, }) {
51
+ const { config, cwd, log, segments } = projectInfo;
78
52
  // Ensure that each segment has a matching schema if it needs to be emitted:
79
53
  for (let i = 0; i < segments.length; i++) {
80
54
  const { segmentName } = segments[i];
@@ -85,105 +59,129 @@ export default async function generate({ projectInfo, forceNothingWrittenLog, te
85
59
  if (!schema.emitSchema)
86
60
  continue;
87
61
  }
88
- let written = false;
89
- const fullSchemaOutAbsolutePaths = new Map();
90
- const usedTemplateNames = new Set();
91
- // Process each template in parallel
92
- await Promise.all(templateFiles.map(async (clientTemplate) => {
93
- const { templatePath, templateName, outDir, fullSchemaJSONFileName } = clientTemplate;
94
- if (!noClient && templatePath) {
95
- // Read the EJS template
96
- const templateContent = await fs.readFile(templatePath, 'utf-8');
97
- const matterResult = templatePath.endsWith('.ejs')
62
+ if (config.fullClient.enabled) {
63
+ const now = Date.now();
64
+ const segmentNames = getIncludedSegmentNames(config, segments, 'fullClient');
65
+ const fullClientTemplateFiles = await getClientTemplateFiles({
66
+ config,
67
+ cwd,
68
+ log,
69
+ cliOptions,
70
+ configKey: 'fullClient',
71
+ });
72
+ const fullClientResults = await Promise.all(fullClientTemplateFiles.map(async (clientTemplateFile) => {
73
+ const { templateFilePath, templateName, templateDef, outCwdRelativeDir } = clientTemplateFile;
74
+ const templateContent = await fs.readFile(templateFilePath, 'utf-8');
75
+ const matterResult = templateFilePath.endsWith('.ejs')
98
76
  ? matter(templateContent)
99
77
  : { data: { imports: [] }, content: templateContent };
100
- if (config.generateFullClient) {
101
- const { written: isWritten } = await writeOneClientFile({
78
+ const clientImports = await getTemplateClientImports({
79
+ config,
80
+ segments,
81
+ outCwdRelativeDir,
82
+ });
83
+ const packageJson = await mergePackages({
84
+ cwd,
85
+ config,
86
+ packages: [config.fullClient.package, templateDef.fullClient?.package],
87
+ });
88
+ const { written } = await writeOneClientFile({
89
+ cwd,
90
+ projectInfo,
91
+ clientTemplateFile,
92
+ fullSchema: pickSegmentFullSchema(fullSchema, segmentNames),
93
+ prettifyClient: config.prettifyClient,
94
+ segmentName: null,
95
+ imports: clientImports.fullClient,
96
+ templateContent,
97
+ matterResult,
98
+ package: packageJson,
99
+ isEnsuringClient,
100
+ outCwdRelativeDir,
101
+ origin: config.origin ?? templateDef?.origin ?? null,
102
+ });
103
+ const outAbsoluteDir = path.join(cwd, outCwdRelativeDir);
104
+ return {
105
+ written,
106
+ templateName,
107
+ outAbsoluteDir,
108
+ };
109
+ }));
110
+ logClientGenerationResults({
111
+ results: fullClientResults,
112
+ log,
113
+ isEnsuringClient,
114
+ forceNothingWrittenLog,
115
+ clientType: 'Full',
116
+ startTime: now,
117
+ });
118
+ }
119
+ if (config.segmentedClient.enabled) {
120
+ const now = Date.now();
121
+ const segmentNames = getIncludedSegmentNames(config, segments, 'segmentedClient');
122
+ const segmentedClientTemplateFiles = await getClientTemplateFiles({
123
+ config,
124
+ cwd,
125
+ log,
126
+ cliOptions,
127
+ configKey: 'segmentedClient',
128
+ });
129
+ const segmentedClientResults = await Promise.all(segmentedClientTemplateFiles.map(async (clientTemplateFile) => {
130
+ const { templateFilePath, templateName, templateDef, outCwdRelativeDir } = clientTemplateFile;
131
+ const templateContent = await fs.readFile(templateFilePath, 'utf-8');
132
+ const matterResult = templateFilePath.endsWith('.ejs')
133
+ ? matter(templateContent)
134
+ : { data: { imports: [] }, content: templateContent };
135
+ const results = await Promise.all(segmentNames.map(async (segmentName) => {
136
+ const clientImports = await getTemplateClientImports({
137
+ config,
138
+ segments,
139
+ outCwdRelativeDir,
140
+ });
141
+ const packageJson = await mergePackages({
142
+ cwd,
143
+ config,
144
+ packages: [
145
+ config.segmentedClient.packages?.[segmentName],
146
+ templateDef.segmentedClient?.packages?.[segmentName],
147
+ ],
148
+ });
149
+ const { written } = await writeOneClientFile({
150
+ cwd,
102
151
  projectInfo,
103
- clientTemplate,
104
- fullSchema,
105
- prettifyClient,
106
- segmentName: null,
107
- imports: clientImports.fullClient,
152
+ clientTemplateFile,
153
+ fullSchema: pickSegmentFullSchema(fullSchema, [segmentName]),
154
+ prettifyClient: config.prettifyClient,
155
+ segmentName,
156
+ imports: clientImports.segmentedClient[segmentName],
108
157
  templateContent,
109
158
  matterResult,
159
+ package: packageJson,
160
+ isEnsuringClient,
161
+ outCwdRelativeDir,
162
+ origin: config.origin ?? templateDef?.origin ?? null,
110
163
  });
111
- if (isWritten) {
112
- usedTemplateNames.add(templateName);
113
- }
114
- written ||= isWritten;
115
- }
116
- // TODO Remove files if generateFullClient is false ???
117
- if (config.generateSegmentClient) {
118
- // Generate client files for each segment
119
- await Promise.all(segments.map(async ({ segmentName }) => {
120
- const segmentFullSchema = {
121
- config: fullSchema.config,
122
- segments: { [segmentName]: fullSchema.segments[segmentName] },
123
- };
124
- const { written: isWritten } = await writeOneClientFile({
125
- projectInfo,
126
- clientTemplate,
127
- fullSchema: segmentFullSchema,
128
- prettifyClient,
129
- segmentName,
130
- imports: clientImports.schemaClient[segmentName],
131
- templateContent,
132
- matterResult,
133
- });
134
- if (isWritten) {
135
- usedTemplateNames.add(templateName);
136
- }
137
- written ||= isWritten;
138
- }));
139
- await removeUnlistedDirectories(outDir, segments.map(({ segmentName }) => segmentName || ROOT_SEGMENT_SCHEMA_NAME));
140
- }
141
- else {
142
- await removeUnlistedDirectories(outDir, []);
143
- }
144
- }
145
- if (config.generateFullClient) {
146
- const fullSchemaOutAbsolutePath = fullSchemaJSONFileName ? path.resolve(outDir, fullSchemaJSONFileName) : null;
147
- if (fullSchemaOutAbsolutePath) {
148
- fullSchemaOutAbsolutePaths.set(null, fullSchemaOutAbsolutePath);
149
- }
150
- }
151
- if (config.generateSegmentClient) {
152
- segments.forEach(({ segmentName }) => {
153
- const fullSchemaOutAbsolutePath = fullSchemaJSONFileName
154
- ? path.resolve(outDir, segmentName || ROOT_SEGMENT_SCHEMA_NAME, fullSchemaJSONFileName)
155
- : null;
156
- if (fullSchemaOutAbsolutePath) {
157
- fullSchemaOutAbsolutePaths.set(segmentName, fullSchemaOutAbsolutePath);
158
- }
159
- });
160
- }
161
- }));
162
- if (fullSchemaJson) {
163
- const fullSchemaOutAbsolutePath = fullSchemaJson
164
- ? path.resolve(clientOutDirAbsolutePath, typeof fullSchemaJson === 'string' ? fullSchemaJson : DEFAULT_FULL_SCHEMA_FILE_NAME)
165
- : null;
166
- if (fullSchemaOutAbsolutePath) {
167
- fullSchemaOutAbsolutePaths.set(null, fullSchemaOutAbsolutePath);
168
- }
169
- }
170
- if (fullSchemaOutAbsolutePaths.size) {
171
- await Promise.all(Array.from(fullSchemaOutAbsolutePaths.entries()).map(async ([segmentName, outPath]) => {
172
- const schema = typeof segmentName === 'string' ? pickSegmentFullSchema(fullSchema, segmentName) : fullSchema;
173
- await fs.mkdir(path.dirname(outPath), { recursive: true });
174
- await fs.writeFile(outPath, JSON.stringify(schema, null, 2));
164
+ return {
165
+ written,
166
+ templateName,
167
+ };
168
+ }));
169
+ const outAbsoluteDir = path.join(cwd, outCwdRelativeDir);
170
+ // Remove unlisted directories in the output directory
171
+ await removeUnlistedDirectories(outAbsoluteDir, segmentNames);
172
+ return {
173
+ written: results.some(({ written }) => written),
174
+ templateName,
175
+ outAbsoluteDir,
176
+ };
175
177
  }));
176
- log.info(`Full schema has been written to ${Array.from(fullSchemaOutAbsolutePaths)
177
- .map((s) => `"${s}"`)
178
- .join(', ')}`);
179
- }
180
- if (written) {
181
- log.info(`Client generated from template${usedTemplateNames.size !== 1 ? 's' : ''} ${chalkHighlightThing(Array.from(usedTemplateNames)
182
- .map((s) => `"${s}"`)
183
- .join(', '))} in ${Date.now() - now}ms`);
184
- }
185
- else {
186
- const logOrDebug = forceNothingWrittenLog ? log.info : log.debug;
187
- logOrDebug(`Client is up to date and doesn't need to be regenerated (${Date.now() - now}ms)`);
178
+ logClientGenerationResults({
179
+ results: segmentedClientResults,
180
+ log,
181
+ isEnsuringClient,
182
+ forceNothingWrittenLog,
183
+ clientType: 'Segmented',
184
+ startTime: now,
185
+ });
188
186
  }
189
187
  }