Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(backend): refactor context part #72

Merged
merged 14 commits into from
Dec 16, 2024
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
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
Loading