Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { createRawTemplateFile } from '@halfdomelabs/sync';

const TsconfigRawTemplate = createRawTemplateFile({
const tsconfig = createRawTemplateFile({
name: 'tsconfig',
source: { path: 'tsconfig.tpl.json' },
});

export const CORE_FASTIFY_SCRIPTS_RAW_TEMPLATES = {
TsconfigRawTemplate,
tsconfig,
};
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ export const fastifyScriptsGenerator = createGenerator({
build: async (builder) => {
await builder.apply(
renderRawTemplateFileAction({
template:
CORE_FASTIFY_SCRIPTS_RAW_TEMPLATES.TsconfigRawTemplate,
template: CORE_FASTIFY_SCRIPTS_RAW_TEMPLATES.tsconfig,
id: 'tsconfig',
destination: 'scripts/tsconfig.json',
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { createTextTemplateFile } from '@halfdomelabs/sync';

const ReadmeTextTemplate = createTextTemplateFile({
const readme = createTextTemplateFile({
name: 'readme',
source: { path: 'README.md' },
variables: { TPL_PROJECT: { description: 'Name of the project' } },
});

export const CORE_README_TEXT_TEMPLATES = {
ReadmeTextTemplate,
readme,
};
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,12 @@ export const readmeGenerator = createGenerator({
build: async (builder) => {
await builder.apply(
renderTextTemplateFileAction({
template: CORE_README_TEXT_TEMPLATES.ReadmeTextTemplate,
id: 'readme',
template: CORE_README_TEXT_TEMPLATES.readme,
destination: 'README.md',
variables: {
TPL_PROJECT: projectName,
},
options: {
shouldFormat: true,
shouldNeverOverwrite: true,
},
}),
Expand Down
2 changes: 2 additions & 0 deletions packages/project-builder-server/src/sync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {

import {
createCodebaseFileReaderFromDirectory,
deleteMetadataFiles,
GeneratorEngine,
writeGeneratorsMetadata,
writeTemplateMetadata,
Expand Down Expand Up @@ -165,6 +166,7 @@ export async function generateForDirectory({

// write metadata to the generated directory
if (templateMetadataWriter?.enabled) {
await deleteMetadataFiles(projectDirectory);
await writeGeneratorsMetadata(project, output.files, projectDirectory);
await writeTemplateMetadata(output.files, projectDirectory);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { createRawTemplateFile } from '@halfdomelabs/sync';

const FaviconRawTemplate = createRawTemplateFile({
const favicon = createRawTemplateFile({
name: 'favicon',
source: { path: 'public/favicon.ico' },
});

export const CORE_REACT_RAW_TEMPLATES = {
FaviconRawTemplate,
favicon,
};
Original file line number Diff line number Diff line change
@@ -1,28 +1,49 @@
import { createTextTemplateFile } from '@halfdomelabs/sync';
import {
createTextTemplateFile,
createTextTemplateGroup,
} from '@halfdomelabs/sync';

const ReadmeTextTemplate = createTextTemplateFile({
const readme = createTextTemplateFile({
name: 'readme',
group: 'static',
source: { path: 'README.md' },
variables: { TPL_PROJECT_NAME: { description: 'Name of the project' } },
});

const ViteEnvTextTemplate = createTextTemplateFile({
const viteEnv = createTextTemplateFile({
name: 'vite-env',
group: 'static',
source: { path: 'src/vite-env.d.ts' },
variables: {},
});

const IndexHtmlTextTemplate = createTextTemplateFile({
const indexHtml = createTextTemplateFile({
name: 'index-html',
group: 'static',
source: { path: 'index.html' },
variables: {
TPL_DESCRIPTION: { description: 'Description of the project' },
TPL_TITLE: { description: 'Title of the home page' },
},
});

const staticGroup = createTextTemplateGroup({
templates: {
readme: {
destination: 'README.md',
template: readme,
},
viteEnv: {
destination: 'src/vite-env.d.ts',
template: viteEnv,
},
indexHtml: {
destination: 'index.html',
template: indexHtml,
},
},
});

export const CORE_REACT_TEXT_TEMPLATES = {
ReadmeTextTemplate,
ViteEnvTextTemplate,
IndexHtmlTextTemplate,
staticGroup,
};
40 changes: 12 additions & 28 deletions packages/react-generators/src/generators/core/react/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
createNonOverwriteableMap,
createProviderType,
renderRawTemplateFileAction,
renderTextTemplateFileAction,
renderTextTemplateGroupAction,
} from '@halfdomelabs/sync';
import { z } from 'zod';

Expand Down Expand Up @@ -138,39 +138,23 @@ export const reactGenerator = createGenerator({
build: async (builder) => {
await builder.apply(
renderRawTemplateFileAction({
template: CORE_REACT_RAW_TEMPLATES.FaviconRawTemplate,
id: 'favicon',
template: CORE_REACT_RAW_TEMPLATES.favicon,
destination: 'public/favicon.ico',
}),
);

await builder.apply(
renderTextTemplateFileAction({
template: CORE_REACT_TEXT_TEMPLATES.ReadmeTextTemplate,
id: 'readme',
destination: 'README.md',
renderTextTemplateGroupAction({
group: CORE_REACT_TEXT_TEMPLATES.staticGroup,
baseDirectory: '',
variables: {
TPL_PROJECT_NAME: project.getProjectName(),
},
}),
);

await builder.apply(
renderTextTemplateFileAction({
template: CORE_REACT_TEXT_TEMPLATES.ViteEnvTextTemplate,
id: 'vite-env',
destination: 'src/vite-env.d.ts',
}),
);

await builder.apply(
renderTextTemplateFileAction({
template: CORE_REACT_TEXT_TEMPLATES.IndexHtmlTextTemplate,
id: 'index-html',
destination: 'index.html',
variables: {
TPL_TITLE: descriptor.title,
TPL_DESCRIPTION: descriptor.description,
readme: {
TPL_PROJECT_NAME: project.getProjectName(),
},
indexHtml: {
TPL_TITLE: descriptor.title,
TPL_DESCRIPTION: descriptor.description,
},
},
}),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,25 @@ export async function prepareGeneratorFiles({
throw new PrepareGeneratorFilesError(fileErrors);
}

const fileIdToRelativePathMap = new Map<string, string>();
for (const [relativePath, file] of files.entries()) {
if (fileIdToRelativePathMap.has(file.id)) {
throw new PrepareGeneratorFilesError([
{
relativePath,
error: new Error(
`File ID ${file.id} is already in use by file ${fileIdToRelativePathMap.get(
file.id,
)}`,
),
},
]);
}
fileIdToRelativePathMap.set(file.id, relativePath);
}

return {
files: operationResults,
fileIdToRelativePathMap: new Map(
Array.from(files.entries(), ([relativePath, file]) => [
file.id,
relativePath,
]),
),
fileIdToRelativePathMap,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ function createMockContext(
};
}

const DEFAULT_FILE_ID = 'test-id';
const DEFAULT_FILE_CONTENTS = 'test contents';

let fileId = 0;

function createMockFileData(overrides: Partial<FileData> = {}): FileData {
return {
id: DEFAULT_FILE_ID,
id: `test-id-${fileId++}`,
contents: DEFAULT_FILE_CONTENTS,
...overrides,
};
Expand All @@ -36,7 +37,12 @@ function createMockFileData(overrides: Partial<FileData> = {}): FileData {
describe('prepareGeneratorFiles', () => {
it('should prepare all files successfully', async () => {
const files = new Map([
['file1.txt', createMockFileData()],
[
'file1.txt',
createMockFileData({
id: 'test-id',
}),
],
[
'file2.txt',
createMockFileData({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export async function runTemplateFileExtractors(
);
}
return {
path: filePath,
path: filePath.replaceAll(path.sep, path.posix.sep),
metadata,
modifiedTime,
};
Expand Down
39 changes: 39 additions & 0 deletions packages/sync/src/templates/metadata/delete-metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { promises as fs } from 'node:fs';
import path from 'node:path';

import {
GENERATOR_INFO_FILENAME,
TEMPLATE_METADATA_FILENAME,
} from '../constants.js';

/**
* Recursively deletes all template metadata files and the generator metadata file
* from the specified directory and its subdirectories.
*
* @param directory - The directory to delete metadata files from
* @returns Promise that resolves when all metadata files are deleted
*/
export async function deleteMetadataFiles(directory: string): Promise<void> {
const entries = await fs.readdir(directory, { withFileTypes: true });

// Delete generator metadata file if it exists
const generatorMetadataPath = path.join(directory, GENERATOR_INFO_FILENAME);
try {
await fs.unlink(generatorMetadataPath);
} catch {
// Ignore if file doesn't exist
}

// Process each entry
for (const entry of entries) {
const fullPath = path.join(directory, entry.name);

if (entry.isDirectory()) {
// Recursively process subdirectories
await deleteMetadataFiles(fullPath);
} else if (entry.name === TEMPLATE_METADATA_FILENAME) {
// Delete template metadata file
await fs.unlink(fullPath);
}
}
}
65 changes: 65 additions & 0 deletions packages/sync/src/templates/metadata/delete-metadata.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { vol } from 'memfs';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import {
GENERATOR_INFO_FILENAME,
TEMPLATE_METADATA_FILENAME,
} from '../constants.js';
import { deleteMetadataFiles } from './delete-metadata.js';

vi.mock('node:fs');
vi.mock('node:fs/promises');

describe('deleteMetadataFiles', () => {
const testDirectory = '/test/project';

beforeEach(() => {
vol.reset();
// Initialize the virtual file system with test files
vol.fromJSON({
[testDirectory]: null,
[`${testDirectory}/${GENERATOR_INFO_FILENAME}`]: '{}',
[`${testDirectory}/src/controllers/${TEMPLATE_METADATA_FILENAME}`]: '{}',
[`${testDirectory}/src/models/${TEMPLATE_METADATA_FILENAME}`]: '{}',
[`${testDirectory}/src/controllers/user-controller.ts`]: '// some code',
[`${testDirectory}/src/models/user-model.ts`]: '// some code',
});
});

it('should delete all metadata files', async () => {
await deleteMetadataFiles(testDirectory);

// Verify generator metadata file is deleted
expect(() =>
vol.readFileSync(`${testDirectory}/${GENERATOR_INFO_FILENAME}`),
).toThrow();

// Verify template metadata files are deleted
expect(() =>
vol.readFileSync(
`${testDirectory}/src/controllers/${TEMPLATE_METADATA_FILENAME}`,
),
).toThrow();
expect(() =>
vol.readFileSync(
`${testDirectory}/src/models/${TEMPLATE_METADATA_FILENAME}`,
),
).toThrow();

// Verify other files are not deleted
expect(
vol.readFileSync(`${testDirectory}/src/controllers/user-controller.ts`),
).toBeDefined();
expect(
vol.readFileSync(`${testDirectory}/src/models/user-model.ts`),
).toBeDefined();
});

it('should handle non-existent metadata files gracefully', async () => {
// Delete all metadata files first
await deleteMetadataFiles(testDirectory);

// Try to delete again - should not throw
await expect(deleteMetadataFiles(testDirectory)).resolves.not.toThrow();
});
});
1 change: 1 addition & 0 deletions packages/sync/src/templates/metadata/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './delete-metadata.js';
export * from './metadata.js';
export * from './write-generators-metadata.js';
export * from './write-template-metadata.js';
Loading