Rework to use CustomExecution

Signed-off-by: Sanjula Ganepola <sanjulagane@gmail.com>
This commit is contained in:
Sanjula Ganepola
2024-09-28 12:14:28 -04:00
parent d2fb99de18
commit e5e1c06701
7 changed files with 111 additions and 23 deletions

6
package-lock.json generated
View File

@@ -9,6 +9,7 @@
"version": "0.0.1",
"license": "Apache-2.0",
"dependencies": {
"child_process": "^1.0.2",
"yaml": "^2.5.1"
},
"devDependencies": {
@@ -1197,6 +1198,11 @@
"node": ">=8"
}
},
"node_modules/child_process": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz",
"integrity": "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g=="
},
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",

View File

@@ -213,6 +213,7 @@
"test": "vscode-test"
},
"dependencies": {
"child_process": "^1.0.2",
"yaml": "^2.5.1"
},
"devDependencies": {
@@ -229,4 +230,4 @@
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4"
}
}
}

View File

@@ -1,5 +1,6 @@
import * as child_process from 'child_process';
import * as path from "path";
import { commands, ShellExecution, TaskGroup, TaskPanelKind, TaskRevealKind, tasks, TaskScope, window } from "vscode";
import { commands, CustomExecution, EventEmitter, Pseudoterminal, TaskDefinition, TaskGroup, TaskPanelKind, TaskRevealKind, tasks, TaskScope, TerminalDimensions, window, workspace } from "vscode";
import { ComponentManager } from "./componentManager";
import { Workflow } from "./workflowManager";
@@ -51,15 +52,14 @@ export class Act {
}
static async runEvent(eventTrigger: EventTrigger) {
return await Act.runCommand(`${Act.base} ${eventTrigger}`);
// return await Act.runCommand(`${Act.base} ${eventTrigger}`);
}
static async runWorkflow(workflow: Workflow) {
return await Act.runCommand(`${Act.base} ${Option.Workflows} '.github/workflows/${path.parse(workflow.uri.fsPath).base}'`, workflow);
return await Act.runCommand(workflow, `${Act.base} ${Option.Workflows} ".github/workflows/${path.parse(workflow.uri.fsPath).base}"`);
}
static async runCommand(command: string, workflow?: Workflow) {
static async runCommand(workflow: Workflow, command: string) {
const unreadyComponents = await ComponentManager.getUnreadyComponents();
if (unreadyComponents.length > 0) {
window.showErrorMessage(`The following required components are not ready: ${unreadyComponents.map(component => component.name).join(', ')}`, 'Fix...').then(async value => {
@@ -70,12 +70,18 @@ export class Act {
return;
}
const workspaceFolder = workspace.getWorkspaceFolder(workflow.uri);
if (!workspaceFolder) {
window.showErrorMessage('Failed to detect workspace folder');
return;
}
await tasks.executeTask({
name: workflow?.name || 'act',
name: workflow.name,
detail: 'Run workflow',
definition: { type: 'GitHub Local Actions' },
source: 'GitHub Local Actions',
scope: TaskScope.Workspace,
scope: workspaceFolder || TaskScope.Workspace,
isBackground: true,
presentationOptions: {
reveal: TaskRevealKind.Always,
@@ -89,7 +95,42 @@ export class Act {
problemMatchers: [],
runOptions: {},
group: TaskGroup.Build,
execution: new ShellExecution(command)
execution: new CustomExecution(async (resolvedDefinition: TaskDefinition): Promise<Pseudoterminal> => {
const writeEmitter = new EventEmitter<string>();
const closeEmitter = new EventEmitter<number>();
return {
onDidWrite: writeEmitter.event,
onDidClose: closeEmitter.event,
open: async (initialDimensions: TerminalDimensions | undefined): Promise<void> => {
writeEmitter.fire(`Workflow: ${workflow.name}\r\nPath: ${workflow.uri.fsPath}\r\nCommand: ${command}\r\nTimestamp: ${new Date().toLocaleTimeString()}\r\n\r\n`);
const exec = child_process.spawn(command, { cwd: workspaceFolder.uri.fsPath, shell: '/usr/bin/bash' });
exec.stdout.on('data', (data) => {
const output = data.toString();
writeEmitter.fire(output);
if (output.includes('success')) {
window.showInformationMessage('Command succeeded!');
}
});
exec.stderr.on('data', (data) => {
const error = data.toString();
writeEmitter.fire(error);
});
exec.on('close', (code) => {
closeEmitter.fire(code || 0);
});
},
close: () => {
// TODO:
}
};
}
)
});
}
}

View File

@@ -1,12 +1,12 @@
export interface Component {
name: string,
icon: string,
status: Status,
status: ComponentStatus,
required: boolean
message?: string
}
export enum Status {
export enum ComponentStatus {
Enabled = 'Enabled',
Warning = 'Warning',
Disabled = 'Disabled'
@@ -18,26 +18,26 @@ export class ComponentManager {
{
name: 'nektos/act',
icon: 'package',
status: Status.Enabled,
status: ComponentStatus.Enabled,
required: true
},
{
name: 'Docker Engine',
icon: 'dashboard',
status: Status.Enabled,
status: ComponentStatus.Enabled,
required: true
},
{
name: 'GitHub Actions Extension',
icon: 'extensions',
status: Status.Warning,
status: ComponentStatus.Warning,
required: false,
message: 'GitHub Actions extension is not required, but is recommended to take advantage of workflow editor features.'
},
{
name: 'GitHub CLI',
icon: 'terminal',
status: Status.Warning,
status: ComponentStatus.Warning,
required: false,
message: 'GitHub CLI is not required, but is recommended if you plan to use it to retrieve GitHub tokens.'
}
@@ -46,6 +46,6 @@ export class ComponentManager {
static async getUnreadyComponents(): Promise<Component[]> {
const components = await ComponentManager.getComponents();
return components.filter(component => component.required && component.status !== Status.Enabled);
return components.filter(component => component.required && component.status !== ComponentStatus.Enabled);
}
}

View File

@@ -1,5 +1,5 @@
import { CancellationToken, Event, FileDecoration, FileDecorationProvider, ProviderResult, ThemeColor, Uri } from "vscode";
import { Status } from "../componentManager";
import { ComponentStatus } from "../componentManager";
import ComponentTreeItem from "./components/component";
import WorkflowTreeItem from "./workflows/workflow";
@@ -9,17 +9,17 @@ export class DecorationProvider implements FileDecorationProvider {
const params = new URLSearchParams(uri.query);
if (uri.scheme === ComponentTreeItem.contextValue) {
if (params.get('status') === Status.Enabled) {
if (params.get('status') === ComponentStatus.Enabled) {
return {
badge: '✅',
color: new ThemeColor('GitHubLocalActions.green')
};
} else if (params.get('status') === Status.Warning) {
} else if (params.get('status') === ComponentStatus.Warning) {
return {
badge: '⚠️',
color: new ThemeColor('GitHubLocalActions.yellow')
};
} else if (params.get('status') === Status.Disabled) {
} else if (params.get('status') === ComponentStatus.Disabled) {
return {
badge: '❌',
color: new ThemeColor('GitHubLocalActions.red')

View File

@@ -7,7 +7,7 @@ export default class WorkflowTreeItem extends TreeItem implements GithubLocalAct
workflow: Workflow;
constructor(workflow: Workflow) {
super(workflow.content.name || workflow.name, workflow.error ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed);
super(workflow.name, workflow.error ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed);
this.workflow = workflow;
this.contextValue = WorkflowTreeItem.contextValue;
this.iconPath = new ThemeIcon('layers');

View File

@@ -10,7 +10,44 @@ export interface Workflow {
error?: string
}
export interface WorkflowLog {
workflow: Workflow,
status: WorkflowStatus
}
export enum WorkflowStatus {
Queued = 'queued',
InProgress = 'inProgress',
success = 'success',
failed = 'failed',
Cancelled = 'cancelled'
}
export enum JobStatus {
Queued = 'queued',
InProgress = 'inProgress',
Skipped = 'skipped',
success = 'success',
failed = 'failed',
Cancelled = 'cancelled'
}
export enum StepStatus {
Queued = 'queued',
InProgress = 'inProgress',
Skipped = 'skipped',
success = 'success',
failed = 'failed',
Cancelled = 'cancelled'
}
export class WorkflowManager {
private workflowLogs: WorkflowLog[] = [];
constructor() {
}
static async getWorkflows(): Promise<Workflow[]> {
const workflows: Workflow[] = [];
@@ -19,17 +56,20 @@ export class WorkflowManager {
const workflowFileUris = await workspace.findFiles(`.github/workflows/*.{yml,yaml}`);
for await (const workflowFileUri of workflowFileUris) {
let yamlContent: any | undefined;
try {
const fileContent = await fs.readFile(workflowFileUri.fsPath, 'utf8');
yamlContent = yaml.parse(fileContent);
workflows.push({
name: path.parse(workflowFileUri.fsPath).name,
name: yamlContent.name || path.parse(workflowFileUri.fsPath).name,
uri: workflowFileUri,
content: yaml.parse(fileContent)
});
} catch (error) {
workflows.push({
name: path.parse(workflowFileUri.fsPath).name,
name: (yamlContent ? yamlContent.name : undefined) || path.parse(workflowFileUri.fsPath).name,
uri: workflowFileUri,
error: 'Failed to parse workflow file'
});