From 092a9426bcc732de42285475b56ceca95dfd3307 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Sun, 24 Nov 2024 23:05:43 -0500 Subject: [PATCH 1/9] Bump to v1.1.1 Signed-off-by: Sanjula Ganepola --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f3665da..17e5aa6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "github-local-actions", - "version": "1.1.0", + "version": "1.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "github-local-actions", - "version": "1.1.0", + "version": "1.1.1", "license": "Apache-2.0", "dependencies": { "child_process": "^1.0.2", diff --git a/package.json b/package.json index 3c863b5..d96b944 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "publisher": "SanjulaGanepola", "license": "Apache-2.0", - "version": "1.1.0", + "version": "1.1.1", "repository": { "url": "https://github.com/SanjulaGanepola/github-local-actions" }, From b0e07549a67f014c918395e5982939b950fb4ce4 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Sun, 24 Nov 2024 23:40:47 -0500 Subject: [PATCH 2/9] Fix act not found when installed as github cli extension Signed-off-by: Sanjula Ganepola --- package.json | 5 +++++ src/act.ts | 13 +++++++++++-- src/configurationManager.ts | 7 +++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3c863b5..6029881 100644 --- a/package.json +++ b/package.json @@ -685,6 +685,11 @@ "configuration": { "title": "GitHub Local Actions", "properties": { + "githubLocalActions.actCommand": { + "markdownDescription": "The base `nektos/act` command to be called. By default, this will be `act` (requires the binary to be on your `PATH`). If the binary is not on your `PATH`, the command can be fully qualified. If `act` is installed as a GitHub CLI extension, this command should be set to `gh act`.", + "type": "string", + "default": "act" + }, "githubLocalActions.dockerDesktopPath": { "type": "string", "markdownDescription": "The path to your Docker Desktop executable (used for Windows and MacOS). To start Docker Engine from the `Components` view, this application will be launched. Refer to the default path based on OS:\n\n* **Windows**: `C:/Program Files/Docker/Docker/Docker Desktop.exe`\n\n* **MacOS**: `/Applications/Docker.app`", diff --git a/src/act.ts b/src/act.ts index dc84b68..9df35fe 100644 --- a/src/act.ts +++ b/src/act.ts @@ -2,6 +2,7 @@ import * as path from "path"; import sanitize from "sanitize-filename"; import { ExtensionContext, ShellExecution, TaskGroup, TaskPanelKind, TaskRevealKind, tasks, TaskScope, Uri, window, workspace, WorkspaceFolder } from "vscode"; import { ComponentsManager } from "./componentsManager"; +import { ConfigurationManager, Section } from "./configurationManager"; import { componentsTreeDataProvider, historyTreeDataProvider } from './extension'; import { HistoryManager, HistoryStatus } from './historyManager'; import { SecretManager } from "./secretManager"; @@ -66,7 +67,8 @@ export interface CommandArgs { } export class Act { - private static base: string = 'act'; + static command: string = 'act'; + static githubCliCommand: string = 'act'; context: ExtensionContext; storageManager: StorageManager; secretManager: SecretManager; @@ -305,10 +307,11 @@ export class Act { } catch (error: any) { } // Build command with settings + const actCommand = ConfigurationManager.get(Section.actCommand) || Act.command; const settings = await this.settingsManager.getSettings(workspaceFolder, true); const command = `set -o pipefail; ` + - `${Act.base} ${commandArgs.options}` + + `${actCommand} ${commandArgs.options}` + (settings.secrets.length > 0 ? ` ${Option.Secret} ${settings.secrets.map(secret => secret.key).join(` ${Option.Secret} `)}` : ``) + (settings.secretFiles.length > 0 ? ` ${Option.SecretFile} "${settings.secretFiles[0].path}"` : ` ${Option.SecretFile} ""`) + (settings.variables.length > 0 ? ` ${Option.Variable} ${settings.variables.map(variable => (variable.value ? `${variable.key}=${variable.value}` : variable.key)).join(` ${Option.Variable} `)}` : ``) + @@ -386,6 +389,12 @@ export class Act { group: TaskGroup.Build, execution: new ShellExecution(command) }); + + if (command.includes('gh-act')) { + ConfigurationManager.set(Section.actCommand, Act.command); + } else { + ConfigurationManager.set(Section.actCommand, Act.githubCliCommand); + } } } } \ No newline at end of file diff --git a/src/configurationManager.ts b/src/configurationManager.ts index bf047bc..605d301 100644 --- a/src/configurationManager.ts +++ b/src/configurationManager.ts @@ -1,4 +1,5 @@ import { ConfigurationTarget, workspace } from 'vscode'; +import { Act } from './act'; export enum Platform { windows = 'win32', @@ -7,6 +8,7 @@ export enum Platform { } export enum Section { + actCommand = 'actCommand', dockerDesktopPath = 'dockerDesktopPath' } @@ -30,6 +32,11 @@ export namespace ConfigurationManager { ConfigurationManager.set(Section.dockerDesktopPath, dockerDesktopPath); } + + let actCommand = ConfigurationManager.get(Section.actCommand); + if (!actCommand) { + ConfigurationManager.set(Section.actCommand, Act.command); + } } export function getSearchTerm(section: Section): string { From b6a12f35a8d4ec7f2b40379a3f105a6838756054 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Mon, 25 Nov 2024 00:10:45 -0500 Subject: [PATCH 3/9] Add getActCommand Signed-off-by: Sanjula Ganepola --- package.json | 2 +- src/act.ts | 12 ++++++++---- src/componentsManager.ts | 3 ++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 6029881..43a7578 100644 --- a/package.json +++ b/package.json @@ -686,7 +686,7 @@ "title": "GitHub Local Actions", "properties": { "githubLocalActions.actCommand": { - "markdownDescription": "The base `nektos/act` command to be called. By default, this will be `act` (requires the binary to be on your `PATH`). If the binary is not on your `PATH`, the command can be fully qualified. If `act` is installed as a GitHub CLI extension, this command should be set to `gh act`.", + "markdownDescription": "The base `nektos/act` command to be called. By default, this will be `act` (requires the binary to be on your `PATH`). If the binary is not on your `PATH`, the command should be fully qualified. If `act` is installed as a GitHub CLI extension, this command should be set to `gh act`.", "type": "string", "default": "act" }, diff --git a/src/act.ts b/src/act.ts index 9df35fe..0959854 100644 --- a/src/act.ts +++ b/src/act.ts @@ -68,7 +68,7 @@ export interface CommandArgs { export class Act { static command: string = 'act'; - static githubCliCommand: string = 'act'; + static githubCliCommand: string = 'gh act'; context: ExtensionContext; storageManager: StorageManager; secretManager: SecretManager; @@ -216,6 +216,10 @@ export class Act { }); } + static getActCommand() { + return ConfigurationManager.get(Section.actCommand) || Act.command; + } + async runAllWorkflows(workspaceFolder: WorkspaceFolder) { return await this.runCommand({ path: workspaceFolder.uri.fsPath, @@ -307,7 +311,7 @@ export class Act { } catch (error: any) { } // Build command with settings - const actCommand = ConfigurationManager.get(Section.actCommand) || Act.command; + const actCommand = Act.getActCommand(); const settings = await this.settingsManager.getSettings(workspaceFolder, true); const command = `set -o pipefail; ` + @@ -391,9 +395,9 @@ export class Act { }); if (command.includes('gh-act')) { - ConfigurationManager.set(Section.actCommand, Act.command); - } else { ConfigurationManager.set(Section.actCommand, Act.githubCliCommand); + } else { + ConfigurationManager.set(Section.actCommand, Act.command); } } } diff --git a/src/componentsManager.ts b/src/componentsManager.ts index d3400e2..acdaaeb 100644 --- a/src/componentsManager.ts +++ b/src/componentsManager.ts @@ -1,5 +1,6 @@ import * as childProcess from "child_process"; import { commands, env, extensions, QuickPickItemKind, ShellExecution, TaskGroup, TaskPanelKind, TaskRevealKind, tasks, TaskScope, ThemeIcon, Uri, window } from "vscode"; +import { Act } from "./act"; import { ConfigurationManager, Platform, Section } from "./configurationManager"; import { act, componentsTreeDataProvider } from "./extension"; import ComponentsTreeDataProvider from "./views/components/componentsTreeDataProvider"; @@ -33,7 +34,7 @@ export class ComponentsManager { async getComponents(): Promise[]> { const components: Component[] = []; - const actCliInfo = await this.getCliInfo('act --version', /act version (.+)/, false, false); + const actCliInfo = await this.getCliInfo(`${Act.getActCommand()} --version`, /act version (.+)/, false, false); components.push({ name: 'nektos/act', icon: 'terminal', From 6292da3f72f3b4943db1a7c7b26b49dfa5f632a3 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Mon, 25 Nov 2024 00:11:48 -0500 Subject: [PATCH 4/9] Add status check and login for github cli extension Signed-off-by: Sanjula Ganepola --- src/act.ts | 22 +++++++++++----------- src/configurationManager.ts | 6 +++--- src/extension.ts | 3 ++- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/act.ts b/src/act.ts index 0959854..f604325 100644 --- a/src/act.ts +++ b/src/act.ts @@ -94,7 +94,7 @@ export class Act { 'Chocolatey': 'choco install act-cli', 'Winget': 'winget install nektos.act', 'Scoop': 'scoop install act', - 'GitHub CLI': 'gh extension install https://github.com/nektos/gh-act' + 'GitHub CLI': 'gh auth status || gh auth login && gh extension install https://github.com/nektos/gh-act' }; this.prebuiltExecutables = { @@ -109,7 +109,7 @@ export class Act { 'Homebrew': 'brew install act', 'Nix': 'nix run nixpkgs#act', 'MacPorts': 'sudo port install act', - 'GitHub CLI': 'gh extension install https://github.com/nektos/gh-act' + 'GitHub CLI': 'gh auth status || gh auth login && gh extension install https://github.com/nektos/gh-act' }; this.prebuiltExecutables = { @@ -123,7 +123,7 @@ export class Act { 'Nix': 'nix run nixpkgs#act', 'AUR': 'yay -Syu act', 'COPR': 'dnf copr enable goncalossilva/act && dnf install act-cli', - 'GitHub CLI': 'gh extension install https://github.com/nektos/gh-act' + 'GitHub CLI': 'gh auth status || gh auth login && gh extension install https://github.com/nektos/gh-act' }; this.prebuiltExecutables = { @@ -160,9 +160,15 @@ export class Act { }); // Refresh components view after installation - tasks.onDidEndTask(e => { + tasks.onDidEndTask(async e => { const taskDefinition = e.execution.task.definition; if (taskDefinition.type === 'nektos/act installation') { + if (taskDefinition.ghCliInstall) { + await ConfigurationManager.set(Section.actCommand, Act.githubCliCommand); + } else { + await ConfigurationManager.set(Section.actCommand, Act.command); + } + componentsTreeDataProvider.refresh(); } }); @@ -375,7 +381,7 @@ export class Act { await tasks.executeTask({ name: 'nektos/act', detail: 'Install nektos/act', - definition: { type: 'nektos/act installation' }, + definition: { type: 'nektos/act installation', ghCliInstall: command.includes('gh-act') }, source: 'GitHub Local Actions', scope: TaskScope.Workspace, isBackground: true, @@ -393,12 +399,6 @@ export class Act { group: TaskGroup.Build, execution: new ShellExecution(command) }); - - if (command.includes('gh-act')) { - ConfigurationManager.set(Section.actCommand, Act.githubCliCommand); - } else { - ConfigurationManager.set(Section.actCommand, Act.command); - } } } } \ No newline at end of file diff --git a/src/configurationManager.ts b/src/configurationManager.ts index 605d301..e6c8c84 100644 --- a/src/configurationManager.ts +++ b/src/configurationManager.ts @@ -16,7 +16,7 @@ export namespace ConfigurationManager { export const group: string = 'githubLocalActions'; export const searchPrefix: string = '@ext:sanjulaganepola.github-local-actions'; - export function initialize(): void { + export async function initialize(): Promise { let dockerDesktopPath = ConfigurationManager.get(Section.dockerDesktopPath); if (!dockerDesktopPath) { switch (process.platform) { @@ -30,12 +30,12 @@ export namespace ConfigurationManager { return; } - ConfigurationManager.set(Section.dockerDesktopPath, dockerDesktopPath); + await ConfigurationManager.set(Section.dockerDesktopPath, dockerDesktopPath); } let actCommand = ConfigurationManager.get(Section.actCommand); if (!actCommand) { - ConfigurationManager.set(Section.actCommand, Act.command); + await ConfigurationManager.set(Section.actCommand, Act.command); } } diff --git a/src/extension.ts b/src/extension.ts index c39afb8..7aa315c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -54,7 +54,8 @@ export function activate(context: vscode.ExtensionContext) { ConfigurationManager.initialize(); workspace.onDidChangeConfiguration(async event => { if (event.affectsConfiguration(ConfigurationManager.group)) { - ConfigurationManager.initialize(); + await ConfigurationManager.initialize(); + componentsTreeDataProvider.refresh(); } }); From 7f00d126810695f803c7b6a8b719e4962be3da44 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Mon, 25 Nov 2024 17:49:26 -0500 Subject: [PATCH 5/9] Fix selected input with no values not being used Signed-off-by: Sanjula Ganepola --- src/settingsManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settingsManager.ts b/src/settingsManager.ts index ce85848..25d3834 100644 --- a/src/settingsManager.ts +++ b/src/settingsManager.ts @@ -63,7 +63,7 @@ export class SettingsManager { const secretFiles = (await this.getSettingFiles(workspaceFolder, StorageKey.SecretFiles)).filter(secretFile => !isUserSelected || secretFile.selected); const variables = (await this.getSetting(workspaceFolder, SettingsManager.variablesRegExp, StorageKey.Variables, false, Visibility.show)).filter(variable => !isUserSelected || variable.selected); const variableFiles = (await this.getSettingFiles(workspaceFolder, StorageKey.VariableFiles)).filter(variableFile => !isUserSelected || variableFile.selected); - const inputs = (await this.getSetting(workspaceFolder, SettingsManager.inputsRegExp, StorageKey.Inputs, false, Visibility.show)).filter(input => !isUserSelected || (input.selected && input.value)); + const inputs = (await this.getSetting(workspaceFolder, SettingsManager.inputsRegExp, StorageKey.Inputs, false, Visibility.show)).filter(input => !isUserSelected || input.selected); const inputFiles = (await this.getSettingFiles(workspaceFolder, StorageKey.InputFiles)).filter(inputFile => !isUserSelected || inputFile.selected); const runners = (await this.getSetting(workspaceFolder, SettingsManager.runnersRegExp, StorageKey.Runners, false, Visibility.show)).filter(runner => !isUserSelected || (runner.selected && runner.value)); const payloadFiles = (await this.getSettingFiles(workspaceFolder, StorageKey.PayloadFiles)).filter(payloadFile => !isUserSelected || payloadFile.selected); From cf7582bfc89fb9c9854c0040a8e82a996ccf9a83 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Mon, 25 Nov 2024 20:02:26 -0500 Subject: [PATCH 6/9] revert Signed-off-by: Sanjula Ganepola --- src/settingsManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settingsManager.ts b/src/settingsManager.ts index 25d3834..ce85848 100644 --- a/src/settingsManager.ts +++ b/src/settingsManager.ts @@ -63,7 +63,7 @@ export class SettingsManager { const secretFiles = (await this.getSettingFiles(workspaceFolder, StorageKey.SecretFiles)).filter(secretFile => !isUserSelected || secretFile.selected); const variables = (await this.getSetting(workspaceFolder, SettingsManager.variablesRegExp, StorageKey.Variables, false, Visibility.show)).filter(variable => !isUserSelected || variable.selected); const variableFiles = (await this.getSettingFiles(workspaceFolder, StorageKey.VariableFiles)).filter(variableFile => !isUserSelected || variableFile.selected); - const inputs = (await this.getSetting(workspaceFolder, SettingsManager.inputsRegExp, StorageKey.Inputs, false, Visibility.show)).filter(input => !isUserSelected || input.selected); + const inputs = (await this.getSetting(workspaceFolder, SettingsManager.inputsRegExp, StorageKey.Inputs, false, Visibility.show)).filter(input => !isUserSelected || (input.selected && input.value)); const inputFiles = (await this.getSettingFiles(workspaceFolder, StorageKey.InputFiles)).filter(inputFile => !isUserSelected || inputFile.selected); const runners = (await this.getSetting(workspaceFolder, SettingsManager.runnersRegExp, StorageKey.Runners, false, Visibility.show)).filter(runner => !isUserSelected || (runner.selected && runner.value)); const payloadFiles = (await this.getSettingFiles(workspaceFolder, StorageKey.PayloadFiles)).filter(payloadFile => !isUserSelected || payloadFile.selected); From 0e125ad2fd8577b67dd964fbc1df5cd17660e03f Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Mon, 25 Nov 2024 22:32:16 -0500 Subject: [PATCH 7/9] Switch to use custom execution to remove pipefail and improve task output retrieval Signed-off-by: Sanjula Ganepola --- src/act.ts | 203 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 130 insertions(+), 73 deletions(-) diff --git a/src/act.ts b/src/act.ts index f604325..5947c2b 100644 --- a/src/act.ts +++ b/src/act.ts @@ -1,6 +1,8 @@ +import * as childProcess from "child_process"; +import * as fs from "fs/promises"; import * as path from "path"; import sanitize from "sanitize-filename"; -import { ExtensionContext, ShellExecution, TaskGroup, TaskPanelKind, TaskRevealKind, tasks, TaskScope, Uri, window, workspace, WorkspaceFolder } from "vscode"; +import { CustomExecution, EventEmitter, ExtensionContext, Pseudoterminal, ShellExecution, TaskDefinition, TaskGroup, TaskPanelKind, TaskRevealKind, tasks, TaskScope, TerminalDimensions, Uri, window, workspace, WorkspaceFolder } from "vscode"; import { ComponentsManager } from "./componentsManager"; import { ConfigurationManager, Section } from "./configurationManager"; import { componentsTreeDataProvider, historyTreeDataProvider } from './extension'; @@ -56,7 +58,8 @@ export enum Option { VariableFile = '--var-file', Input = '--input', InputFile = '--input-file', - PayloadFile = '--eventpath' + PayloadFile = '--eventpath', + Json = '--json' } export interface CommandArgs { @@ -78,6 +81,8 @@ export class Act { settingsManager: SettingsManager; installationCommands: { [packageManager: string]: string }; prebuiltExecutables: { [architecture: string]: string }; + refreshInterval: NodeJS.Timeout | undefined; + runningTaskCount: number; constructor(context: ExtensionContext) { this.context = context; @@ -87,6 +92,7 @@ export class Act { this.workflowsManager = new WorkflowsManager(); this.historyManager = new HistoryManager(this.storageManager); this.settingsManager = new SettingsManager(this.storageManager, this.secretManager); + this.runningTaskCount = 0; switch (process.platform) { case 'win32': @@ -140,21 +146,26 @@ export class Act { } // Setup automatic history view refreshing - let refreshInterval: NodeJS.Timeout | undefined; tasks.onDidStartTask(e => { const taskDefinition = e.execution.task.definition; - if (taskDefinition.type === 'GitHub Local Actions' && !refreshInterval) { - refreshInterval = setInterval(() => { - historyTreeDataProvider.refresh(); - }, 1000); + if (taskDefinition.type === 'GitHub Local Actions') { + this.runningTaskCount++; + + if (!this.refreshInterval && this.runningTaskCount >= 0) { + this.refreshInterval = setInterval(() => { + historyTreeDataProvider.refresh(); + }, 1000); + } } }); tasks.onDidEndTask(e => { const taskDefinition = e.execution.task.definition; if (taskDefinition.type === 'GitHub Local Actions') { - if (refreshInterval) { - clearInterval(refreshInterval); - refreshInterval = undefined; + this.runningTaskCount--; + + if (this.refreshInterval && this.runningTaskCount == 0) { + clearInterval(this.refreshInterval); + this.refreshInterval = undefined; } } }); @@ -163,6 +174,7 @@ export class Act { tasks.onDidEndTask(async e => { const taskDefinition = e.execution.task.definition; if (taskDefinition.type === 'nektos/act installation') { + // Update base act command based on installation method if (taskDefinition.ghCliInstall) { await ConfigurationManager.set(Section.actCommand, Act.githubCliCommand); } else { @@ -172,54 +184,6 @@ export class Act { componentsTreeDataProvider.refresh(); } }); - - tasks.onDidStartTaskProcess(e => { - const taskDefinition = e.execution.task.definition; - if (taskDefinition.type === 'GitHub Local Actions') { - const commandArgs: CommandArgs = taskDefinition.commandArgs; - const historyIndex = taskDefinition.historyIndex; - - // Add new entry to workspace history - this.historyManager.workspaceHistory[commandArgs.path].push({ - index: historyIndex, - count: taskDefinition.count, - name: `${commandArgs.name}`, - status: HistoryStatus.Running, - date: { - start: taskDefinition.start.toString() - }, - taskExecution: e.execution, - commandArgs: commandArgs, - logPath: taskDefinition.logPath - }); - historyTreeDataProvider.refresh(); - this.storageManager.update(StorageKey.WorkspaceHistory, this.historyManager.workspaceHistory); - } - }); - tasks.onDidEndTaskProcess(e => { - const taskDefinition = e.execution.task.definition; - if (taskDefinition.type === 'GitHub Local Actions') { - const commandArgs: CommandArgs = taskDefinition.commandArgs; - const historyIndex = taskDefinition.historyIndex; - - // Set end status - if (this.historyManager.workspaceHistory[commandArgs.path][historyIndex].status === HistoryStatus.Running) { - if (e.exitCode === 0) { - this.historyManager.workspaceHistory[commandArgs.path][historyIndex].status = HistoryStatus.Success; - } else if (!e.exitCode) { - this.historyManager.workspaceHistory[commandArgs.path][historyIndex].status = HistoryStatus.Cancelled; - } else { - this.historyManager.workspaceHistory[commandArgs.path][historyIndex].status = HistoryStatus.Failed; - } - } - - // Set end time - this.historyManager.workspaceHistory[commandArgs.path][historyIndex].date.end = new Date().toString(); - - historyTreeDataProvider.refresh(); - this.storageManager.update(StorageKey.WorkspaceHistory, this.historyManager.workspaceHistory); - } - }); } static getActCommand() { @@ -320,7 +284,6 @@ export class Act { const actCommand = Act.getActCommand(); const settings = await this.settingsManager.getSettings(workspaceFolder, true); const command = - `set -o pipefail; ` + `${actCommand} ${commandArgs.options}` + (settings.secrets.length > 0 ? ` ${Option.Secret} ${settings.secrets.map(secret => secret.key).join(` ${Option.Secret} `)}` : ``) + (settings.secretFiles.length > 0 ? ` ${Option.SecretFile} "${settings.secretFiles[0].path}"` : ` ${Option.SecretFile} ""`) + @@ -329,11 +292,10 @@ export class Act { (settings.inputs.length > 0 ? ` ${Option.Input} ${settings.inputs.map(input => `${input.key}=${input.value}`).join(` ${Option.Input} `)}` : ``) + (settings.inputFiles.length > 0 ? ` ${Option.InputFile} "${settings.inputFiles[0].path}"` : ` ${Option.InputFile} ""`) + (settings.runners.length > 0 ? ` ${Option.Platform} ${settings.runners.map(runner => `${runner.key}=${runner.value}`).join(` ${Option.Platform} `)}` : ``) + - (settings.payloadFiles.length > 0 ? ` ${Option.PayloadFile} "${settings.payloadFiles[0].path}"` : ` ${Option.PayloadFile} ""`) + - ` 2>&1 | tee "${logPath}"`; + (settings.payloadFiles.length > 0 ? ` ${Option.PayloadFile} "${settings.payloadFiles[0].path}"` : ` ${Option.PayloadFile} ""`); // Execute task - await tasks.executeTask({ + const taskExecution = await tasks.executeTask({ name: `${commandArgs.name} #${count}`, detail: `${commandArgs.name} #${count}`, definition: { @@ -359,18 +321,113 @@ export class Act { problemMatchers: [], runOptions: {}, group: TaskGroup.Build, - execution: new ShellExecution( - command, - { - cwd: commandArgs.path, - env: settings.secrets - .filter(secret => secret.value) - .reduce((previousValue, currentValue) => { - previousValue[currentValue.key] = currentValue.value; - return previousValue; - }, {} as Record) + execution: new CustomExecution(async (resolvedDefinition: TaskDefinition): Promise => { + // Add new entry to workspace history + this.historyManager.workspaceHistory[commandArgs.path].push({ + index: historyIndex, + count: count, + name: `${commandArgs.name}`, + status: HistoryStatus.Running, + date: { + start: start.toString() + }, + taskExecution: taskExecution, + commandArgs: commandArgs, + logPath: logPath + }); + historyTreeDataProvider.refresh(); + this.storageManager.update(StorageKey.WorkspaceHistory, this.historyManager.workspaceHistory); + + const writeEmitter = new EventEmitter(); + const closeEmitter = new EventEmitter(); + + writeEmitter.event(async data => { + try { + // Create log file if it does not exist + try { + await fs.access(logPath); + } catch (error: any) { + await fs.writeFile(logPath, ''); + } + + // Append data to log file + await fs.appendFile(logPath, data); + } catch (error) { } + }); + + const handleIO = (data: any) => { + const lines: string[] = data.toString().split('\n').filter((line: string) => line != ''); + for (const line of lines) { + writeEmitter.fire(`${line.trimEnd()}\r\n`); + } } - ) + + const exec = childProcess.spawn( + command, + { + cwd: commandArgs.path, + shell: true, + env: settings.secrets + .filter(secret => secret.value) + .reduce((previousValue, currentValue) => { + previousValue[currentValue.key] = currentValue.value; + return previousValue; + }, {} as Record) + } + ); + exec.stdout.on('data', handleIO); + exec.stderr.on('data', handleIO); + exec.on('exit', (code, signal) => { + // Set execution status and end time in workspace history + if (this.historyManager.workspaceHistory[commandArgs.path][historyIndex].status === HistoryStatus.Running) { + if (code === 0) { + this.historyManager.workspaceHistory[commandArgs.path][historyIndex].status = HistoryStatus.Success; + } else if (!code) { + this.historyManager.workspaceHistory[commandArgs.path][historyIndex].status = HistoryStatus.Cancelled; + } else { + this.historyManager.workspaceHistory[commandArgs.path][historyIndex].status = HistoryStatus.Failed; + } + } + this.historyManager.workspaceHistory[commandArgs.path][historyIndex].date.end = new Date().toString(); + historyTreeDataProvider.refresh(); + this.storageManager.update(StorageKey.WorkspaceHistory, this.historyManager.workspaceHistory); + + if (signal === 'SIGINT') { + writeEmitter.fire(`\r\nTask interrupted.\r\n`); + closeEmitter.fire(code || 0); + } else { + writeEmitter.fire(`\r\nTask exited with exit code ${code}.\r\n`); + closeEmitter.fire(code || 0); + } + }); + exec.on('close', (code) => { + closeEmitter.fire(code || 0); + }); + + return { + onDidWrite: writeEmitter.event, + onDidClose: closeEmitter.event, + open: async (initialDimensions: TerminalDimensions | undefined): Promise => { + writeEmitter.fire(`${command}\r\n\r\n`); + }, + handleInput: (data: string) => { + if (data === '\x03') { + exec.kill('SIGINT'); + exec.stdout.destroy(); + exec.stdin.destroy(); + exec.stderr.destroy(); + } else { + exec.stdin.write(data === '\r' ? '\r\n' : data) + } + }, + close: () => { + exec.kill('SIGINT'); + exec.stdout.destroy(); + exec.stdin.destroy(); + exec.stderr.destroy(); + }, + }; + }) }); this.storageManager.update(StorageKey.WorkspaceHistory, this.historyManager.workspaceHistory); } From 14fdc55230f3c691cc95c092faaa20367e54c914 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Mon, 25 Nov 2024 23:06:45 -0500 Subject: [PATCH 8/9] Add decoration in settings when setting, variable, input, or runner is selected but not set Signed-off-by: Sanjula Ganepola --- src/act.ts | 2 +- src/settingsManager.ts | 4 ++-- src/views/decorationProvider.ts | 31 +++++++++++++++++++++++-------- src/views/settings/inputs.ts | 4 +++- src/views/settings/runners.ts | 4 +++- src/views/settings/secrets.ts | 4 +++- src/views/settings/setting.ts | 18 +++++++++++++----- src/views/settings/variables.ts | 4 +++- 8 files changed, 51 insertions(+), 20 deletions(-) diff --git a/src/act.ts b/src/act.ts index 5947c2b..8c20e63 100644 --- a/src/act.ts +++ b/src/act.ts @@ -287,7 +287,7 @@ export class Act { `${actCommand} ${commandArgs.options}` + (settings.secrets.length > 0 ? ` ${Option.Secret} ${settings.secrets.map(secret => secret.key).join(` ${Option.Secret} `)}` : ``) + (settings.secretFiles.length > 0 ? ` ${Option.SecretFile} "${settings.secretFiles[0].path}"` : ` ${Option.SecretFile} ""`) + - (settings.variables.length > 0 ? ` ${Option.Variable} ${settings.variables.map(variable => (variable.value ? `${variable.key}=${variable.value}` : variable.key)).join(` ${Option.Variable} `)}` : ``) + + (settings.variables.length > 0 ? ` ${Option.Variable} ${settings.variables.map(variable => `${variable.key}=${variable.value}`).join(` ${Option.Variable} `)}` : ``) + (settings.variableFiles.length > 0 ? ` ${Option.VariableFile} "${settings.variableFiles[0].path}"` : ` ${Option.VariableFile} ""`) + (settings.inputs.length > 0 ? ` ${Option.Input} ${settings.inputs.map(input => `${input.key}=${input.value}`).join(` ${Option.Input} `)}` : ``) + (settings.inputFiles.length > 0 ? ` ${Option.InputFile} "${settings.inputFiles[0].path}"` : ` ${Option.InputFile} ""`) + diff --git a/src/settingsManager.ts b/src/settingsManager.ts index ce85848..b0c3a5d 100644 --- a/src/settingsManager.ts +++ b/src/settingsManager.ts @@ -59,9 +59,9 @@ export class SettingsManager { } async getSettings(workspaceFolder: WorkspaceFolder, isUserSelected: boolean): Promise { - const secrets = (await this.getSetting(workspaceFolder, SettingsManager.secretsRegExp, StorageKey.Secrets, true, Visibility.hide)).filter(secret => !isUserSelected || secret.selected); + const secrets = (await this.getSetting(workspaceFolder, SettingsManager.secretsRegExp, StorageKey.Secrets, true, Visibility.hide)).filter(secret => !isUserSelected || (secret.selected && secret.value)); const secretFiles = (await this.getSettingFiles(workspaceFolder, StorageKey.SecretFiles)).filter(secretFile => !isUserSelected || secretFile.selected); - const variables = (await this.getSetting(workspaceFolder, SettingsManager.variablesRegExp, StorageKey.Variables, false, Visibility.show)).filter(variable => !isUserSelected || variable.selected); + const variables = (await this.getSetting(workspaceFolder, SettingsManager.variablesRegExp, StorageKey.Variables, false, Visibility.show)).filter(variable => !isUserSelected || (variable.selected && variable.value)); const variableFiles = (await this.getSettingFiles(workspaceFolder, StorageKey.VariableFiles)).filter(variableFile => !isUserSelected || variableFile.selected); const inputs = (await this.getSetting(workspaceFolder, SettingsManager.inputsRegExp, StorageKey.Inputs, false, Visibility.show)).filter(input => !isUserSelected || (input.selected && input.value)); const inputFiles = (await this.getSettingFiles(workspaceFolder, StorageKey.InputFiles)).filter(inputFile => !isUserSelected || inputFile.selected); diff --git a/src/views/decorationProvider.ts b/src/views/decorationProvider.ts index a34b56e..a35d255 100644 --- a/src/views/decorationProvider.ts +++ b/src/views/decorationProvider.ts @@ -1,6 +1,11 @@ import { CancellationToken, Event, FileDecoration, FileDecorationProvider, ProviderResult, ThemeColor, Uri } from "vscode"; import { CliStatus, ExtensionStatus } from "../componentsManager"; import ComponentTreeItem from "./components/component"; +import InputsTreeItem from "./settings/inputs"; +import RunnersTreeItem from "./settings/runners"; +import SecretsTreeItem from "./settings/secrets"; +import { SettingContextValue } from "./settings/setting"; +import VariablesTreeItem from "./settings/variables"; import WorkflowTreeItem from "./workflows/workflow"; export class DecorationProvider implements FileDecorationProvider { @@ -37,14 +42,24 @@ export class DecorationProvider implements FileDecorationProvider { color: new ThemeColor('GitHubLocalActions.red') }; } + } else if ([SecretsTreeItem.contextValue, VariablesTreeItem.contextValue, InputsTreeItem.contextValue, RunnersTreeItem.contextValue].includes(uri.scheme)) { + const hasAllValues = params.get('hasAllValues') === 'true'; + + if (!hasAllValues) { + return { + color: new ThemeColor('GitHubLocalActions.red') + }; + } + } else if (Object.values(SettingContextValue).includes(uri.scheme as any)) { + const isSelected = params.get('isSelected') === 'true'; + const hasValue = params.get('hasValue') === 'true'; + + if (isSelected && !hasValue) { + return { + badge: '?', + color: new ThemeColor('GitHubLocalActions.red') + }; + } } - - // else if (uri.scheme === SecretsTreeItem.contextValue || uri.scheme === VariablesTreeItem.contextValue || uri.scheme === InputsTreeItem.contextValue || uri.scheme === RunnersTreeItem.contextValue) { - // const selected = params.get('selected'); - - // return { - // badge: `${selected}` - // }; - // } } } \ No newline at end of file diff --git a/src/views/settings/inputs.ts b/src/views/settings/inputs.ts index 6c1deb9..d4bf749 100644 --- a/src/views/settings/inputs.ts +++ b/src/views/settings/inputs.ts @@ -1,4 +1,4 @@ -import { ThemeIcon, TreeItem, TreeItemCollapsibleState, WorkspaceFolder } from "vscode"; +import { ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri, WorkspaceFolder } from "vscode"; import { act } from "../../extension"; import { Setting, SettingFile } from "../../settingsManager"; import { StorageKey } from "../../storageManager"; @@ -17,6 +17,8 @@ export default class InputsTreeItem extends TreeItem implements GithubLocalActio (selectedInputFiles.length > 0 ? ` + ${selectedInputFiles[0].name}` : ``); this.contextValue = InputsTreeItem.contextValue; this.iconPath = new ThemeIcon('record-keys'); + const hasAllValues = inputs.filter(input => input.selected && input.value === '').length === 0; + this.resourceUri = Uri.parse(`${InputsTreeItem.contextValue}:Inputs?hasAllValues=${hasAllValues}`, true); } async getChildren(): Promise { diff --git a/src/views/settings/runners.ts b/src/views/settings/runners.ts index e345410..31aea9d 100644 --- a/src/views/settings/runners.ts +++ b/src/views/settings/runners.ts @@ -1,4 +1,4 @@ -import { ThemeIcon, TreeItem, TreeItemCollapsibleState, WorkspaceFolder } from "vscode"; +import { ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri, WorkspaceFolder } from "vscode"; import { act } from "../../extension"; import { Setting } from "../../settingsManager"; import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem"; @@ -12,6 +12,8 @@ export default class RunnersTreeItem extends TreeItem implements GithubLocalActi this.description = `${runners.filter(runner => runner.selected).length}/${runners.length}`; this.contextValue = RunnersTreeItem.contextValue; this.iconPath = new ThemeIcon('server-environment'); + const hasAllValues = runners.filter(runner => runner.selected && runner.value === '').length === 0; + this.resourceUri = Uri.parse(`${RunnersTreeItem.contextValue}:Runners?hasAllValues=${hasAllValues}`, true); } async getChildren(): Promise { diff --git a/src/views/settings/secrets.ts b/src/views/settings/secrets.ts index 8259532..3bfda9a 100644 --- a/src/views/settings/secrets.ts +++ b/src/views/settings/secrets.ts @@ -1,4 +1,4 @@ -import { ThemeIcon, TreeItem, TreeItemCollapsibleState, WorkspaceFolder } from "vscode"; +import { ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri, WorkspaceFolder } from "vscode"; import { act } from "../../extension"; import { Setting, SettingFile } from "../../settingsManager"; import { StorageKey } from "../../storageManager"; @@ -17,6 +17,8 @@ export default class SecretsTreeItem extends TreeItem implements GithubLocalActi (selectedSecretFiles.length > 0 ? ` + ${selectedSecretFiles[0].name}` : ``); this.contextValue = SecretsTreeItem.contextValue; this.iconPath = new ThemeIcon('lock'); + const hasAllValues = secrets.filter(secret => secret.selected && secret.value === '').length === 0; + this.resourceUri = Uri.parse(`${SecretsTreeItem.contextValue}:Secrets?hasAllValues=${hasAllValues}`, true); } async getChildren(): Promise { diff --git a/src/views/settings/setting.ts b/src/views/settings/setting.ts index cbdaea6..45df970 100644 --- a/src/views/settings/setting.ts +++ b/src/views/settings/setting.ts @@ -1,8 +1,15 @@ -import { ThemeIcon, TreeItem, TreeItemCheckboxState, TreeItemCollapsibleState, WorkspaceFolder } from "vscode"; +import { ThemeIcon, TreeItem, TreeItemCheckboxState, TreeItemCollapsibleState, Uri, WorkspaceFolder } from "vscode"; import { Setting, Visibility } from "../../settingsManager"; import { StorageKey } from "../../storageManager"; import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem"; +export enum SettingContextValue { + secret = 'githubLocalActions.secret', + variable = 'githubLocalActions.variable', + input = 'githubLocalActions.input', + runner = 'githubLocalActions.runner' +} + export default class SettingTreeItem extends TreeItem implements GithubLocalActionsTreeItem { setting: Setting; storageKey: StorageKey; @@ -19,6 +26,7 @@ export default class SettingTreeItem extends TreeItem implements GithubLocalActi this.contextValue = `${treeItem.contextValue}_${setting.password ? setting.visible : ''}`; this.iconPath = treeItem.iconPath; this.checkboxState = setting.selected ? TreeItemCheckboxState.Checked : TreeItemCheckboxState.Unchecked; + this.resourceUri = Uri.parse(`${treeItem.contextValue}:${setting.key}?isSelected=${setting.selected}&hasValue=${setting.value !== ''}`, true); } static getSecretTreeItem(workspaceFolder: WorkspaceFolder, secret: Setting): SettingTreeItem { @@ -27,7 +35,7 @@ export default class SettingTreeItem extends TreeItem implements GithubLocalActi secret, StorageKey.Secrets, { - contextValue: 'githubLocalActions.secret', + contextValue: SettingContextValue.secret, iconPath: new ThemeIcon('key') } ); @@ -39,7 +47,7 @@ export default class SettingTreeItem extends TreeItem implements GithubLocalActi variable, StorageKey.Variables, { - contextValue: 'githubLocalActions.variable', + contextValue: SettingContextValue.variable, iconPath: new ThemeIcon('symbol-variable') } ); @@ -51,7 +59,7 @@ export default class SettingTreeItem extends TreeItem implements GithubLocalActi input, StorageKey.Inputs, { - contextValue: 'githubLocalActions.input', + contextValue: SettingContextValue.input, iconPath: new ThemeIcon('symbol-parameter') } ); @@ -63,7 +71,7 @@ export default class SettingTreeItem extends TreeItem implements GithubLocalActi runner, StorageKey.Runners, { - contextValue: 'githubLocalActions.runner', + contextValue: SettingContextValue.runner, iconPath: new ThemeIcon('vm-connect') } ); diff --git a/src/views/settings/variables.ts b/src/views/settings/variables.ts index 09b6a8a..3df81f0 100644 --- a/src/views/settings/variables.ts +++ b/src/views/settings/variables.ts @@ -1,4 +1,4 @@ -import { ThemeIcon, TreeItem, TreeItemCollapsibleState, WorkspaceFolder } from "vscode"; +import { ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri, WorkspaceFolder } from "vscode"; import { act } from "../../extension"; import { Setting, SettingFile } from "../../settingsManager"; import { StorageKey } from "../../storageManager"; @@ -17,6 +17,8 @@ export default class VariablesTreeItem extends TreeItem implements GithubLocalAc (selectedVariableFiles.length > 0 ? ` + ${selectedVariableFiles[0].name}` : ``); this.contextValue = VariablesTreeItem.contextValue; this.iconPath = new ThemeIcon('symbol-key'); + const hasAllValues = variables.filter(variable => variable.selected && variable.value === '').length === 0; + this.resourceUri = Uri.parse(`${VariablesTreeItem.contextValue}:Variables?hasAllValues=${hasAllValues}`, true); } async getChildren(): Promise { From bbdb8a42f71703c950b30baaa1443c23db5bb5c0 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Mon, 25 Nov 2024 23:10:00 -0500 Subject: [PATCH 9/9] Fix interrupted exit code Signed-off-by: Sanjula Ganepola --- src/act.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/act.ts b/src/act.ts index 8c20e63..8e4e78b 100644 --- a/src/act.ts +++ b/src/act.ts @@ -394,7 +394,7 @@ export class Act { if (signal === 'SIGINT') { writeEmitter.fire(`\r\nTask interrupted.\r\n`); - closeEmitter.fire(code || 0); + closeEmitter.fire(code || 1); } else { writeEmitter.fire(`\r\nTask exited with exit code ${code}.\r\n`); closeEmitter.fire(code || 0);