Skip to content

Commit

Permalink
refactor(backend): refactor context part (#72)
Browse files Browse the repository at this point in the history
- how we get input data

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
  • Loading branch information
Sma1lboy and autofix-ci[bot] authored Dec 16, 2024
1 parent 418a3a8 commit 3ec3ff9
Show file tree
Hide file tree
Showing 23 changed files with 858 additions and 753 deletions.
4 changes: 2 additions & 2 deletions backend/src/build-system/__tests__/test-file-create.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import { FileGeneratorHandler } from '../handlers/file-generate'; // Update with actual file path to the handler
import * as normalizePath from 'normalize-path';
import normalizePath from 'normalize-path';
import { FileGeneratorHandler } from '../handlers/file-manager/file-generate';

describe('FileGeneratorHandler', () => {
const projectSrcPath = normalizePath(
Expand Down
53 changes: 23 additions & 30 deletions backend/src/build-system/__tests__/test-generate-doc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,14 @@ import { BuildSequence } from '../types';
import { BuildSequenceExecutor } from '../executor';
import * as fs from 'fs';
import * as path from 'path';
import { writeToFile } from './utils';

describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => {
// Generate a unique folder with a timestamp
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const logFolderPath = `./log-${timestamp}`;
const logFolderPath = `./logs/generate-docs-${timestamp}`;
fs.mkdirSync(logFolderPath, { recursive: true });

// Utility function to extract Markdown content and write to .md files
const writeMarkdownToFile = (handlerName: string, data: any) => {
// Extract "data" field and remove surrounding Markdown code block formatting
const markdownContent = data?.data?.replace(/```/g, '') || '';
const filePath = path.join(logFolderPath, `${handlerName}.md`);
fs.writeFileSync(filePath, markdownContent, 'utf8');
console.log(`Logged ${handlerName} result data to ${filePath}`);
};

it('should execute the full sequence and log results to individual files', async () => {
const sequence: BuildSequence = {
id: 'test-sequence',
Expand All @@ -32,7 +24,7 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => {
name: 'Generate PRD',
nodes: [
{
id: 'op:PRD::STATE:GENERATE',
id: 'op:PRD',
name: 'PRD Generation Node',
type: 'ANALYSIS',
subType: 'PRD',
Expand All @@ -44,11 +36,11 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => {
name: 'Generate UX Sitemap Document',
nodes: [
{
id: 'op:UXSMD::STATE:GENERATE',
id: 'op:UX:SMD',
name: 'UX Sitemap Document Node',
type: 'UX',
subType: 'SITEMAP',
requires: ['op:PRD::STATE:GENERATE'],
requires: ['op:PRD'],
},
],
},
Expand All @@ -57,11 +49,11 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => {
name: 'Generate UX Sitemap Structure',
nodes: [
{
id: 'op:UXSMS::STATE:GENERATE',
id: 'op:UX:SMS',
name: 'UX Sitemap Structure Node',
type: 'UX',
subType: 'VIEWS',
requires: ['op:UXSMD::STATE:GENERATE'],
requires: ['op:UX:SMD'],
},
],
},
Expand All @@ -70,9 +62,9 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => {
name: 'UX Data Map Document',
nodes: [
{
id: 'op:UX_DATAMAP::STATE:GENERATE',
id: 'op:UX:DATAMAP:DOC',
name: 'UX Data Map Document node',
requires: ['op:UXSMD::STATE:GENERATE'],
requires: ['op:UX:SMD'],
},
],
},
Expand All @@ -81,12 +73,12 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => {
name: 'file structure generation',
nodes: [
{
id: 'op:FSTRUCT::STATE:GENERATE',
id: 'op:FILE:STRUCT',
name: 'file structure generation',
requires: [
'op:UXSMD::STATE:GENERATE',
'op:UX_DATAMAP::STATE:GENERATE',
],
requires: ['op:UX:SMD', 'op:UX:DATAMAP:DOC'],
options: {
projectPart: 'frontend',
},
},
],
},
Expand All @@ -95,11 +87,12 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => {
name: 'File_Arch Document',
nodes: [
{
id: 'op:FILE_ARCH::STATE:GENERATE',
id: 'op:FILE:ARCH',
name: 'File_Arch',
requires: [
'op:FSTRUCT::STATE:GENERATE',
'op:UX_DATAMAP::STATE:GENERATE',
'op:FILE:STRUCT',
//TODO: here use datamap doc rather than datamap struct, we have to change this
'op:UX:DATAMAP:DOC',
],
},
],
Expand All @@ -110,19 +103,19 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS', () => {
const context = new BuilderContext(sequence, 'test');

// Set input data for context
context.setData('projectName', 'spotify like music web');
context.setData('description', 'user can play music');
context.setData('platform', 'web');
context.setGlobalContext('projectName', 'spotify like music web');
context.setGlobalContext('description', 'user can play music');
context.setGlobalContext('platform', 'web');

try {
await BuildSequenceExecutor.executeSequence(sequence, context);

for (const step of sequence.steps) {
for (const node of step.nodes) {
const resultData = await context.getResult(node.id);
const resultData = await context.getNodeData(node.id);
console.log(resultData);
if (resultData) {
writeMarkdownToFile(node.name.replace(/ /g, '_'), resultData);
writeToFile(logFolderPath, node.id, resultData);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,54 +1,32 @@
/* eslint-disable no-console */
import { BuilderContext } from 'src/build-system/context';
import { BuildResult, BuildSequence } from '../types';
import { BuildSequence } from '../types';
import { BuildSequenceExecutor } from '../executor';
import * as fs from 'fs';
import * as path from 'path';
import { writeToFile } from './utils';

describe('Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGenerator', () => {
// Generate a unique folder with a timestamp
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const logFolderPath = `./logs/backend_code_generator-${timestamp}`;
fs.mkdirSync(logFolderPath, { recursive: true });

/**
* Utility function to extract content within <GENERATE> tags and write to .md files.
* @param handlerName - The name of the handler/node.
* @param data - The data returned by the handler/node.
*/
const writeMarkdownToFile = (handlerName: string, data: BuildResult) => {
try {
// Extract "data" field and ensure it's a string
const content: string = data?.data;
if (typeof content !== 'string') {
throw new Error(`Invalid data format for handler: ${handlerName}`);
}

const sanitizedHandlerName = handlerName.replace(/[^a-zA-Z0-9_-]/g, '_');
const filePath = path.join(logFolderPath, `${sanitizedHandlerName}.md`);
fs.writeFileSync(filePath, content, 'utf8');
console.log(`Logged ${handlerName} result data to ${filePath}`);
} catch (error) {
console.error(`Failed to write markdown for ${handlerName}:`, error);
throw error;
}
};

it('should execute the backend code generation sequence and log results to individual files', async () => {
// Define the build sequence up to Backend Code Generator
const sequence: BuildSequence = {
id: 'test-backend-sequence',
version: '1.0.0',
name: 'Test PRD to Backend Code Generation Sequence',
description:
'Testing sequence execution from PRD to Backend Code Generation',
name: 'Spotify-like Music Web',
description: 'Users can play music',
databaseType: 'SQLite',
steps: [
{
id: 'step-1',
name: 'Generate PRD',
nodes: [
{
id: 'op:PRD::STATE:GENERATE',
id: 'op:PRD',
name: 'PRD Generation Node',
type: 'ANALYSIS',
subType: 'PRD',
Expand All @@ -60,11 +38,11 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGener
name: 'Generate UX Sitemap Document',
nodes: [
{
id: 'op:UXSMD::STATE:GENERATE',
id: 'op:UX:SMD',
name: 'UX Sitemap Document Node',
type: 'UX',
subType: 'SITEMAP',
requires: ['op:PRD::STATE:GENERATE'],
requires: ['op:PRD'],
},
],
},
Expand All @@ -73,11 +51,11 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGener
name: 'Generate UX Data Map Document',
nodes: [
{
id: 'op:UX_DATAMAP::STATE:GENERATE',
id: 'op:UX:DATAMAP:DOC',
name: 'UX Data Map Document Node',
type: 'UX',
subType: 'DATAMAP',
requires: ['op:UXSMD::STATE:GENERATE'],
requires: ['op:UX:SMD'],
},
],
},
Expand All @@ -86,11 +64,11 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGener
name: 'Generate Database Requirements',
nodes: [
{
id: 'op:DATABASE_REQ::STATE:GENERATE',
id: 'op:DATABASE_REQ',
name: 'Database Requirements Node',
type: 'DATABASE',
subType: 'SCHEMAS',
requires: ['op:UX_DATAMAP::STATE:GENERATE'],
requires: ['op:UX:DATAMAP:DOC'],
},
],
},
Expand All @@ -103,7 +81,7 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGener
name: 'Database Schemas Node',
type: 'DATABASE',
subType: 'SCHEMAS',
requires: ['op:DATABASE_REQ::STATE:GENERATE'],
requires: ['op:DATABASE_REQ'],
},
],
},
Expand All @@ -112,13 +90,10 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGener
name: 'Generate Backend Code',
nodes: [
{
id: 'op:BACKEND_CODE::STATE:GENERATE',
id: 'op:BACKEND:CODE',
name: 'Backend Code Generator Node',
type: 'BACKEND',
requires: [
'op:DATABASE:SCHEMAS',
'op:UX_DATAMAP::STATE:GENERATE',
],
requires: ['op:DATABASE:SCHEMAS', 'op:UX:DATAMAP:DOC'],
},
],
},
Expand All @@ -128,30 +103,23 @@ describe('Sequence: PRD -> UXSD -> UXDD -> UXSS -> DBSchemas -> BackendCodeGener
// Initialize the BuilderContext with the defined sequence and environment
const context = new BuilderContext(sequence, 'test-env');

// Set input data for context
context.setData('projectName', 'Spotify-like Music Web');
context.setData('description', 'Users can play music');
context.setData('platform', 'web');
context.setData('databaseType', 'SQLite'); // Can be 'PostgreSQL', 'MongoDB', etc., based on your needs

try {
// Execute the build sequence
await BuildSequenceExecutor.executeSequence(sequence, context);

// Iterate through each step and node to retrieve and log results
for (const step of sequence.steps) {
for (const node of step.nodes) {
const resultData = await context.getResult(node.id);
const resultData = await context.getNodeData(node.id);
console.log(`Result for ${node.name}:`, resultData);

if (resultData && resultData.success) {
writeMarkdownToFile(node.name, resultData);
} else if (resultData && !resultData.success) {
if (resultData) {
writeToFile(logFolderPath, node.name, resultData);
} else {
console.error(
`Handler ${node.name} failed with error:`,
resultData.error,
);
// Optionally, you can log this to a separate file or handle it as needed
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions backend/src/build-system/__tests__/test.file-arch.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BuilderContext } from 'src/build-system/context';
import { FileArchGenerateHandler } from '../handlers/file-arch';
import markdownToTxt from 'markdown-to-txt';
import { readFileSync } from 'fs-extra';
import { FileArchGenerateHandler } from '../handlers/file-manager/file-arch';

describe('FileArchGenerateHandler', () => {
it('should generate file architecture document', async () => {
Expand All @@ -23,8 +23,9 @@ describe('FileArchGenerateHandler', () => {
const dataMapStruct = markdownToTxt(
readFileSync('./datamap-structure.md', 'utf-8'),
);

const result = await handler.run(context, fileStructure, dataMapStruct);
context.setNodeData('op:FILE:STRUCT', fileStructure);
context.setNodeData('op:UX:DATAMAP:DOC', dataMapStruct);
const result = await handler.run(context);
console.log(result);
}, 30000);
});
2 changes: 1 addition & 1 deletion backend/src/build-system/__tests__/testVirtualDir.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import { VirtualDirectory } from '../virtual-dir';
import * as normalizePath from 'normalize-path';
import normalizePath from 'normalize-path';

describe('VirtualDirectory', () => {
const structMdFilePath = normalizePath(
Expand Down
48 changes: 48 additions & 0 deletions backend/src/build-system/__tests__/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as fs from 'fs';
import * as path from 'path';
/**
* Utility function to write content to a file in a clean, formatted manner.
* @param handlerName - The name of the handler.
* @param data - The data to be written to the file.
*/
export const writeToFile = (
rootPath: string,
handlerName: string,
data: string | object,
): void => {
try {
// Sanitize handler name to prevent illegal file names
const sanitizedHandlerName = handlerName.replace(/[^a-zA-Z0-9_-]/g, '_');
const filePath = path.join(rootPath, `${sanitizedHandlerName}.md`);

// Generate clean and readable content
const formattedContent = formatContent(data);

// Write the formatted content to the file
fs.writeFileSync(filePath, formattedContent, 'utf8');
console.log(`Successfully wrote data for ${handlerName} to ${filePath}`);
} catch (error) {
console.error(`Failed to write data for ${handlerName}:`, error);
throw error;
}
};

/**
* Formats the content for writing to the file.
* @param data - The content to format (either a string or an object).
* @returns A formatted string.
*/
export const formatContent = (data: string | object): string => {
if (typeof data === 'string') {
// Remove unnecessary escape characters and normalize newlines
return data
.replace(/\\n/g, '\n') // Handle escaped newlines
.replace(/\\t/g, '\t'); // Handle escaped tabs
} else if (typeof data === 'object') {
// Pretty-print JSON objects with 2-space indentation
return JSON.stringify(data, null, 2);
} else {
// Convert other types to strings
return String(data);
}
};
Loading

0 comments on commit 3ec3ff9

Please sign in to comment.