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
7 changes: 7 additions & 0 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ const App: React.FC = () => {
cloneRepository,
launchApplication,
launchExecutable,
validateWorkflow,
logs,
clearLogs,
isProcessing,
Expand Down Expand Up @@ -1254,6 +1255,11 @@ const App: React.FC = () => {
});
}, [clearLogs]);

const handleValidateWorkflow = useCallback((repo: Repository, relativePath: string) => {
openLogPanelForRepo(repo.id, false);
return validateWorkflow(repo, relativePath);
}, [openLogPanelForRepo, validateWorkflow]);

const handleCancelTask = useCallback((repoId: string) => {
const repo = repositories.find(r => r.id === repoId);
if (!repo) {
Expand Down Expand Up @@ -1745,6 +1751,7 @@ const App: React.FC = () => {
defaultCategoryId={repoFormState.defaultCategoryId}
onOpenWeblink={handleOpenWeblink}
detectedExecutables={detectedExecutables}
onValidateWorkflow={handleValidateWorkflow}
/>;
case 'dashboard':
default:
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

- _No unreleased changes._
- **Workflow Template Explorer:** Added a CI tab to the repository form so you can browse `.github/workflows` files, fork recommended templates, edit YAML in place, run validation via `yamllint`/`act`, and push commits without leaving the modal.

## [0.26.0]

Expand Down
518 changes: 514 additions & 4 deletions components/modals/RepoFormModal.tsx

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion electron/electron.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IpcRendererEvent } from 'electron';
import type { Repository, Task, TaskStep, GlobalSettings, LogLevel, LocalPathState as AppLocalPathState, DetailedStatus, Commit, BranchInfo, DebugLogEntry, VcsType, ProjectInfo, UpdateStatusMessage, Category, AppDataContextState, ReleaseInfo, CommitDiffFile } from '../types';
import type { Repository, Task, TaskStep, GlobalSettings, LogLevel, LocalPathState as AppLocalPathState, DetailedStatus, Commit, BranchInfo, DebugLogEntry, VcsType, ProjectInfo, UpdateStatusMessage, Category, AppDataContextState, ReleaseInfo, CommitDiffFile, WorkflowFileSummary, WorkflowTemplateSuggestion } from '../types';

export type LocalPathState = AppLocalPathState;

Expand All @@ -25,6 +25,7 @@ export interface IElectronAPI {
getDoc: (docName: string) => Promise<string>;
getProjectInfo: (repoPath: string) => Promise<ProjectInfo>;
getProjectSuggestions: (args: { repoPath: string, repoName: string }) => Promise<ProjectSuggestion[]>;
getWorkflowTemplates: (args: { repoPath: string; repoName: string }) => Promise<WorkflowTemplateSuggestion[]>;
getDelphiVersions: () => Promise<{ name: string; version: string }[]>;
checkVcsStatus: (repo: Repository) => Promise<{ isDirty: boolean; output: string; untrackedFiles: string[]; changedFiles: string[] }>;
getDetailedVcsStatus: (repo: Repository) => Promise<DetailedStatus | null>;
Expand Down Expand Up @@ -60,6 +61,12 @@ export interface IElectronAPI {
pathJoin: (...args: string[]) => Promise<string>;
detectExecutables: (repoPath: string) => Promise<string[]>;
launchExecutable: (args: { repoPath: string, executablePath: string }) => Promise<{ success: boolean; output: string }>;
listWorkflowFiles: (repoPath: string) => Promise<WorkflowFileSummary[]>;
readWorkflowFile: (args: { repoPath: string; relativePath: string }) => Promise<{ success: boolean; content?: string; error?: string }>;
writeWorkflowFile: (args: { repoPath: string; relativePath: string; content: string }) => Promise<{ success: boolean; error?: string }>;
createWorkflowFromTemplate: (args: { repoPath: string; relativePath: string; content: string; overwrite?: boolean }) => Promise<{ success: boolean; error?: string }>;
commitWorkflowFiles: (args: { repo: Repository; filePaths: string[]; message: string }) => Promise<{ success: boolean; error?: string }>;
validateWorkflow: (args: { repo: Repository; relativePath: string; executionId: string }) => void;
openLocalPath: (path: string) => Promise<{ success: boolean; error?: string }>;
openInstallationFolder: () => Promise<{ success: boolean; error?: string; path?: string }>;
openWeblink: (url: string) => Promise<{ success: boolean; error?: string }>;
Expand Down
300 changes: 289 additions & 11 deletions electron/main.ts

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion electron/preload.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
import type { Repository, Task, TaskStep, GlobalSettings, LogLevel, ProjectSuggestion, LocalPathState, DetailedStatus, Commit, BranchInfo, DebugLogEntry, VcsType, ProjectInfo, Category, AppDataContextState, ReleaseInfo, CommitDiffFile } from '../types';
import type { Repository, Task, TaskStep, GlobalSettings, LogLevel, ProjectSuggestion, LocalPathState, DetailedStatus, Commit, BranchInfo, DebugLogEntry, VcsType, ProjectInfo, Category, AppDataContextState, ReleaseInfo, CommitDiffFile, WorkflowFileSummary, WorkflowTemplateSuggestion } from '../types';

const taskLogChannel = 'task-log';
const taskStepEndChannel = 'task-step-end';
Expand Down Expand Up @@ -31,6 +31,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
// Smart Scripts
getProjectInfo: (repoPath: string): Promise<ProjectInfo> => ipcRenderer.invoke('get-project-info', repoPath),
getProjectSuggestions: (args: { repoPath: string, repoName: string }): Promise<ProjectSuggestion[]> => ipcRenderer.invoke('get-project-suggestions', args),
getWorkflowTemplates: (args: { repoPath: string; repoName: string }): Promise<WorkflowTemplateSuggestion[]> => ipcRenderer.invoke('get-workflow-templates', args),
getDelphiVersions: (): Promise<{ name: string; version: string }[]> => ipcRenderer.invoke('get-delphi-versions'),

// Version Control
Expand Down Expand Up @@ -69,6 +70,14 @@ contextBridge.exposeInMainWorld('electronAPI', {
pathJoin: (...args: string[]): Promise<string> => ipcRenderer.invoke('path-join', ...args),
detectExecutables: (repoPath: string): Promise<string[]> => ipcRenderer.invoke('detect-executables', repoPath),
launchExecutable: (args: { repoPath: string, executablePath: string }): Promise<{ success: boolean; output: string }> => ipcRenderer.invoke('launch-executable', args),
listWorkflowFiles: (repoPath: string): Promise<WorkflowFileSummary[]> => ipcRenderer.invoke('list-workflow-files', repoPath),
readWorkflowFile: (args: { repoPath: string; relativePath: string }): Promise<{ success: boolean; content?: string; error?: string }> => ipcRenderer.invoke('read-workflow-file', args),
writeWorkflowFile: (args: { repoPath: string; relativePath: string; content: string }): Promise<{ success: boolean; error?: string }> => ipcRenderer.invoke('write-workflow-file', args),
createWorkflowFromTemplate: (args: { repoPath: string; relativePath: string; content: string; overwrite?: boolean }): Promise<{ success: boolean; error?: string }> => ipcRenderer.invoke('create-workflow-from-template', args),
commitWorkflowFiles: (args: { repo: Repository; filePaths: string[]; message: string }): Promise<{ success: boolean; error?: string }> => ipcRenderer.invoke('commit-workflow-files', args),
validateWorkflow: (args: { repo: Repository; relativePath: string; executionId: string }) => {
ipcRenderer.send('validate-workflow', args);
},
openLocalPath: (path: string): Promise<{ success: boolean; error?: string }> => ipcRenderer.invoke('open-local-path', path),
openInstallationFolder: (): Promise<{ success: boolean; error?: string; path?: string }> => ipcRenderer.invoke('open-installation-folder'),
openWeblink: (url: string): Promise<{ success: boolean; error?: string }> => ipcRenderer.invoke('open-weblink', url),
Expand Down
28 changes: 28 additions & 0 deletions hooks/useRepositoryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,33 @@ export const useRepositoryManager = ({ repositories, updateRepository }: { repos
addLogEntry(repoId, `Failed to launch executable: ${e.message}`, LogLevel.Error);
}
}, [addLogEntry]);

const validateWorkflow = useCallback((repo: Repository, relativePath: string) => {
return new Promise<'success' | 'failed'>((resolve) => {
if (!window.electronAPI?.validateWorkflow) {
addLogEntry(repo.id, 'Workflow validation is not available in this environment.', LogLevel.Warn);
resolve('failed');
return;
}
const executionId = `workflow-validate-${repo.id}-${Date.now()}`;
const handleLog = (_event: any, logData: { executionId: string; message: string; level: LogLevel }) => {
if (logData.executionId === executionId) {
addLogEntry(repo.id, logData.message, logData.level);
}
};
const handleEnd = (_event: any, endData: { executionId: string; exitCode: number }) => {
if (endData.executionId === executionId) {
window.electronAPI.removeTaskLogListener(handleLog);
window.electronAPI.removeTaskStepEndListener(handleEnd);
resolve(endData.exitCode === 0 ? 'success' : 'failed');
}
};
addLogEntry(repo.id, `Validating workflow ${relativePath}…`, LogLevel.Info);
window.electronAPI.onTaskLog(handleLog);
window.electronAPI.onTaskStepEnd(handleEnd);
window.electronAPI.validateWorkflow({ repo, relativePath, executionId });
});
}, [addLogEntry]);

const clearLogs = (repoId: string) => {
setLogs(prev => ({...prev, [repoId]: []}));
Expand Down Expand Up @@ -259,6 +286,7 @@ export const useRepositoryManager = ({ repositories, updateRepository }: { repos
cloneRepository,
launchApplication,
launchExecutable,
validateWorkflow,
logs,
clearLogs,
isProcessing,
Expand Down
18 changes: 18 additions & 0 deletions types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ export interface NodejsCapabilities {
linters: ('eslint' | 'prettier')[];
bundlers: ('vite' | 'webpack' | 'rollup' | 'tsup' | 'swc')[];
monorepo: { workspaces: boolean, turbo: boolean, nx: boolean, yarnBerryPnp: boolean };
hasElectron: boolean;
}

export interface GoModuleInfo {
Expand Down Expand Up @@ -528,3 +529,20 @@ export interface ProjectInfo {
maven?: MavenCapabilities;
dotnet?: DotnetCapabilities;
}

export interface WorkflowFileSummary {
name: string;
relativePath: string;
absolutePath: string;
mtimeMs: number;
}

export interface WorkflowTemplateSuggestion {
id: string;
label: string;
description: string;
filename: string;
content: string;
tags: string[];
recommended: boolean;
}