From 7f00d126810695f803c7b6a8b719e4962be3da44 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Mon, 25 Nov 2024 17:49:26 -0500 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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); From ecbc4dccb516ec474535cfe964eb157bd10d7a22 Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Tue, 26 Nov 2024 21:47:48 -0500 Subject: [PATCH 6/8] Fix to use default OS shells Signed-off-by: Sanjula Ganepola --- src/act.ts | 40 +++++++++++++------ src/componentsManager.ts | 6 +-- src/settingsManager.ts | 4 +- .../settings/settingsTreeDataProvider.ts | 8 ++-- 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/act.ts b/src/act.ts index 8e4e78b..421e0b8 100644 --- a/src/act.ts +++ b/src/act.ts @@ -2,9 +2,9 @@ import * as childProcess from "child_process"; import * as fs from "fs/promises"; import * as path from "path"; import sanitize from "sanitize-filename"; -import { CustomExecution, EventEmitter, ExtensionContext, Pseudoterminal, ShellExecution, TaskDefinition, TaskGroup, TaskPanelKind, TaskRevealKind, tasks, TaskScope, TerminalDimensions, Uri, window, workspace, WorkspaceFolder } from "vscode"; +import { CustomExecution, env, 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 { ConfigurationManager, Platform, Section } from "./configurationManager"; import { componentsTreeDataProvider, historyTreeDataProvider } from './extension'; import { HistoryManager, HistoryStatus } from './historyManager'; import { SecretManager } from "./secretManager"; @@ -171,9 +171,9 @@ export class Act { }); // Refresh components view after installation - tasks.onDidEndTask(async e => { + tasks.onDidEndTaskProcess(async e => { const taskDefinition = e.execution.task.definition; - if (taskDefinition.type === 'nektos/act installation') { + if (taskDefinition.type === 'nektos/act installation' && e.exitCode === 0) { // Update base act command based on installation method if (taskDefinition.ghCliInstall) { await ConfigurationManager.set(Section.actCommand, Act.githubCliCommand); @@ -190,6 +190,19 @@ export class Act { return ConfigurationManager.get(Section.actCommand) || Act.command; } + private getShell() { + switch (process.platform) { + case Platform.windows: + return 'cmd'; + case Platform.mac: + return 'zsh'; + case Platform.linux: + return 'bash'; + default: + return env.shell; + } + } + async runAllWorkflows(workspaceFolder: WorkspaceFolder) { return await this.runCommand({ path: workspaceFolder.uri.fsPath, @@ -366,13 +379,16 @@ export class Act { 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) + shell: this.getShell(), + env: { + ...process.env, + ...settings.secrets + .filter(secret => secret.value) + .reduce((previousValue, currentValue) => { + previousValue[currentValue.key] = currentValue.value; + return previousValue; + }, {} as Record) + } } ); exec.stdout.on('data', handleIO); @@ -454,7 +470,7 @@ export class Act { problemMatchers: [], runOptions: {}, group: TaskGroup.Build, - execution: new ShellExecution(command) + execution: new ShellExecution(command, { executable: this.getShell() }) }); } } diff --git a/src/componentsManager.ts b/src/componentsManager.ts index acdaaeb..d0cd127 100644 --- a/src/componentsManager.ts +++ b/src/componentsManager.ts @@ -104,7 +104,7 @@ export class ComponentsManager { if (selectedPrebuiltExecutable) { await env.openExternal(Uri.parse(selectedPrebuiltExecutable.link)); - window.showInformationMessage('Unpack and run the executable in the terminal specifying the full path or add it to one of the paths in your PATH environment variable. Once nektos/act is successfully installed, refresh the components view.', 'Refresh').then(async value => { + window.showInformationMessage('Unpack the executable and move it to your desired location. Once nektos/act is successfully installed, add it to your shell\'s PATH and then refresh the components view.', 'Refresh').then(async value => { if (value === 'Refresh') { componentsTreeDataProvider.refresh(); } @@ -112,7 +112,7 @@ export class ComponentsManager { } } else if (selectedInstallationMethod.link) { await env.openExternal(Uri.parse(selectedInstallationMethod.link)); - window.showInformationMessage('Once nektos/act is successfully installed, refresh the components view.', 'Refresh').then(async value => { + window.showInformationMessage('Once nektos/act is successfully installed, add it to your shell\'s PATH and then refresh the components view.', 'Refresh').then(async value => { if (value === 'Refresh') { componentsTreeDataProvider.refresh(); } @@ -162,7 +162,7 @@ export class ComponentsManager { problemMatchers: [], runOptions: {}, group: TaskGroup.Build, - execution: new ShellExecution('sudo dockerd') + execution: new ShellExecution('sudo dockerd', { executable: env.shell }) }); } else { window.showErrorMessage(`Invalid environment: ${process.platform}`, 'Report an Issue').then(async value => { diff --git a/src/settingsManager.ts b/src/settingsManager.ts index b0c3a5d..b161ef5 100644 --- a/src/settingsManager.ts +++ b/src/settingsManager.ts @@ -164,7 +164,7 @@ export class SettingsManager { return environments; } - async createSettingFile(workspaceFolder: WorkspaceFolder, storageKey: StorageKey, settingFileName: string) { + async createSettingFile(workspaceFolder: WorkspaceFolder, storageKey: StorageKey, settingFileName: string, content: string) { const settingFileUri = Uri.file(path.join(workspaceFolder.uri.fsPath, settingFileName)); try { @@ -172,7 +172,7 @@ export class SettingsManager { window.showErrorMessage(`A file or folder named ${settingFileName} already exists at ${workspaceFolder.uri.fsPath}. Please choose another name.`); } catch (error: any) { try { - await workspace.fs.writeFile(settingFileUri, new TextEncoder().encode('')); + await workspace.fs.writeFile(settingFileUri, new TextEncoder().encode(content)); await this.locateSettingFile(workspaceFolder, storageKey, [settingFileUri]); const document = await workspace.openTextDocument(settingFileUri); await window.showTextDocument(document); diff --git a/src/views/settings/settingsTreeDataProvider.ts b/src/views/settings/settingsTreeDataProvider.ts index 8a90368..73ec5da 100644 --- a/src/views/settings/settingsTreeDataProvider.ts +++ b/src/views/settings/settingsTreeDataProvider.ts @@ -29,7 +29,7 @@ export default class SettingsTreeDataProvider implements TreeDataProvider Date: Tue, 26 Nov 2024 22:11:21 -0500 Subject: [PATCH 7/8] Use hardcoded shells for act commands Signed-off-by: Sanjula Ganepola --- src/act.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/act.ts b/src/act.ts index 421e0b8..a353fb4 100644 --- a/src/act.ts +++ b/src/act.ts @@ -190,19 +190,6 @@ export class Act { return ConfigurationManager.get(Section.actCommand) || Act.command; } - private getShell() { - switch (process.platform) { - case Platform.windows: - return 'cmd'; - case Platform.mac: - return 'zsh'; - case Platform.linux: - return 'bash'; - default: - return env.shell; - } - } - async runAllWorkflows(workspaceFolder: WorkspaceFolder) { return await this.runCommand({ path: workspaceFolder.uri.fsPath, @@ -375,11 +362,24 @@ export class Act { } } + let shell = env.shell; + switch (process.platform) { + case Platform.windows: + shell = 'cmd'; + break; + case Platform.mac: + shell = 'zsh'; + break; + case Platform.linux: + shell = 'bash'; + break; + } + const exec = childProcess.spawn( command, { cwd: commandArgs.path, - shell: this.getShell(), + shell: shell, env: { ...process.env, ...settings.secrets @@ -470,7 +470,7 @@ export class Act { problemMatchers: [], runOptions: {}, group: TaskGroup.Build, - execution: new ShellExecution(command, { executable: this.getShell() }) + execution: new ShellExecution(command) }); } } From fbd551adea57434a473ec7399d88213cc77a86dd Mon Sep 17 00:00:00 2001 From: Sanjula Ganepola Date: Tue, 26 Nov 2024 22:11:45 -0500 Subject: [PATCH 8/8] fix gh command Signed-off-by: Sanjula Ganepola --- src/act.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/act.ts b/src/act.ts index a353fb4..0e6f989 100644 --- a/src/act.ts +++ b/src/act.ts @@ -100,7 +100,7 @@ export class Act { 'Chocolatey': 'choco install act-cli', 'Winget': 'winget install nektos.act', 'Scoop': 'scoop install act', - 'GitHub CLI': 'gh auth status || gh auth login && 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 = { @@ -115,7 +115,7 @@ export class Act { 'Homebrew': 'brew install act', 'Nix': 'nix run nixpkgs#act', 'MacPorts': 'sudo port install act', - 'GitHub CLI': 'gh auth status || gh auth login && 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 = { @@ -129,7 +129,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 auth status || gh auth login && 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 = {