Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
31 changes: 31 additions & 0 deletions .changeset/components-folder-organization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
'@baseplate-dev/react-generators': patch
---

Reorganize components folder structure in generated codebases

The components folder structure has been reorganized to improve organization and reduce bundle size:

**Breaking Changes:**

- Removed bundle export at `components/index.ts` to prevent importing all components at once
- Moved all UI components from `components/` to `components/ui/` folder

**New Structure:**

```
components/
├── ui/ # UI components
│ ├── button.tsx
│ ├── input.tsx
│ └── ...
└── [other-components] # Custom application components
```

**Migration:**

- Replace `import { Button } from '@src/components'` with `import { Button } from '@src/components/ui/button'`
- Update imports to use specific component paths instead of barrel exports
- UI components are now co-located in the `ui/` subfolder for better organization

This change improves tree-shaking, reduces bundle size, and provides clearer separation between UI library components and custom application components.
33 changes: 33 additions & 0 deletions .changeset/template-name-syntax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
'@baseplate-dev/core-generators': patch
---

Add $templateName syntax for intra-generator template references

Templates can now reference other templates within the same generator using the `$templateName` syntax. This enables templates to access file paths of other templates in the same generator during generation.

Key features:

- Use `$templateName` in template files to reference other generator templates
- Kebab-case template names are automatically converted to camelCase (e.g., `session-constants` → `sessionConstants`)
- Configure referenced templates using the `referencedGeneratorTemplates` field in extractor.json
- Works seamlessly with existing variable replacement and import maps
- Provides clear error messages for missing template references

Example usage:

```typescript
// In template file
import { Constants } from '$sessionConstants';
import { Utils } from '$authUtils';

// In extractor.json
{
"user-service": {
"sourceFile": "services/user.service.ts",
"referencedGeneratorTemplates": ["session-constants", "auth-utils"]
}
}
```

This feature is designed for intra-generator template references only. For cross-generator references, continue using import map providers.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface NodeEslintRenderers {
RenderTsTemplateFileActionInput<
typeof NODE_ESLINT_TEMPLATES.eslintConfig
>,
'destination' | 'importMapProviders' | 'template'
'destination' | 'importMapProviders' | 'template' | 'generatorPaths'
>,
) => BuilderAction;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"importMapProviders": {},
"pathRootRelativePath": "{src-root}/utils/nulls.ts",
"projectExports": { "restrictObjectNulls": {} },
"referencedGeneratorTemplates": ["normalize-types"],
"sourceFile": "src/utils/nulls.ts",
"variables": {}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface NodeTsUtilsRenderers {
render: (
options: Omit<
RenderTsTemplateFileActionInput<typeof NODE_TS_UTILS_TEMPLATES.arrays>,
'destination' | 'importMapProviders' | 'template'
'destination' | 'importMapProviders' | 'template' | 'generatorPaths'
>,
) => BuilderAction;
};
Expand All @@ -24,23 +24,23 @@ export interface NodeTsUtilsRenderers {
RenderTsTemplateFileActionInput<
typeof NODE_TS_UTILS_TEMPLATES.normalizeTypes
>,
'destination' | 'importMapProviders' | 'template'
'destination' | 'importMapProviders' | 'template' | 'generatorPaths'
>,
) => BuilderAction;
};
nulls: {
render: (
options: Omit<
RenderTsTemplateFileActionInput<typeof NODE_TS_UTILS_TEMPLATES.nulls>,
'destination' | 'importMapProviders' | 'template'
'destination' | 'importMapProviders' | 'template' | 'generatorPaths'
>,
) => BuilderAction;
};
string: {
render: (
options: Omit<
RenderTsTemplateFileActionInput<typeof NODE_TS_UTILS_TEMPLATES.string>,
'destination' | 'importMapProviders' | 'template'
'destination' | 'importMapProviders' | 'template' | 'generatorPaths'
>,
) => BuilderAction;
};
Expand Down Expand Up @@ -81,6 +81,7 @@ const nodeTsUtilsRenderersTask = createGeneratorTask({
typescriptFile.renderTemplateFile({
template: NODE_TS_UTILS_TEMPLATES.nulls,
destination: paths.nulls,
generatorPaths: paths,
...options,
}),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const nulls = createTsTemplateFile({
importMapProviders: {},
name: 'nulls',
projectExports: { restrictObjectNulls: {} },
referencedGeneratorTemplates: { normalizeTypes: {} },
source: {
path: path.join(import.meta.dirname, '../templates/src/utils/nulls.ts'),
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @ts-nocheck

import type { NormalizeTypes } from './normalize-types.js';
import type { NormalizeTypes } from '$normalizeTypes';

/**
* Restricts an object from having null in certain fields. This
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const tsUtilsGenerator = createGenerator({
template,
destination: paths[key as TsUtilKey],
generatorInfo: builder.generatorInfo,
generatorPaths: paths as unknown as Record<'', string>,
});
Comment on lines +33 to 34
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use a normal string index signature instead of Record<'', string>.

Record<'', string> creates a type with the single key "", which is almost certainly not what you mean and may trip type-checking or autocomplete later on.
Either remove the cast altogether or cast to Record<string, string>:

-  generatorPaths: paths as unknown as Record<'', string>,
+  generatorPaths: paths as unknown as Record<string, string>,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
generatorPaths: paths as unknown as Record<'', string>,
});
generatorPaths: paths as unknown as Record<string, string>,
});
🤖 Prompt for AI Agents
In packages/core-generators/src/generators/node/ts-utils/ts-utils.generator.ts
at lines 33-34, the type cast uses Record<'', string>, which incorrectly defines
a record with only an empty string key. Replace this with Record<string, string>
to correctly type the object as having string keys and string values, or remove
the cast if unnecessary.

}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { safeMergeAll } from '@baseplate-dev/utils';
import path from 'node:path';
import { z } from 'zod';

import type { RenderTsTemplateGroupActionInput as RenderTsTemplateGroupActionInputV2 } from '#src/renderers/typescript/actions/render-ts-template-group-action.js';
import type { RenderTsTemplateGroupActionInput } from '#src/renderers/typescript/actions/render-ts-template-group-action.js';
import type {
RenderTsCodeFileTemplateOptions,
RenderTsFragmentActionInput,
Expand Down Expand Up @@ -90,7 +90,7 @@ export interface TypescriptFileProvider {
* @returns The action for the template group
*/
addLazyTemplateGroup<T extends TsTemplateGroup = TsTemplateGroup>(
payload: RenderTsTemplateGroupActionInputV2<T> & {
payload: RenderTsTemplateGroupActionInput<T> & {
generatorInfo: GeneratorInfo;
},
options?: Omit<LazyTemplateFileEntry, 'payload'>,
Expand All @@ -116,7 +116,7 @@ export interface TypescriptFileProvider {
renderTemplateGroup<
T extends Record<string, TsTemplateFile> = Record<string, TsTemplateFile>,
>(
payload: RenderTsTemplateGroupActionInputV2<T>,
payload: RenderTsTemplateGroupActionInput<T>,
): BuilderAction;
/**
* Marks an import as used
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface NodeVitestRenderers {
RenderTsTemplateFileActionInput<
typeof NODE_VITEST_TEMPLATES.globalSetup
>,
'destination' | 'importMapProviders' | 'template'
'destination' | 'importMapProviders' | 'template' | 'generatorPaths'
>,
) => BuilderAction;
};
Expand All @@ -26,7 +26,7 @@ export interface NodeVitestRenderers {
RenderTsTemplateFileActionInput<
typeof NODE_VITEST_TEMPLATES.vitestConfig
>,
'destination' | 'importMapProviders' | 'template'
'destination' | 'importMapProviders' | 'template' | 'generatorPaths'
>,
) => BuilderAction;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { differenceSet } from '@baseplate-dev/utils';
import type { TsPositionedHoistedFragment } from '../fragments/types.js';
import type { RenderTsCodeFileTemplateOptions } from '../renderers/file.js';
import type {
InferGeneratorPathsFromReferencedGeneratorMap,
InferImportMapProvidersFromProviderTypeMap,
InferTsTemplateVariablesFromMap,
TsTemplateFile,
Expand Down Expand Up @@ -58,11 +59,25 @@ type RenderTsTemplateFileActionImportMapProvidersInput<
>;
};

type RenderTsTemplateFileActionReferencedGeneratorTemplatesInput<
T extends TsTemplateFile,
> = keyof Exclude<T['referencedGeneratorTemplates'], undefined> extends never
? Partial<{
// Slightly awkward hack to force Typescript to enforce the keys for an empty generator paths object
generatorPaths: Partial<Record<'', string>>;
}>
: {
generatorPaths: InferGeneratorPathsFromReferencedGeneratorMap<
T['referencedGeneratorTemplates']
>;
};

export type RenderTsTemplateFileActionInput<
T extends TsTemplateFile = TsTemplateFile,
> = RenderTsTemplateFileActionInputBase<T> &
RenderTsTemplateFileActionVariablesInput<T> &
RenderTsTemplateFileActionImportMapProvidersInput<T>;
RenderTsTemplateFileActionImportMapProvidersInput<T> &
RenderTsTemplateFileActionReferencedGeneratorTemplatesInput<T>;

export function renderTsTemplateFileAction<
T extends TsTemplateFile = TsTemplateFile,
Expand All @@ -75,6 +90,7 @@ export function renderTsTemplateFileAction<
importMapProviders,
renderOptions,
positionedHoistedFragments,
generatorPaths,
generatorInfo: providedGeneratorInfo,
}: RenderTsTemplateFileActionInput<T>): BuilderAction {
return {
Expand Down Expand Up @@ -124,6 +140,7 @@ export function renderTsTemplateFileAction<
variables: variableValues,
importMapProviders,
positionedHoistedFragments,
generatorPaths: generatorPaths ?? {},
options: {
...renderOptions,
includeMetadata: shouldIncludeMetadata,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import path from 'node:path';

import type { RenderTsCodeFileTemplateOptions } from '../renderers/file.js';
import type {
InferGeneratorPathsFromReferencedGeneratorMap,
InferImportMapProvidersFromProviderTypeMap,
InferTsTemplateVariablesFromMap,
TsTemplateFile,
Expand Down Expand Up @@ -48,6 +49,13 @@ type InferImportMapProvidersFromTemplateGroup<T extends TsTemplateGroup> =
>;
}>;

type InferGeneratorPathsFromTemplateGroup<T extends TsTemplateGroup> =
IntersectionOfValues<{
[K in keyof T]: InferGeneratorPathsFromReferencedGeneratorMap<
T[K]['referencedGeneratorTemplates']
>;
}>;

interface RenderTsTemplateGroupActionInputBase<T extends TsTemplateGroup> {
group: T;
paths: {
Expand All @@ -74,7 +82,14 @@ export type RenderTsTemplateGroupActionInput<
? {
importMapProviders?: never;
}
: { importMapProviders: InferImportMapProvidersFromTemplateGroup<T> });
: { importMapProviders: InferImportMapProvidersFromTemplateGroup<T> }) &
(keyof InferGeneratorPathsFromTemplateGroup<T> extends never
? {
generatorPaths?: never;
}
: {
generatorPaths: InferGeneratorPathsFromTemplateGroup<T>;
});

/**
* Extracts the template file inputs from a template group
Expand All @@ -90,6 +105,7 @@ export function extractTsTemplateFileInputsFromTemplateGroup<
importMapProviders,
writeOptions,
renderOptions,
generatorPaths,
}: RenderTsTemplateGroupActionInput<T>): RenderTsTemplateFileActionInput[] {
const fileActionInputs: RenderTsTemplateFileActionInput[] = [];
const typedImportMapProviders =
Expand Down Expand Up @@ -124,6 +140,7 @@ export function extractTsTemplateFileInputsFromTemplateGroup<
: undefined,
writeOptions: writeOptions?.[key],
importMapProviders: templateSpecificProviders,
generatorPaths: generatorPaths ?? {},
renderOptions: {
...renderOptions,
resolveModule: (specifier) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ResolverFactory } from 'oxc-resolver';
import type { SourceFile } from 'ts-morph';

import { camelCase } from 'es-toolkit';
import { isBuiltin } from 'node:module';
import path from 'node:path';
import { Node, Project, SyntaxKind } from 'ts-morph';
Expand Down Expand Up @@ -28,9 +29,9 @@ export interface TsTemplateImportLookupContext {
*/
outputDirectory: string;
/**
* The output relative paths of the files that are generated by the generator.
* A map of output relative paths to the name of the template in the generator.
*/
internalOutputRelativePaths: string[];
internalOutputRelativePaths: Map<string, string>;
/**
* The resolver factory to use to resolve imports.
*/
Expand Down Expand Up @@ -75,6 +76,24 @@ function toTypesPackageName(pkgName: string): string | undefined {
return `@types/${pkgName}`;
}

/**
* The result of organizing the imports in a Typescript template file.
*/
interface OrganizeTsTemplateImportsResult {
/**
* The contents of the file with the imports converted to template paths.
*/
contents: string;
/**
* The project exports that are used in the file.
*/
usedProjectExports: TsProjectExport[];
/**
* The generator files that are used in the file.
*/
referencedGeneratorTemplates: Set<string>;
}

Comment on lines +82 to +96
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Export the OrganizeTsTemplateImportsResult interface.

Per the coding guidelines, interfaces should be exported if they're used as return types of exported functions.

-interface OrganizeTsTemplateImportsResult {
+export interface OrganizeTsTemplateImportsResult {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
interface OrganizeTsTemplateImportsResult {
/**
* The contents of the file with the imports converted to template paths.
*/
contents: string;
/**
* The project exports that are used in the file.
*/
usedProjectExports: TsProjectExport[];
/**
* The generator files that are used in the file.
*/
referencedGeneratorTemplates: Set<string>;
}
export interface OrganizeTsTemplateImportsResult {
/**
* The contents of the file with the imports converted to template paths.
*/
contents: string;
/**
* The project exports that are used in the file.
*/
usedProjectExports: TsProjectExport[];
/**
* The generator files that are used in the file.
*/
referencedGeneratorTemplates: Set<string>;
}
🤖 Prompt for AI Agents
In
packages/core-generators/src/renderers/typescript/extractor/organize-ts-template-imports.ts
around lines 82 to 96, the interface OrganizeTsTemplateImportsResult is not
exported but is used as a return type of an exported function. To fix this, add
the export keyword before the interface declaration to make it exported.

/**
* Organizes the imports in a Typescript template file.
* - Removes unused imports
Expand All @@ -96,10 +115,7 @@ export async function organizeTsTemplateImports(
resolver,
outputDirectory,
}: TsTemplateImportLookupContext,
): Promise<{
contents: string;
usedProjectExports: TsProjectExport[];
}> {
): Promise<OrganizeTsTemplateImportsResult> {
const project = new Project({ useInMemoryFileSystem: true });
const sourceFile = project.createSourceFile(filePath, contents);

Expand Down Expand Up @@ -163,6 +179,7 @@ export async function organizeTsTemplateImports(
);

const usedProjectExports: TsProjectExport[] = [];
const referencedGeneratorTemplates = new Set<string>();

const updatedImportDeclarations = await Promise.all(
tsImportDeclarations.map(async (importDeclaration) => {
Expand Down Expand Up @@ -198,16 +215,14 @@ export async function organizeTsTemplateImports(
}
// Don't modify imports for files in the generator
const relativeOutputPath = path.relative(outputDirectory, resolvedPath);
if (internalOutputRelativePaths.includes(relativeOutputPath)) {
const relativeImportPath = path
.relative(path.dirname(filePath), resolvedPath)
.replace(/\.(t|j)sx?$/, '.js');
const internalTemplateName =
internalOutputRelativePaths.get(relativeOutputPath);
if (internalTemplateName) {
referencedGeneratorTemplates.add(internalTemplateName);
const fixedImportDeclaration: TsImportDeclaration = {
...importDeclaration,
// convert to relative path
moduleSpecifier: relativeImportPath.startsWith('.')
? relativeImportPath
: `./${relativeImportPath}`,
// convert to internal template name
moduleSpecifier: `$${camelCase(internalTemplateName)}`,
};
return [fixedImportDeclaration];
}
Expand Down Expand Up @@ -291,5 +306,6 @@ export async function organizeTsTemplateImports(
return {
contents: sourceFile.getFullText(),
usedProjectExports,
referencedGeneratorTemplates,
};
}
Loading