@baseplate-dev/core-generators
Version:
Core generators for Baseplate
155 lines (150 loc) • 7.3 kB
JavaScript
import { TEMPLATE_EXTRACTOR_GENERATED_DIRECTORY } from '@baseplate-dev/sync';
import { mapValuesOfMap } from '@baseplate-dev/utils';
import { posixJoin } from '@baseplate-dev/utils/node';
import { camelCase } from 'change-case';
import { z } from 'zod';
import { getGeneratedTemplateConstantName, getGeneratedTemplateExportName, getGeneratedTemplateInterfaceName, getGeneratedTemplateProviderName, resolvePackagePathSpecifier, } from '#src/renderers/extractor/utils/index.js';
import { tsImportBuilder } from '#src/renderers/typescript/imports/index.js';
import { renderTsCodeFileTemplate } from '#src/renderers/typescript/renderers/index.js';
import { TsCodeUtils, tsTemplate, } from '#src/renderers/typescript/utils/index.js';
export const GENERATED_PATHS_FILE_NAME = 'template-paths.ts';
const GENERATED_PATHS_FILE_PATH = posixJoin(TEMPLATE_EXTRACTOR_GENERATED_DIRECTORY, GENERATED_PATHS_FILE_NAME);
const GENERATED_PATHS_TEMPLATE = `
import { createProviderType } from '@baseplate-dev/sync';
export interface TPL_PATHS_TYPE_NAME {
TPL_PATHS_CONTENTS;
}
const TPL_PATHS_PROVIDER_EXPORT = createProviderType<TPL_PATHS_TYPE_NAME>('TPL_PATHS_PROVIDER_NAME');
TPL_PATHS_TASK_DECLARATION
export const TPL_PATHS_EXPORT_NAME = {
provider: TPL_PATHS_PROVIDER_EXPORT,
TPL_PATHS_TASK_EXPORT
}
`;
const PATH_ROOT_REGEX = /^{([^}]+)}/;
const pathProviderConfigSchema = z.object({
type: z.literal('path'),
/**
* The path roots offered by this provider.
*/
pathRoots: z.array(z.object({
/**
* The name of the path root e.g. `feature-root
*/
name: z.string().min(1),
/**
* The method to get the path root, e.g. `getDirectoryBase`
*/
method: z.string().min(1),
})),
});
export function getPathsFileExportNames(generatorName) {
return {
interfaceName: getGeneratedTemplateInterfaceName(generatorName, 'paths'),
providerExportName: getGeneratedTemplateExportName(generatorName, 'paths'),
taskName: getGeneratedTemplateExportName(generatorName, 'paths-task'),
rootExportName: getGeneratedTemplateConstantName(generatorName, 'paths'),
};
}
function createPathsTask(generatorName, pathMap, context, { providerExportName }) {
const extractorConfig = context.configLookup.getExtractorConfigOrThrow(generatorName);
const allPathProviders = context.configLookup.getProviderConfigsByType('path', pathProviderConfigSchema);
const pathRootsUsed = new Set();
for (const [, pathRootRelativePath] of pathMap) {
// Extract all the path roots used of the form {path-root-name}/...
const pathRootMatches = PATH_ROOT_REGEX.exec(pathRootRelativePath);
if (!pathRootMatches) {
throw new Error(`Path root relative path ${pathRootRelativePath} does not contain a path root`);
}
pathRootsUsed.add(pathRootMatches[1]);
}
const pathProviders = allPathProviders
.filter(({ config }) => config.pathRoots.some((p) => pathRootsUsed.has(p.name)))
.map((p) => ({
providerName: p.providerName,
providerVariableName: camelCase(p.providerName.replace(/Provider$/, '')),
providerPathSpecifier: p.packagePathSpecifier,
pathRoots: p.config.pathRoots
.filter((root) => pathRootsUsed.has(root.name))
.map((root) => ({
name: root.name,
variableName: camelCase(root.name),
methodName: root.method,
})),
}))
.toSorted((a, b) => a.providerName.localeCompare(b.providerName));
const dependencies = TsCodeUtils.mergeFragmentsAsObject(Object.fromEntries(pathProviders.map((p) => [
p.providerVariableName,
TsCodeUtils.importFragment(p.providerName, resolvePackagePathSpecifier(p.providerPathSpecifier, extractorConfig.packageName)),
])));
const providerVariableName = providerExportName.replace(/Provider$/, '');
const exports = TsCodeUtils.mergeFragmentsAsObject({
[providerVariableName]: `${providerExportName}.export()`,
});
const pathRootDeclarations = TsCodeUtils.mergeFragments(new Map(pathProviders.flatMap((p) => p.pathRoots.map(({ methodName, variableName }) => [
variableName,
tsTemplate `const ${variableName} = ${p.providerVariableName}.${methodName}()`,
]))), '\n').contents;
const pathRootLookup = Object.fromEntries(pathProviders.flatMap((p) => p.pathRoots.map((r) => [r.name, r.variableName])));
const pathsContents = TsCodeUtils.mergeFragmentsAsObject(mapValuesOfMap(pathMap, (pathRootRelativePath) => {
const resolvedPath = pathRootRelativePath.replace(PATH_ROOT_REGEX, (pathRootPlaceholder) => {
const pathRootName = pathRootPlaceholder.slice(1, -1);
const pathRootVariableName = pathRootLookup[pathRootName];
if (!pathRootVariableName) {
throw new Error(`Path root ${pathRootName} not found in path root lookup. Ensure it is registered with the providers.json file.`);
}
return `$\{${pathRootVariableName}}`;
});
return `\`${resolvedPath}\``;
}));
const run = tsTemplate `
function run({ ${pathProviders.map((p) => p.providerVariableName).join(', ')} }) {
${pathRootDeclarations}
return {
providers: {
${providerExportName}: ${pathsContents}
}
}
}
`;
return TsCodeUtils.mergeFragmentsAsObject({
dependencies,
exports,
run,
});
}
export async function writePathMapFile(generatorName, pathMap, context, options = {}) {
const extractorConfig = context.configLookup.getExtractorConfigOrThrow(generatorName);
const pathMapPath = posixJoin(extractorConfig.generatorDirectory, GENERATED_PATHS_FILE_PATH);
const fileExportNames = getPathsFileExportNames(generatorName);
const { interfaceName, providerExportName, taskName } = fileExportNames;
const taskFragment = options.skipTaskGeneration
? null
: createPathsTask(generatorName, pathMap, context, fileExportNames);
const pathMapContents = renderTsCodeFileTemplate({
templateContents: GENERATED_PATHS_TEMPLATE,
variables: {
TPL_PATHS_TYPE_NAME: interfaceName,
TPL_PATHS_CONTENTS: TsCodeUtils.mergeFragmentsAsInterfaceContent(new Map([...pathMap].map(([templateName]) => [templateName, 'string']))),
TPL_PATHS_PROVIDER_EXPORT: providerExportName,
TPL_PATHS_PROVIDER_NAME: getGeneratedTemplateProviderName(generatorName, 'paths'),
TPL_PATHS_TASK_DECLARATION: taskFragment
? TsCodeUtils.templateWithImports([
tsImportBuilder(['createGeneratorTask']).from('@baseplate-dev/sync'),
]) `const ${taskName} = createGeneratorTask(${taskFragment});`
: '',
TPL_PATHS_TASK_EXPORT: taskFragment ? `task: ${taskName},` : '',
TPL_PATHS_EXPORT_NAME: fileExportNames.rootExportName,
},
options: {
importSortOptions: {
internalPatterns: [/^#src/],
},
},
});
await context.fileContainer.writeFile(pathMapPath, pathMapContents);
return {
exportName: fileExportNames.rootExportName,
};
}
//# sourceMappingURL=paths-file.js.map