Skip to content

Commit

Permalink
feat(backend)file generate function specific place (#71)
Browse files Browse the repository at this point in the history
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a new test suite for path utilities, validating various
path-related functionalities.
- Added a utility function to save generated code to a specified file
path.
	- Introduced a utility function to extract JSON from Markdown content.
	- Registered the `FileGeneratorHandler` for file generation processes.

- **Bug Fixes**
- Enhanced error handling for file writing and JSON extraction
processes.

- **Chores**
- Updated import statements for utility modules to improve code
organization.
	- Removed deprecated utility class related to JSON extraction.
- Modified import style for path and os modules in the common-path
configuration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Jackson Chen <[email protected]>
Co-authored-by: Jackson Chen <[email protected]>
  • Loading branch information
3 people authored Dec 16, 2024
1 parent dfe754a commit 418a3a8
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import * as path from 'path';
import * as os from 'os';
import { existsSync, rmdirSync } from 'fs-extra';
import * as pathUtil from '../../config/common-path';
import { saveGeneratedCode } from 'src/build-system/utils/files';
import {
getRootDir,
getProjectsDir,
getProjectPath,
} from 'src/config/common-path';

describe('Path Utilities', () => {
const APP_NAME = 'codefox';
const ROOT_DIR = path.join(os.homedir(), `.${APP_NAME}`);

const cleanUp = () => {
// if (existsSync(ROOT_DIR)) {
// rmdirSync(ROOT_DIR, { recursive: true });
// }
};

beforeEach(() => {
cleanUp();
});

afterAll(() => {
cleanUp();
});

it('should return a valid root directory', () => {
const rootDir = getRootDir();
expect(rootDir).toBeDefined();
expect(rootDir).toContain('.codefox');
});

it('should return a valid projects directory', () => {
const projectsDir = getProjectsDir();
expect(projectsDir).toBeDefined();
expect(projectsDir).toContain('projects');
});

it('should return a valid project path for a given ID', () => {
const projectId = 'test-project';
const projectPath = getProjectPath(projectId);
expect(projectPath).toBeDefined();
expect(projectPath).toContain(projectId);
});

it('should resolve paths correctly', () => {
const rootDir = pathUtil.getRootDir();
const projectsDir = pathUtil.getProjectsDir();
expect(rootDir).toBeDefined();
expect(projectsDir).toBeDefined();
});

it('should create and return the root directory', async () => {
const rootDir = pathUtil.getRootDir();

await generateAndSaveCode();
expect(rootDir).toBe(ROOT_DIR);
expect(existsSync(ROOT_DIR)).toBe(true);
});
});

async function generateAndSaveCode() {
const generatedCode = `
import { Controller, Get } from '@nestjs/common';
@Controller('example')
export class ExampleController {
@Get()
getHello(): string {
return 'Hello World!';
}
}
`;

const fileName = 'example.controller.ts';

try {
const filePath = await saveGeneratedCode(fileName, generatedCode);
console.log(`Generated code saved at: ${filePath}`);
} catch (error) {
console.error('Failed to save generated code:', error);
}
}
13 changes: 11 additions & 2 deletions backend/src/build-system/context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ModelProvider } from 'src/common/model-provider';
import { BuildHandlerManager } from './hanlder-manager';
import {
BuildExecutionState,
Expand All @@ -8,8 +7,15 @@ import {
} from './types';
import { Logger } from '@nestjs/common';
import { VirtualDirectory } from './virtual-dir';
import { ModelProvider } from 'src/common/model-provider';

export type GlobalDataKeys = 'projectName' | 'description' | 'platform';
import { v4 as uuidv4 } from 'uuid';

export type GlobalDataKeys =
| 'projectName'
| 'description'
| 'platform'
| 'projectUUID';
type ContextData = {
[key in GlobalDataKeys]: string;
} & Record<string, any>;
Expand Down Expand Up @@ -37,6 +43,9 @@ export class BuilderContext {
this.model = ModelProvider.getInstance();
this.logger = new Logger(`builder-context-${id}`);
this.virtualDirectory = new VirtualDirectory();

const projectUUID = uuidv4();
this.setData('projectUUID', projectUUID);
}

canExecute(nodeId: string): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { BuildHandler, BuildResult } from 'src/build-system/types';
import { BuilderContext } from 'src/build-system/context';
import { generateFileArchPrompt } from './prompt';
import { Logger } from '@nestjs/common';
import { FileUtil } from 'src/build-system/utils/util';
import { extractJsonFromMarkdown } from 'src/build-system/utils/strings';

export class FileArchGenerateHandler implements BuildHandler {
readonly id = 'op:FILE_ARCH::STATE:GENERATE';
Expand Down Expand Up @@ -50,7 +50,7 @@ export class FileArchGenerateHandler implements BuildHandler {
);

// validation test
jsonData = FileUtil.extractJsonFromMarkdown(fileArchContent);
jsonData = extractJsonFromMarkdown(fileArchContent);
if (jsonData == null) {
retry += 1;
this.logger.error('Extract Json From Markdown fail');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,22 @@ import * as toposort from 'toposort';
import { VirtualDirectory } from '../../../virtual-dir';
import { BuilderContext } from 'src/build-system/context';
import { BuildHandler, BuildResult } from 'src/build-system/types';
import { FileUtil } from 'src/build-system/utils/util';
import { extractJsonFromMarkdown } from 'src/build-system/utils/strings';
import { getProjectPath } from 'src/config/common-path';
import * as normalizePath from 'normalize-path';

export class FileGeneratorHandler {
export class FileGeneratorHandler implements BuildHandler {
readonly id = 'op:FILE_GENERATE::STATE:GENERATE';
private readonly logger = new Logger('FileGeneratorHandler');
private virtualDir: VirtualDirectory;

async run(context: BuilderContext, args: unknown): Promise<BuildResult> {
this.virtualDir = context.virtualDirectory;
const fileArch = args[0] as string;
const uuid = context.getData('projectUUID');

const projectSrcPath = getProjectPath(uuid);

// change here
const projectSrcPath = '';
this.generateFiles(JSON.stringify(fileArch, null, 2), projectSrcPath);

return {
Expand All @@ -34,7 +38,7 @@ export class FileGeneratorHandler {
markdownContent: string,
projectSrcPath: string,
): Promise<{ success: boolean; data: string }> {
const jsonData = FileUtil.extractJsonFromMarkdown(markdownContent);
const jsonData = extractJsonFromMarkdown(markdownContent);
// Build the dependency graph and detect cycles before any file operations
const { graph, nodes } = this.buildDependencyGraph(jsonData);
this.detectCycles(graph);
Expand All @@ -47,7 +51,7 @@ export class FileGeneratorHandler {

// Generate files in the correct order
for (const file of sortedFiles) {
const fullPath = path.resolve(projectSrcPath, file);
const fullPath = normalizePath(path.resolve(projectSrcPath, file));
this.logger.log(`Generating file in dependency order: ${fullPath}`);
// TODO(allen)
await this.createFile(fullPath);
Expand Down
2 changes: 2 additions & 0 deletions backend/src/build-system/hanlder-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { FileArchGenerateHandler } from './handlers/file-manager/file-arch';
import { BackendCodeHandler } from './handlers/backend/code-generate';
import { DBSchemaHandler } from './handlers/database/schemas/schemas';
import { DatabaseRequirementHandler } from './handlers/database/requirements-document';
import { FileGeneratorHandler } from './handlers/file-manager/file-generate';

export class BuildHandlerManager {
private static instance: BuildHandlerManager;
Expand All @@ -30,6 +31,7 @@ export class BuildHandlerManager {
new BackendCodeHandler(),
new DBSchemaHandler(),
new DatabaseRequirementHandler(),
new FileGeneratorHandler(),
];

for (const handler of builtInHandlers) {
Expand Down
26 changes: 26 additions & 0 deletions backend/src/build-system/utils/files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Logger } from '@nestjs/common';
import fs from 'fs-extra';

const logger = new Logger('file-utils');
/**
* Saves the given content to the specified file path using fs-extra.
* Ensures that all directories in the path exist before writing the file.
*
* @param filePath - The complete file path including the file name.
* @param content - The content to be written to the file.
* @returns The file path where the content was written.
* @throws Will throw an error if the file could not be written.
*/
export async function saveGeneratedCode(
filePath: string,
content: string,
): Promise<string> {
try {
// fs-extra's outputFile creates all directories if they don't exist
await fs.outputFile(filePath, content, 'utf8');
return filePath;
} catch (error) {
logger.error('Error saving generated code:', error);
throw error;
}
}
26 changes: 26 additions & 0 deletions backend/src/build-system/utils/strings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Logger } from '@nestjs/common';

const logger = new Logger('common-utils');

/**
* Extract JSON data from Markdown content.
* @param markdownContent The Markdown content containing the JSON.
*/
export function extractJsonFromMarkdown(markdownContent: string): {
files: Record<string, { dependsOn: string[] }>;
} {
const jsonMatch = /<GENERATEDCODE>([\s\S]*?)<\/GENERATEDCODE>/m.exec(
markdownContent,
);
if (!jsonMatch) {
logger.error('No JSON found in the provided Markdown content.');
return null;
}

try {
return JSON.parse(jsonMatch[1]);
} catch (error) {
logger.error('Invalid JSON format in the Markdown content: ' + error);
return null;
}
}
30 changes: 0 additions & 30 deletions backend/src/build-system/utils/util.ts

This file was deleted.

0 comments on commit 418a3a8

Please sign in to comment.