Skip to content

Commit 1de015c

Browse files
authored
Merge pull request #111 from beNative/codex/add-ci-tab-for-workflow-template-editing
Add CI workflow explorer and validation
2 parents 8c886db + f52aaba commit 1de015c

8 files changed

Lines changed: 875 additions & 18 deletions

File tree

App.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ const App: React.FC = () => {
159159
cloneRepository,
160160
launchApplication,
161161
launchExecutable,
162+
validateWorkflow,
162163
logs,
163164
clearLogs,
164165
isProcessing,
@@ -1254,6 +1255,11 @@ const App: React.FC = () => {
12541255
});
12551256
}, [clearLogs]);
12561257

1258+
const handleValidateWorkflow = useCallback((repo: Repository, relativePath: string) => {
1259+
openLogPanelForRepo(repo.id, false);
1260+
return validateWorkflow(repo, relativePath);
1261+
}, [openLogPanelForRepo, validateWorkflow]);
1262+
12571263
const handleCancelTask = useCallback((repoId: string) => {
12581264
const repo = repositories.find(r => r.id === repoId);
12591265
if (!repo) {
@@ -1745,6 +1751,7 @@ const App: React.FC = () => {
17451751
defaultCategoryId={repoFormState.defaultCategoryId}
17461752
onOpenWeblink={handleOpenWeblink}
17471753
detectedExecutables={detectedExecutables}
1754+
onValidateWorkflow={handleValidateWorkflow}
17481755
/>;
17491756
case 'dashboard':
17501757
default:

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7-
- _No unreleased changes._
7+
- **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.
88

99
## [0.26.0]
1010

components/modals/RepoFormModal.tsx

Lines changed: 514 additions & 4 deletions
Large diffs are not rendered by default.

electron/electron.d.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { IpcRendererEvent } from 'electron';
2-
import type { Repository, Task, TaskStep, GlobalSettings, LogLevel, LocalPathState as AppLocalPathState, DetailedStatus, Commit, BranchInfo, DebugLogEntry, VcsType, ProjectInfo, UpdateStatusMessage, Category, AppDataContextState, ReleaseInfo, CommitDiffFile } from '../types';
2+
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';
33

44
export type LocalPathState = AppLocalPathState;
55

@@ -25,6 +25,7 @@ export interface IElectronAPI {
2525
getDoc: (docName: string) => Promise<string>;
2626
getProjectInfo: (repoPath: string) => Promise<ProjectInfo>;
2727
getProjectSuggestions: (args: { repoPath: string, repoName: string }) => Promise<ProjectSuggestion[]>;
28+
getWorkflowTemplates: (args: { repoPath: string; repoName: string }) => Promise<WorkflowTemplateSuggestion[]>;
2829
getDelphiVersions: () => Promise<{ name: string; version: string }[]>;
2930
checkVcsStatus: (repo: Repository) => Promise<{ isDirty: boolean; output: string; untrackedFiles: string[]; changedFiles: string[] }>;
3031
getDetailedVcsStatus: (repo: Repository) => Promise<DetailedStatus | null>;
@@ -60,6 +61,12 @@ export interface IElectronAPI {
6061
pathJoin: (...args: string[]) => Promise<string>;
6162
detectExecutables: (repoPath: string) => Promise<string[]>;
6263
launchExecutable: (args: { repoPath: string, executablePath: string }) => Promise<{ success: boolean; output: string }>;
64+
listWorkflowFiles: (repoPath: string) => Promise<WorkflowFileSummary[]>;
65+
readWorkflowFile: (args: { repoPath: string; relativePath: string }) => Promise<{ success: boolean; content?: string; error?: string }>;
66+
writeWorkflowFile: (args: { repoPath: string; relativePath: string; content: string }) => Promise<{ success: boolean; error?: string }>;
67+
createWorkflowFromTemplate: (args: { repoPath: string; relativePath: string; content: string; overwrite?: boolean }) => Promise<{ success: boolean; error?: string }>;
68+
commitWorkflowFiles: (args: { repo: Repository; filePaths: string[]; message: string }) => Promise<{ success: boolean; error?: string }>;
69+
validateWorkflow: (args: { repo: Repository; relativePath: string; executionId: string }) => void;
6370
openLocalPath: (path: string) => Promise<{ success: boolean; error?: string }>;
6471
openInstallationFolder: () => Promise<{ success: boolean; error?: string; path?: string }>;
6572
openWeblink: (url: string) => Promise<{ success: boolean; error?: string }>;

electron/main.ts

Lines changed: 289 additions & 11 deletions
Large diffs are not rendered by default.

electron/preload.ts

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

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

3637
// Version Control
@@ -69,6 +70,14 @@ contextBridge.exposeInMainWorld('electronAPI', {
6970
pathJoin: (...args: string[]): Promise<string> => ipcRenderer.invoke('path-join', ...args),
7071
detectExecutables: (repoPath: string): Promise<string[]> => ipcRenderer.invoke('detect-executables', repoPath),
7172
launchExecutable: (args: { repoPath: string, executablePath: string }): Promise<{ success: boolean; output: string }> => ipcRenderer.invoke('launch-executable', args),
73+
listWorkflowFiles: (repoPath: string): Promise<WorkflowFileSummary[]> => ipcRenderer.invoke('list-workflow-files', repoPath),
74+
readWorkflowFile: (args: { repoPath: string; relativePath: string }): Promise<{ success: boolean; content?: string; error?: string }> => ipcRenderer.invoke('read-workflow-file', args),
75+
writeWorkflowFile: (args: { repoPath: string; relativePath: string; content: string }): Promise<{ success: boolean; error?: string }> => ipcRenderer.invoke('write-workflow-file', args),
76+
createWorkflowFromTemplate: (args: { repoPath: string; relativePath: string; content: string; overwrite?: boolean }): Promise<{ success: boolean; error?: string }> => ipcRenderer.invoke('create-workflow-from-template', args),
77+
commitWorkflowFiles: (args: { repo: Repository; filePaths: string[]; message: string }): Promise<{ success: boolean; error?: string }> => ipcRenderer.invoke('commit-workflow-files', args),
78+
validateWorkflow: (args: { repo: Repository; relativePath: string; executionId: string }) => {
79+
ipcRenderer.send('validate-workflow', args);
80+
},
7281
openLocalPath: (path: string): Promise<{ success: boolean; error?: string }> => ipcRenderer.invoke('open-local-path', path),
7382
openInstallationFolder: (): Promise<{ success: boolean; error?: string; path?: string }> => ipcRenderer.invoke('open-installation-folder'),
7483
openWeblink: (url: string): Promise<{ success: boolean; error?: string }> => ipcRenderer.invoke('open-weblink', url),

hooks/useRepositoryManager.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,33 @@ export const useRepositoryManager = ({ repositories, updateRepository }: { repos
232232
addLogEntry(repoId, `Failed to launch executable: ${e.message}`, LogLevel.Error);
233233
}
234234
}, [addLogEntry]);
235+
236+
const validateWorkflow = useCallback((repo: Repository, relativePath: string) => {
237+
return new Promise<'success' | 'failed'>((resolve) => {
238+
if (!window.electronAPI?.validateWorkflow) {
239+
addLogEntry(repo.id, 'Workflow validation is not available in this environment.', LogLevel.Warn);
240+
resolve('failed');
241+
return;
242+
}
243+
const executionId = `workflow-validate-${repo.id}-${Date.now()}`;
244+
const handleLog = (_event: any, logData: { executionId: string; message: string; level: LogLevel }) => {
245+
if (logData.executionId === executionId) {
246+
addLogEntry(repo.id, logData.message, logData.level);
247+
}
248+
};
249+
const handleEnd = (_event: any, endData: { executionId: string; exitCode: number }) => {
250+
if (endData.executionId === executionId) {
251+
window.electronAPI.removeTaskLogListener(handleLog);
252+
window.electronAPI.removeTaskStepEndListener(handleEnd);
253+
resolve(endData.exitCode === 0 ? 'success' : 'failed');
254+
}
255+
};
256+
addLogEntry(repo.id, `Validating workflow ${relativePath}…`, LogLevel.Info);
257+
window.electronAPI.onTaskLog(handleLog);
258+
window.electronAPI.onTaskStepEnd(handleEnd);
259+
window.electronAPI.validateWorkflow({ repo, relativePath, executionId });
260+
});
261+
}, [addLogEntry]);
235262

236263
const clearLogs = (repoId: string) => {
237264
setLogs(prev => ({...prev, [repoId]: []}));
@@ -259,6 +286,7 @@ export const useRepositoryManager = ({ repositories, updateRepository }: { repos
259286
cloneRepository,
260287
launchApplication,
261288
launchExecutable,
289+
validateWorkflow,
262290
logs,
263291
clearLogs,
264292
isProcessing,

types.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ export interface NodejsCapabilities {
448448
linters: ('eslint' | 'prettier')[];
449449
bundlers: ('vite' | 'webpack' | 'rollup' | 'tsup' | 'swc')[];
450450
monorepo: { workspaces: boolean, turbo: boolean, nx: boolean, yarnBerryPnp: boolean };
451+
hasElectron: boolean;
451452
}
452453

453454
export interface GoModuleInfo {
@@ -528,3 +529,20 @@ export interface ProjectInfo {
528529
maven?: MavenCapabilities;
529530
dotnet?: DotnetCapabilities;
530531
}
532+
533+
export interface WorkflowFileSummary {
534+
name: string;
535+
relativePath: string;
536+
absolutePath: string;
537+
mtimeMs: number;
538+
}
539+
540+
export interface WorkflowTemplateSuggestion {
541+
id: string;
542+
label: string;
543+
description: string;
544+
filename: string;
545+
content: string;
546+
tags: string[];
547+
recommended: boolean;
548+
}

0 commit comments

Comments
 (0)