UNPKG

made-beta

Version:

It allows you to create tasks in your project manager (e.g., Github) automatically based on predefined processes. Additionally, it generates documentation based on the project.

352 lines (316 loc) 13.7 kB
import type { AstNode, LangiumCoreServices, LangiumDocument } from 'langium'; import chalk from 'chalk'; import * as path from 'node:path'; import * as fs from 'node:fs'; import { URI } from 'langium'; import type { AtomicUserStory, TaskBacklog, TeamMember, Epic, Model, Backlog, Team, Roadmap, Milestone, Release } from '../language/generated/ast.js'; import { type Issue, type Person } from 'made-lib-beta'; export async function extractDocument(fileName: string, services: LangiumCoreServices): Promise<LangiumDocument> { const extensions = services.LanguageMetaData.fileExtensions; if (!extensions.includes(path.extname(fileName))) { console.error(chalk.yellow(`Please choose a file with one of these extensions: ${extensions}.`)); process.exit(1); } if (!fs.existsSync(fileName)) { console.error(chalk.red(`File ${fileName} does not exist.`)); process.exit(1); } const document = await services.shared.workspace.LangiumDocuments.getOrCreateDocument(URI.file(path.resolve(fileName))); await services.shared.workspace.DocumentBuilder.build([document], { validation: true }); const validationErrors = (document.diagnostics ?? []).filter(e => e.severity === 1); if (validationErrors.length > 0) { console.error(chalk.red('There are validation errors:')); for (const validationError of validationErrors) { console.error(chalk.red( `line ${validationError.range.start.line + 1}: ${validationError.message} [${document.textDocument.getText(validationError.range)}]` )); } process.exit(1); } return document; } export async function extractAstNode<T extends AstNode>(fileName: string, services: LangiumCoreServices): Promise<T> { return (await extractDocument(fileName, services)).parseResult?.value as T; } interface FilePathData { destination: string, name: string } export function extractDestinationAndName(filePath: string, destination: string | undefined): FilePathData { filePath = path.basename(filePath, path.extname(filePath)).replace(/[.-]/g, ''); return { destination: destination ?? path.join(path.dirname(filePath), 'generated'), name: path.basename(filePath) }; } export function astEpicToIssue(epic: Epic, assigneeMap: Map<string, Person>, backlogName: string): Issue { return { id: epic.id, type: 'Epic', subtype: '', title: epic.name ?? epic.label ?? '', description: epic.description ?? '', labels: epic.labelx ?? [], assignee: assigneeMap.get(epic.id), backlog: backlogName, criterions: epic.criterions ?? [], observation: epic.observation ?? '' }; } export function astStoryToIssue(story: AtomicUserStory, assigneeMap: Map<string, Person>, backlogName: string, parentEpic?: Epic): Issue { return { id: story.id, type: 'Feature', subtype: '', title: story.name ?? story.label ?? '', description: story.description ?? '', labels: story.labelx ?? [], assignee: assigneeMap.get(story.id), depends: [ ...(parentEpic ? [{ id: parentEpic.id, type: 'Epic', subtype: '' }] : []) ], backlog: backlogName, criterions: story.criterions ?? [], requirements: story.requirements ?? [], observation: story.observation ?? '', }; } export function astTaskToIssue(task: TaskBacklog, parentStory: AtomicUserStory, assigneeMap: Map<string, Person>, backlogName: string): Issue { return { id: task.id, type: 'Task', subtype: '', title: task.name ?? task.label ?? '', description: task.description ?? '', labels: task.labelx ?? [], assignee: assigneeMap.get(task.id), depends: [ { id: parentStory.id, type: 'Feature', subtype: '' } ], backlog: backlogName, deliverables: task.deliverables ?? [], }; } export function astTeamMemberToPerson(member: TeamMember): Person { return { id: member.id ?? '', email: member.email ?? '', name: member.name ?? '', discord: '' // ajuste se houver campo no modelo }; } export function astTeamToTeam(team: TeamMember[]): Person[] { return team.map(member => ({ id: member.id ?? '', email: member.email ?? '', name: member.name ?? '', discord: '' // ajuste se houver campo no modelo })); } export function astTimeBoxToTimeBox(timebox: any): any { return { id: timebox.id, description: timebox.description ?? '', startDate: timebox.startDate ?? '', endDate: timebox.endDate ?? '', name: timebox.name ?? '', status: timebox.status, completeDate: timebox.completedDate, sprintItems: (timebox.sprintBacklog?.planningItems ?? []).map((item: any) => { // Monta o Issue correspondente ao backlogItem let issue: Issue | undefined = undefined; const ref = item.backlogItem?.ref; if (ref) { if (ref.$type === 'Epic') { issue = astEpicToIssue(ref, new Map(), ''); // ajuste o assigneeMap e backlogName se necessário } else if (ref.$type === 'AtomicUserStory') { issue = astStoryToIssue(ref, new Map(), '', undefined); } else if (ref.$type === 'TaskBacklog') { issue = astTaskToIssue(ref, ref.$container, new Map(), ''); } } return { id: item.backlogItem?.ref?.id ?? '', assignee: item.assignee?.ref ? { id: item.assignee.ref.id ?? '', name: item.assignee.ref.name ?? '', email: item.assignee.ref.email ?? '' } : undefined, issue, startDate: item.startDate, dueDate: item.dueDate, plannedStartDate: item.startDate, plannedDueDate: item.dueDate, status: item.status, }; }) }; } export function buildAssigneeMap(model: Model): Map<string, Person> { const assigneeMap = new Map<string, Person>(); for (const component of model.components) { if (component.$type === 'TimeBox' && component.sprintBacklog) { for (const planningItem of component.sprintBacklog.planningItems) { const backlogId = planningItem.backlogItem?.ref?.id; const assigneeRef = planningItem.assignee?.ref; if (backlogId && assigneeRef) { assigneeMap.set(backlogId, astTeamMemberToPerson(assigneeRef)); } } } } return assigneeMap; } export function processBacklogs(backlogs: Backlog[], assigneeMap: Map<string, Person>): { epics: Issue[], stories: Issue[], tasks: Issue[], backlogList: any[] } { const epics: Issue[] = []; const stories: Issue[] = []; const tasks: Issue[] = []; const backlogList: any[] = []; for (const backlog of backlogs) { const localEpics: Issue[] = []; const localStories: Issue[] = []; const localTasks: Issue[] = []; for (const item of backlog.items) { if (item.$type === 'Epic') { const epic = astEpicToIssue(item, assigneeMap, backlog.name ?? backlog.id); epics.push(epic); localEpics.push(epic); for (const story of item.userstories) { const storyIssue = astStoryToIssue(story, assigneeMap, backlog.name ?? backlog.id, item); stories.push(storyIssue); localStories.push(storyIssue); for (const task of story.tasks) { const taskIssue = astTaskToIssue(task, story, assigneeMap, backlog.name ?? backlog.id); tasks.push(taskIssue); localTasks.push(taskIssue); } } } else if (item.$type === 'AtomicUserStory') { const storyIssue = astStoryToIssue(item, assigneeMap, backlog.name ?? backlog.id); stories.push(storyIssue); localStories.push(storyIssue); for (const task of item.tasks) { const taskIssue = astTaskToIssue(task, item, assigneeMap, backlog.name ?? backlog.id); tasks.push(taskIssue); localTasks.push(taskIssue); } } } backlogList.push({ id: backlog.id, name: backlog.name ?? backlog.id, description: backlog.description ?? '', issues: [...localEpics, ...localStories, ...localTasks] }); } return { epics, stories, tasks, backlogList }; } export function processTeams(teamsRaw: Team[]): any[] { return teamsRaw.map(team => ({ id: team.id, name: team.name ?? '', description: team.description ?? '', teamMembers: astTeamToTeam(team.teammember) })); } export function processProject(astProject: any): any { return { id: astProject.id, name: astProject.name ?? '', description: astProject.description, startDate: astProject.startDate ?? '', dueDate: astProject.dueDate ?? '', completedDate: astProject.completedDate }; } export function processTimeBoxes(timeboxesRaw: any[]): any[] { return timeboxesRaw.map(astTimeBoxToTimeBox); } export function astReleaseToRelease(release: Release, assigneeMap: Map<string, Person>): any { const issues: Issue[] = []; // Adiciona o item principal como issue if (release.item?.ref) { const ref = release.item.ref; if (ref.$type === 'Epic') { issues.push(astEpicToIssue(ref, assigneeMap, '')); } else if (ref.$type === 'AtomicUserStory') { issues.push(astStoryToIssue(ref, assigneeMap, '', undefined)); } else if (ref.$type === 'TaskBacklog') { const dummyStory = { id: '', name: '', $type: 'AtomicUserStory' } as AtomicUserStory; issues.push(astTaskToIssue(ref, dummyStory, assigneeMap, '')); } } // Adiciona os itens adicionais como issues if (release.itens) { for (const item of release.itens) { if (item.ref) { const ref = item.ref; if (ref.$type === 'Epic') { issues.push(astEpicToIssue(ref, assigneeMap, '')); } else if (ref.$type === 'AtomicUserStory') { issues.push(astStoryToIssue(ref, assigneeMap, '', undefined)); } else if (ref.$type === 'TaskBacklog') { const dummyStory = { id: '', name: '', $type: 'AtomicUserStory' } as AtomicUserStory; issues.push(astTaskToIssue(ref, dummyStory, assigneeMap, '')); } } } } return { id: release.id, version: release.version ?? '', name: release.name ?? '', description: release.description ?? '', releasedDate: release.releasedDate, dueDate: release.dueDate ?? '', status: release.status, issues }; } export function astMilestoneToMilestone(milestone: Milestone, milestonesMap: Map<string, Milestone>, assigneeMap: Map<string, Person>): any { return { id: milestone.id, name: milestone.name ?? '', description: milestone.description ?? '', startDate: milestone.startDate ?? '', dueDate: milestone.dueDate ?? '', status: milestone.status, dependencies: [ ...(milestone.depend ? [astMilestoneToMilestone(milestone.depend.ref!, milestonesMap, assigneeMap)] : []), ...(milestone.depends?.map(dep => astMilestoneToMilestone(dep.ref!, milestonesMap, assigneeMap)) ?? []) ], releases: milestone.releases?.map(release => astReleaseToRelease(release, assigneeMap)) ?? [] }; } export function astRoadmapToRoadmap(roadmap: Roadmap, milestonesMap: Map<string, Milestone>, assigneeMap: Map<string, Person>): any { return { id: roadmap.id, name: roadmap.name ?? '', description: roadmap.description ?? '', milestones: roadmap.milestones?.map(milestone => astMilestoneToMilestone(milestone, milestonesMap, assigneeMap)) ?? [] }; } export function processRoadmaps(roadmaps: Roadmap[], assigneeMap: Map<string, Person>): any[] { const milestonesMap = new Map<string, Milestone>(); // Primeiro, coleta todos os milestones para resolver dependências roadmaps.forEach(roadmap => { roadmap.milestones?.forEach(milestone => { milestonesMap.set(`${roadmap.id}.${milestone.id}`, milestone); }); }); // Depois, mapeia os roadmaps completos return roadmaps.map(roadmap => astRoadmapToRoadmap(roadmap, milestonesMap, assigneeMap)); }