feat: Add Gitea workflow support (.gitea/workflows) - Support for both .github/workflows and .gitea/workflows directories - Fixed workflow execution path resolution - Cleaned up debugging code - Updated to version 1.2.5
Some checks failed
Test Gitea Workflow / test (push) Has been cancelled

This commit is contained in:
2025-08-03 23:00:22 +07:00
parent bc25f97d70
commit adf88e431c
8 changed files with 4699 additions and 1318 deletions

View File

@@ -0,0 +1,21 @@
name: Test Gitea Workflow
run-name: Test Gitea Workflow
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run tests
run: echo "Testing Gitea workflow"
- name: Build
run: echo "Building project"

View File

@@ -917,4 +917,4 @@
"webpack": "^5.96.1", "webpack": "^5.96.1",
"webpack-cli": "^5.1.4" "webpack-cli": "^5.1.4"
} }
} }

2935
src/Extension Host.log Normal file

File diff suppressed because it is too large Load Diff

2228
src/act.ts

File diff suppressed because it is too large Load Diff

View File

@@ -1,60 +1,70 @@
import { ConfigurationTarget, workspace } from 'vscode'; import { ConfigurationTarget, workspace } from "vscode";
import { Act } from './act'; import { Act } from "./act";
import { WorkflowsManager } from './workflowsManager';
export enum Platform { export enum Platform {
windows = 'win32', windows = "win32",
mac = 'darwin', mac = "darwin",
linux = 'linux' linux = "linux",
} }
export enum Section { export enum Section {
actCommand = 'actCommand', actCommand = "actCommand",
workflowsDirectory = 'workflowsDirectory', dockerDesktopPath = "dockerDesktopPath",
dockerDesktopPath = 'dockerDesktopPath'
} }
export namespace ConfigurationManager { export namespace ConfigurationManager {
export const group: string = 'githubLocalActions'; export const group: string = "githubLocalActions";
export const searchPrefix: string = '@ext:sanjulaganepola.github-local-actions'; export const searchPrefix: string =
"@ext:sanjulaganepola.github-local-actions";
export async function initialize(): Promise<void> { export async function initialize(): Promise<void> {
let actCommand = ConfigurationManager.get<string>(Section.actCommand); let actCommand = ConfigurationManager.get<string>(Section.actCommand);
if (!actCommand) { if (!actCommand) {
await ConfigurationManager.set(Section.actCommand, Act.defaultActCommand); await ConfigurationManager.set(Section.actCommand, Act.defaultActCommand);
}
let workflowsDirectory = ConfigurationManager.get<string>(Section.workflowsDirectory);
if (!workflowsDirectory) {
await ConfigurationManager.set(Section.workflowsDirectory, WorkflowsManager.defaultWorkflowsDirectory);
}
let dockerDesktopPath = ConfigurationManager.get<string>(Section.dockerDesktopPath);
if (!dockerDesktopPath) {
switch (process.platform) {
case Platform.windows:
dockerDesktopPath = 'C:/Program Files/Docker/Docker/Docker Desktop.exe';
break;
case Platform.mac:
dockerDesktopPath = '/Applications/Docker.app';
break;
default:
return;
}
await ConfigurationManager.set(Section.dockerDesktopPath, dockerDesktopPath);
}
} }
export function getSearchTerm(section: Section): string { // Don't set a default workflows directory to allow multi-directory support
return `${ConfigurationManager.searchPrefix} ${ConfigurationManager.group}.${section}`; // let workflowsDirectory = ConfigurationManager.get<string>(Section.workflowsDirectory);
} // if (!workflowsDirectory) {
// await ConfigurationManager.set(Section.workflowsDirectory, WorkflowsManager.defaultWorkflowsDirectory);
// }
export function get<T>(section: Section): T | undefined { let dockerDesktopPath = ConfigurationManager.get<string>(
return workspace.getConfiguration(ConfigurationManager.group).get(section) as T; Section.dockerDesktopPath
} );
if (!dockerDesktopPath) {
switch (process.platform) {
case Platform.windows:
dockerDesktopPath =
"C:/Program Files/Docker/Docker/Docker Desktop.exe";
break;
case Platform.mac:
dockerDesktopPath = "/Applications/Docker.app";
break;
default:
return;
}
export async function set(section: Section, value: any): Promise<void> { await ConfigurationManager.set(
return await workspace.getConfiguration(ConfigurationManager.group).update(section, value, ConfigurationTarget.Global); Section.dockerDesktopPath,
dockerDesktopPath
);
} }
} }
export function getSearchTerm(section: Section): string {
return `${ConfigurationManager.searchPrefix} ${ConfigurationManager.group}.${section}`;
}
export function get<T>(section: Section): T | undefined {
return workspace
.getConfiguration(ConfigurationManager.group)
.get(section) as T;
}
export async function set(section: Section, value: any): Promise<void> {
return await workspace
.getConfiguration(ConfigurationManager.group)
.update(section, value, ConfigurationTarget.Global);
}
}

View File

@@ -1,15 +1,23 @@
import { commands, env, ExtensionContext, TreeCheckboxChangeEvent, Uri, window, workspace } from 'vscode'; import {
import { Act } from './act'; commands,
import { ConfigurationManager, Section } from './configurationManager'; env,
import { IssueHandler } from './issueHandler'; ExtensionContext,
import ComponentsTreeDataProvider from './views/components/componentsTreeDataProvider'; TreeCheckboxChangeEvent,
import { DecorationProvider } from './views/decorationProvider'; Uri,
import { GithubLocalActionsTreeItem } from './views/githubLocalActionsTreeItem'; window,
import HistoryTreeDataProvider from './views/history/historyTreeDataProvider'; workspace,
import SettingTreeItem from './views/settings/setting'; } from "vscode";
import SettingsTreeDataProvider from './views/settings/settingsTreeDataProvider'; import { Act } from "./act";
import WorkflowsTreeDataProvider from './views/workflows/workflowsTreeDataProvider'; import { ConfigurationManager, Section } from "./configurationManager";
import { WorkflowsManager } from './workflowsManager'; import { IssueHandler } from "./issueHandler";
import ComponentsTreeDataProvider from "./views/components/componentsTreeDataProvider";
import { DecorationProvider } from "./views/decorationProvider";
import { GithubLocalActionsTreeItem } from "./views/githubLocalActionsTreeItem";
import HistoryTreeDataProvider from "./views/history/historyTreeDataProvider";
import SettingTreeItem from "./views/settings/setting";
import SettingsTreeDataProvider from "./views/settings/settingsTreeDataProvider";
import WorkflowsTreeDataProvider from "./views/workflows/workflowsTreeDataProvider";
import { WorkflowsManager } from "./workflowsManager";
export let act: Act; export let act: Act;
export let componentsTreeDataProvider: ComponentsTreeDataProvider; export let componentsTreeDataProvider: ComponentsTreeDataProvider;
@@ -18,83 +26,120 @@ export let historyTreeDataProvider: HistoryTreeDataProvider;
export let settingsTreeDataProvider: SettingsTreeDataProvider; export let settingsTreeDataProvider: SettingsTreeDataProvider;
export function activate(context: ExtensionContext) { export function activate(context: ExtensionContext) {
console.log('Congratulations, your extension "github-local-actions" is now active!'); console.log(
'Congratulations, your extension "github-local-actions" is now active!'
);
act = new Act(context); act = new Act(context);
// Create tree views // Create tree views
const decorationProvider = new DecorationProvider(); const decorationProvider = new DecorationProvider();
componentsTreeDataProvider = new ComponentsTreeDataProvider(context); componentsTreeDataProvider = new ComponentsTreeDataProvider(context);
const componentsTreeView = window.createTreeView(ComponentsTreeDataProvider.VIEW_ID, { treeDataProvider: componentsTreeDataProvider, showCollapseAll: true }); const componentsTreeView = window.createTreeView(
workflowsTreeDataProvider = new WorkflowsTreeDataProvider(context); ComponentsTreeDataProvider.VIEW_ID,
const workflowsTreeView = window.createTreeView(WorkflowsTreeDataProvider.VIEW_ID, { treeDataProvider: workflowsTreeDataProvider, showCollapseAll: true }); { treeDataProvider: componentsTreeDataProvider, showCollapseAll: true }
historyTreeDataProvider = new HistoryTreeDataProvider(context); );
const historyTreeView = window.createTreeView(HistoryTreeDataProvider.VIEW_ID, { treeDataProvider: historyTreeDataProvider, showCollapseAll: true });
settingsTreeDataProvider = new SettingsTreeDataProvider(context);
const settingsTreeView = window.createTreeView(SettingsTreeDataProvider.VIEW_ID, { treeDataProvider: settingsTreeDataProvider, showCollapseAll: true });
settingsTreeView.onDidChangeCheckboxState(async (event: TreeCheckboxChangeEvent<GithubLocalActionsTreeItem>) => {
await settingsTreeDataProvider.onDidChangeCheckboxState(event as TreeCheckboxChangeEvent<SettingTreeItem>);
});
// Create file watcher workflowsTreeDataProvider = new WorkflowsTreeDataProvider(context);
let workflowsFileWatcher = setupFileWatcher(context); const workflowsTreeView = window.createTreeView(
WorkflowsTreeDataProvider.VIEW_ID,
{ treeDataProvider: workflowsTreeDataProvider, showCollapseAll: true }
);
// Initialize configurations historyTreeDataProvider = new HistoryTreeDataProvider(context);
ConfigurationManager.initialize(); const historyTreeView = window.createTreeView(
workspace.onDidChangeConfiguration(async event => { HistoryTreeDataProvider.VIEW_ID,
if (event.affectsConfiguration(ConfigurationManager.group)) { { treeDataProvider: historyTreeDataProvider, showCollapseAll: true }
await ConfigurationManager.initialize(); );
settingsTreeDataProvider = new SettingsTreeDataProvider(context);
const settingsTreeView = window.createTreeView(
SettingsTreeDataProvider.VIEW_ID,
{ treeDataProvider: settingsTreeDataProvider, showCollapseAll: true }
);
settingsTreeView.onDidChangeCheckboxState(
async (event: TreeCheckboxChangeEvent<GithubLocalActionsTreeItem>) => {
await settingsTreeDataProvider.onDidChangeCheckboxState(
event as TreeCheckboxChangeEvent<SettingTreeItem>
);
}
);
if (event.affectsConfiguration(`${ConfigurationManager.group}.${Section.actCommand}`) || // Create file watcher
event.affectsConfiguration(`${ConfigurationManager.group}.${Section.dockerDesktopPath}`)) { let workflowsFileWatcher = setupFileWatcher(context);
componentsTreeDataProvider.refresh();
}
if (event.affectsConfiguration(`${ConfigurationManager.group}.${Section.workflowsDirectory}`)) { // Initialize configurations
workflowsTreeDataProvider.refresh(); ConfigurationManager.initialize();
settingsTreeDataProvider.refresh(); workspace.onDidChangeConfiguration(async (event) => {
if (event.affectsConfiguration(ConfigurationManager.group)) {
await ConfigurationManager.initialize();
if (workflowsFileWatcher) { if (
workflowsFileWatcher.dispose(); event.affectsConfiguration(
workflowsFileWatcher = setupFileWatcher(context); `${ConfigurationManager.group}.${Section.actCommand}`
} ) ||
} event.affectsConfiguration(
} `${ConfigurationManager.group}.${Section.dockerDesktopPath}`
}); )
) {
componentsTreeDataProvider.refresh();
}
}
});
context.subscriptions.push( context.subscriptions.push(
componentsTreeView, componentsTreeView,
workflowsTreeView, workflowsTreeView,
historyTreeView, historyTreeView,
settingsTreeView, settingsTreeView,
window.registerFileDecorationProvider(decorationProvider), window.registerFileDecorationProvider(decorationProvider),
workflowsFileWatcher, workflowsFileWatcher,
commands.registerCommand('githubLocalActions.viewDocumentation', async () => { commands.registerCommand(
await env.openExternal(Uri.parse('https://sanjulaganepola.github.io/github-local-actions-docs')); "githubLocalActions.viewDocumentation",
}), async () => {
commands.registerCommand('githubLocalActions.reportAnIssue', async () => { await env.openExternal(
await IssueHandler.openBugReport(context); Uri.parse(
}), "https://sanjulaganepola.github.io/github-local-actions-docs"
); )
);
}
),
commands.registerCommand("githubLocalActions.reportAnIssue", async () => {
await IssueHandler.openBugReport(context);
})
);
} }
function setupFileWatcher(context: ExtensionContext) { function setupFileWatcher(context: ExtensionContext) {
const workflowsDirectory = WorkflowsManager.getWorkflowsDirectory(); const workflowsDirectories = WorkflowsManager.getWorkflowsDirectories();
const workflowsFileWatcher = workspace.createFileSystemWatcher(`**/${workflowsDirectory}/*.{${WorkflowsManager.ymlExtension},${WorkflowsManager.yamlExtension}}`); const fileWatchers: any[] = [];
workflowsFileWatcher.onDidCreate(() => {
workflowsTreeDataProvider.refresh();
settingsTreeDataProvider.refresh();
});
workflowsFileWatcher.onDidChange(() => {
workflowsTreeDataProvider.refresh();
settingsTreeDataProvider.refresh();
});
workflowsFileWatcher.onDidDelete(() => {
workflowsTreeDataProvider.refresh();
settingsTreeDataProvider.refresh();
});
return workflowsFileWatcher; for (const workflowsDirectory of workflowsDirectories) {
const workflowsFileWatcher = workspace.createFileSystemWatcher(
`**/${workflowsDirectory}/*.{${WorkflowsManager.ymlExtension},${WorkflowsManager.yamlExtension}}`
);
workflowsFileWatcher.onDidCreate(() => {
workflowsTreeDataProvider.refresh();
settingsTreeDataProvider.refresh();
});
workflowsFileWatcher.onDidChange(() => {
workflowsTreeDataProvider.refresh();
settingsTreeDataProvider.refresh();
});
workflowsFileWatcher.onDidDelete(() => {
workflowsTreeDataProvider.refresh();
settingsTreeDataProvider.refresh();
});
fileWatchers.push(workflowsFileWatcher);
}
// Return a disposable that disposes all watchers
return {
dispose: () => {
fileWatchers.forEach((watcher) => watcher.dispose());
},
};
} }
export function deactivate() { } export function deactivate() {}

View File

@@ -1,5 +1,14 @@
import * as path from "path"; import * as path from "path";
import { CancellationToken, commands, EventEmitter, ExtensionContext, TreeDataProvider, TreeItem, window, workspace } from "vscode"; import {
CancellationToken,
commands,
EventEmitter,
ExtensionContext,
TreeDataProvider,
TreeItem,
window,
workspace,
} from "vscode";
import { Event } from "../../act"; import { Event } from "../../act";
import { act } from "../../extension"; import { act } from "../../extension";
import { Utils } from "../../utils"; import { Utils } from "../../utils";
@@ -9,176 +18,276 @@ import JobTreeItem from "./job";
import WorkflowTreeItem from "./workflow"; import WorkflowTreeItem from "./workflow";
import WorkspaceFolderWorkflowsTreeItem from "./workspaceFolderWorkflows"; import WorkspaceFolderWorkflowsTreeItem from "./workspaceFolderWorkflows";
export default class WorkflowsTreeDataProvider implements TreeDataProvider<GithubLocalActionsTreeItem> { export default class WorkflowsTreeDataProvider
private _onDidChangeTreeData = new EventEmitter<GithubLocalActionsTreeItem | undefined | null | void>(); implements TreeDataProvider<GithubLocalActionsTreeItem>
readonly onDidChangeTreeData = this._onDidChangeTreeData.event; {
static VIEW_ID = 'workflows'; private _onDidChangeTreeData = new EventEmitter<
GithubLocalActionsTreeItem | undefined | null | void
>();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
static VIEW_ID = "workflows";
constructor(context: ExtensionContext) { constructor(context: ExtensionContext) {
context.subscriptions.push( context.subscriptions.push(
commands.registerCommand('githubLocalActions.runAllWorkflows', async (workspaceFolderWorkflowsTreeItem?: WorkspaceFolderWorkflowsTreeItem) => { commands.registerCommand(
const workspaceFolder = await Utils.getWorkspaceFolder(workspaceFolderWorkflowsTreeItem?.workspaceFolder); "githubLocalActions.runAllWorkflows",
if (workspaceFolder) { async (
await act.runAllWorkflows(workspaceFolder); workspaceFolderWorkflowsTreeItem?: WorkspaceFolderWorkflowsTreeItem
} ) => {
}), const workspaceFolder = await Utils.getWorkspaceFolder(
commands.registerCommand('githubLocalActions.runEvent', async (workspaceFolderWorkflowsTreeItem?: WorkspaceFolderWorkflowsTreeItem) => { workspaceFolderWorkflowsTreeItem?.workspaceFolder
const workspaceFolder = await Utils.getWorkspaceFolder(workspaceFolderWorkflowsTreeItem?.workspaceFolder); );
if (workspaceFolder) { if (workspaceFolder) {
const event = await window.showQuickPick(Object.values(Event), { await act.runAllWorkflows(workspaceFolder);
title: 'Select the event to run', }
placeHolder: 'Event'
});
if (event) {
await act.runEvent(workspaceFolder, event as Event);
}
}
}),
commands.registerCommand('githubLocalActions.refreshWorkflows', async () => {
this.refresh();
}),
commands.registerCommand('githubLocalActions.openWorkflow', async (workflowTreeItem: WorkflowTreeItem) => {
try {
const document = await workspace.openTextDocument(workflowTreeItem.workflow.uri);
await window.showTextDocument(document);
} catch (error: any) {
try {
await workspace.fs.stat(workflowTreeItem.workflow.uri);
window.showErrorMessage(`Failed to open workflow. Error: ${error}`);
} catch (error: any) {
window.showErrorMessage(`Workflow ${path.parse(workflowTreeItem.workflow.uri.fsPath).base} not found.`);
}
}
}),
commands.registerCommand('githubLocalActions.runWorkflow', async (workflowTreeItem: WorkflowTreeItem) => {
if (workflowTreeItem) {
await act.runWorkflow(workflowTreeItem.workspaceFolder, workflowTreeItem.workflow);
} else {
let errorMessage: string | undefined;
const activeTextEditor = window.activeTextEditor;
if (activeTextEditor) {
const uri = activeTextEditor.document.uri;
const fileName = path.parse(uri.fsPath).base;
const workflowsDirectory = WorkflowsManager.getWorkflowsDirectory();
if (uri.path.match(`.*/${workflowsDirectory}/.*\\.(${WorkflowsManager.yamlExtension}|${WorkflowsManager.ymlExtension})`)) {
const workspaceFolder = workspace.getWorkspaceFolder(uri);
if (workspaceFolder) {
const workflows = await act.workflowsManager.getWorkflows(workspaceFolder);
const workflow = workflows.find(workflow => workflow.uri.fsPath === uri.fsPath);
if (workflow) {
await act.runWorkflow(workspaceFolder, workflow);
} else {
errorMessage = `Workflow not found in workflow directory (${workflowsDirectory}).`;
}
} else {
errorMessage = `${fileName} must be opened in a workspace folder to be executed locally.`;
}
} else {
errorMessage = `${fileName} is not a workflow that can be executed locally.`;
}
} else {
errorMessage = 'No workflow opened to execute locally.';
}
if (errorMessage) {
window.showErrorMessage(errorMessage, 'View Workflows').then(async value => {
if (value === 'View Workflows') {
await commands.executeCommand('workflows.focus');
}
});
}
}
}),
commands.registerCommand('githubLocalActions.runJob', async (jobTreeItem: JobTreeItem) => {
await act.runJob(jobTreeItem.workspaceFolder, jobTreeItem.workflow, jobTreeItem.job);
}),
commands.registerCommand('githubLocalActions.runWorkflowEvent', async (workflowTreeItem: WorkflowTreeItem) => {
// Filter to only events that are registered on the workflow
const registeredEventsOnWorkflow = Object.keys(workflowTreeItem.workflow.yaml.on);
if (registeredEventsOnWorkflow.length === 0) {
window.showErrorMessage(`No events registered on the workflow (${workflowTreeItem.workflow.name}). Add an event to the \`on\` section of the workflow to trigger it.`);
return;
}
const event = await window.showQuickPick(registeredEventsOnWorkflow, {
title: 'Select the event to run',
placeHolder: 'Event',
});
if (event) {
await act.runEvent(workflowTreeItem.workspaceFolder, event as Event, { workflow: workflowTreeItem.workflow });
}
}),
commands.registerCommand('githubLocalActions.runJobEvent', async (jobTreeItem: JobTreeItem) => {
// Filter to only events that are registered on the job's parent workflow
const registeredEventsOnJobParentWorkflow = Object.keys(jobTreeItem.workflow.yaml.on);
if (registeredEventsOnJobParentWorkflow.length === 0) {
window.showErrorMessage(`No events registered on the workflow (${jobTreeItem.workflow.name}). Add an event to the \`on\` section of the workflow to trigger it.`);
return;
}
const event = await window.showQuickPick(registeredEventsOnJobParentWorkflow, {
title: 'Select the event to run',
placeHolder: 'Event'
});
if (event) {
await act.runEvent(jobTreeItem.workspaceFolder, event as Event, { workflow: jobTreeItem.workflow, job: jobTreeItem.job });
}
})
);
}
refresh(element?: GithubLocalActionsTreeItem) {
this._onDidChangeTreeData.fire(element);
}
getTreeItem(element: GithubLocalActionsTreeItem): GithubLocalActionsTreeItem | Thenable<GithubLocalActionsTreeItem> {
return element;
}
async resolveTreeItem(item: TreeItem, element: GithubLocalActionsTreeItem, token: CancellationToken): Promise<GithubLocalActionsTreeItem> {
if (element.getToolTip) {
element.tooltip = await element.getToolTip();
} }
),
commands.registerCommand(
"githubLocalActions.runEvent",
async (
workspaceFolderWorkflowsTreeItem?: WorkspaceFolderWorkflowsTreeItem
) => {
const workspaceFolder = await Utils.getWorkspaceFolder(
workspaceFolderWorkflowsTreeItem?.workspaceFolder
);
if (workspaceFolder) {
const event = await window.showQuickPick(Object.values(Event), {
title: "Select the event to run",
placeHolder: "Event",
});
return element; if (event) {
} await act.runEvent(workspaceFolder, event as Event);
}
}
}
),
commands.registerCommand(
"githubLocalActions.refreshWorkflows",
async () => {
this.refresh();
}
),
commands.registerCommand(
"githubLocalActions.openWorkflow",
async (workflowTreeItem: WorkflowTreeItem) => {
try {
const document = await workspace.openTextDocument(
workflowTreeItem.workflow.uri
);
await window.showTextDocument(document);
} catch (error: any) {
try {
await workspace.fs.stat(workflowTreeItem.workflow.uri);
window.showErrorMessage(
`Failed to open workflow. Error: ${error}`
);
} catch (error: any) {
window.showErrorMessage(
`Workflow ${
path.parse(workflowTreeItem.workflow.uri.fsPath).base
} not found.`
);
}
}
}
),
commands.registerCommand(
"githubLocalActions.runWorkflow",
async (workflowTreeItem: WorkflowTreeItem) => {
if (workflowTreeItem) {
await act.runWorkflow(
workflowTreeItem.workspaceFolder,
workflowTreeItem.workflow
);
} else {
let errorMessage: string | undefined;
async getChildren(element?: GithubLocalActionsTreeItem): Promise<GithubLocalActionsTreeItem[]> { const activeTextEditor = window.activeTextEditor;
if (element) { if (activeTextEditor) {
return element.getChildren(); const uri = activeTextEditor.document.uri;
} else { const fileName = path.parse(uri.fsPath).base;
const items: GithubLocalActionsTreeItem[] = []; const workflowsDirectory =
let noWorkflows: boolean = true; WorkflowsManager.getWorkflowsDirectory();
if (
const workspaceFolders = workspace.workspaceFolders; uri.path.match(
if (workspaceFolders) { `.*/${workflowsDirectory}/.*\\.(${WorkflowsManager.yamlExtension}|${WorkflowsManager.ymlExtension})`
if (workspaceFolders.length === 1) { )
items.push(...await new WorkspaceFolderWorkflowsTreeItem(workspaceFolders[0]).getChildren()); ) {
const workspaceFolder = workspace.getWorkspaceFolder(uri);
const workflows = await act.workflowsManager.getWorkflows(workspaceFolders[0]); if (workspaceFolder) {
if (workflows && workflows.length > 0) { const workflows = await act.workflowsManager.getWorkflows(
noWorkflows = false; workspaceFolder
} );
} else if (workspaceFolders.length > 1) { const workflow = workflows.find(
for (const workspaceFolder of workspaceFolders) { (workflow) => workflow.uri.fsPath === uri.fsPath
items.push(new WorkspaceFolderWorkflowsTreeItem(workspaceFolder)); );
if (workflow) {
const workflows = await act.workflowsManager.getWorkflows(workspaceFolder); await act.runWorkflow(workspaceFolder, workflow);
if (workflows && workflows.length > 0) { } else {
noWorkflows = false; errorMessage = `Workflow not found in workflow directory (${workflowsDirectory}).`;
} }
} } else {
errorMessage = `${fileName} must be opened in a workspace folder to be executed locally.`;
} }
} else {
errorMessage = `${fileName} is not a workflow that can be executed locally.`;
}
} else {
errorMessage = "No workflow opened to execute locally.";
} }
await commands.executeCommand('setContext', 'githubLocalActions:noWorkflows', noWorkflows); if (errorMessage) {
return items; window
.showErrorMessage(errorMessage, "View Workflows")
.then(async (value) => {
if (value === "View Workflows") {
await commands.executeCommand("workflows.focus");
}
});
}
}
} }
),
commands.registerCommand(
"githubLocalActions.runJob",
async (jobTreeItem: JobTreeItem) => {
await act.runJob(
jobTreeItem.workspaceFolder,
jobTreeItem.workflow,
jobTreeItem.job
);
}
),
commands.registerCommand(
"githubLocalActions.runWorkflowEvent",
async (workflowTreeItem: WorkflowTreeItem) => {
// Filter to only events that are registered on the workflow
const registeredEventsOnWorkflow = Object.keys(
workflowTreeItem.workflow.yaml.on
);
if (registeredEventsOnWorkflow.length === 0) {
window.showErrorMessage(
`No events registered on the workflow (${workflowTreeItem.workflow.name}). Add an event to the \`on\` section of the workflow to trigger it.`
);
return;
}
const event = await window.showQuickPick(registeredEventsOnWorkflow, {
title: "Select the event to run",
placeHolder: "Event",
});
if (event) {
await act.runEvent(
workflowTreeItem.workspaceFolder,
event as Event,
{ workflow: workflowTreeItem.workflow }
);
}
}
),
commands.registerCommand(
"githubLocalActions.runJobEvent",
async (jobTreeItem: JobTreeItem) => {
// Filter to only events that are registered on the job's parent workflow
const registeredEventsOnJobParentWorkflow = Object.keys(
jobTreeItem.workflow.yaml.on
);
if (registeredEventsOnJobParentWorkflow.length === 0) {
window.showErrorMessage(
`No events registered on the workflow (${jobTreeItem.workflow.name}). Add an event to the \`on\` section of the workflow to trigger it.`
);
return;
}
const event = await window.showQuickPick(
registeredEventsOnJobParentWorkflow,
{
title: "Select the event to run",
placeHolder: "Event",
}
);
if (event) {
await act.runEvent(jobTreeItem.workspaceFolder, event as Event, {
workflow: jobTreeItem.workflow,
job: jobTreeItem.job,
});
}
}
)
);
}
refresh(element?: GithubLocalActionsTreeItem) {
this._onDidChangeTreeData.fire(element);
}
getTreeItem(
element: GithubLocalActionsTreeItem
): GithubLocalActionsTreeItem | Thenable<GithubLocalActionsTreeItem> {
return element;
}
async resolveTreeItem(
item: TreeItem,
element: GithubLocalActionsTreeItem,
token: CancellationToken
): Promise<GithubLocalActionsTreeItem> {
if (element.getToolTip) {
element.tooltip = await element.getToolTip();
} }
}
return element;
}
async getChildren(
element?: GithubLocalActionsTreeItem
): Promise<GithubLocalActionsTreeItem[]> {
if (element) {
return element.getChildren();
} else {
const items: GithubLocalActionsTreeItem[] = [];
let noWorkflows: boolean = true;
const workspaceFolders = workspace.workspaceFolders;
if (workspaceFolders) {
if (workspaceFolders.length === 1) {
items.push(
...(await new WorkspaceFolderWorkflowsTreeItem(
workspaceFolders[0]
).getChildren())
);
const workflows = await act.workflowsManager.getWorkflows(
workspaceFolders[0]
);
if (workflows && workflows.length > 0) {
noWorkflows = false;
}
} else if (workspaceFolders.length > 1) {
for (const workspaceFolder of workspaceFolders) {
items.push(new WorkspaceFolderWorkflowsTreeItem(workspaceFolder));
const workflows = await act.workflowsManager.getWorkflows(
workspaceFolder
);
if (workflows && workflows.length > 0) {
noWorkflows = false;
}
}
}
}
await commands.executeCommand(
"setContext",
"githubLocalActions:noWorkflows",
noWorkflows
);
return items;
}
}
}

View File

@@ -2,57 +2,84 @@ import * as fs from "fs/promises";
import * as path from "path"; import * as path from "path";
import { RelativePattern, Uri, workspace, WorkspaceFolder } from "vscode"; import { RelativePattern, Uri, workspace, WorkspaceFolder } from "vscode";
import * as yaml from "yaml"; import * as yaml from "yaml";
import { ConfigurationManager, Section } from "./configurationManager";
export interface Workflow { export interface Workflow {
name: string, name: string;
uri: Uri, uri: Uri;
fileContent?: string, fileContent?: string;
yaml?: any, yaml?: any;
error?: string error?: string;
} }
export interface Job { export interface Job {
name: string name: string;
id: string id: string;
} }
export class WorkflowsManager { export class WorkflowsManager {
static defaultWorkflowsDirectory: string = '.github/workflows'; static defaultWorkflowsDirectory: string = ".github/workflows";
static yamlExtension: string = 'yaml'; static giteaWorkflowsDirectory: string = ".gitea/workflows";
static ymlExtension: string = 'yml'; static yamlExtension: string = "yaml";
static ymlExtension: string = "yml";
static getWorkflowsDirectories(): string[] {
const directories = [
WorkflowsManager.defaultWorkflowsDirectory,
WorkflowsManager.giteaWorkflowsDirectory,
];
return directories;
}
static getWorkflowsDirectory(): string { static getWorkflowsDirectory(): string {
return ConfigurationManager.get<string>(Section.workflowsDirectory) || WorkflowsManager.defaultWorkflowsDirectory; return WorkflowsManager.defaultWorkflowsDirectory;
} }
async getWorkflows(workspaceFolder: WorkspaceFolder): Promise<Workflow[]> { async getWorkflows(workspaceFolder: WorkspaceFolder): Promise<Workflow[]> {
const workflows: Workflow[] = []; const workflows: Workflow[] = [];
const workflowsDirectory = WorkflowsManager.getWorkflowsDirectory(); const workflowsDirectories = WorkflowsManager.getWorkflowsDirectories();
const workflowFileUris = await workspace.findFiles(new RelativePattern(workspaceFolder, `${workflowsDirectory}/*.{${WorkflowsManager.yamlExtension},${WorkflowsManager.ymlExtension}}`));
for await (const workflowFileUri of workflowFileUris) {
let yamlContent: any | undefined;
for (const workflowsDirectory of workflowsDirectories) {
try { try {
const fileContent = await fs.readFile(workflowFileUri.fsPath, 'utf8'); const workflowFileUris = await workspace.findFiles(
yamlContent = yaml.parse(fileContent); new RelativePattern(
workspaceFolder,
`${workflowsDirectory}/*.{${WorkflowsManager.yamlExtension},${WorkflowsManager.ymlExtension}}`
)
);
workflows.push({ for await (const workflowFileUri of workflowFileUris) {
name: yamlContent.name || path.parse(workflowFileUri.fsPath).name, let yamlContent: any | undefined;
uri: workflowFileUri,
fileContent: fileContent, try {
yaml: yaml.parse(fileContent) const fileContent = await fs.readFile(
}); workflowFileUri.fsPath,
} catch (error: any) { "utf8"
workflows.push({ );
name: (yamlContent ? yamlContent.name : undefined) || path.parse(workflowFileUri.fsPath).name, yamlContent = yaml.parse(fileContent);
uri: workflowFileUri,
error: 'Failed to parse workflow' workflows.push({
}); name: yamlContent.name || path.parse(workflowFileUri.fsPath).name,
uri: workflowFileUri,
fileContent: fileContent,
yaml: yaml.parse(fileContent),
});
} catch (error: any) {
workflows.push({
name:
(yamlContent ? yamlContent.name : undefined) ||
path.parse(workflowFileUri.fsPath).name,
uri: workflowFileUri,
error: "Failed to parse workflow",
});
}
}
} catch (error) {
// Directory doesn't exist, skip it
continue;
} }
} }
return workflows; return workflows;
} }
} }