diff --git a/src/act.ts b/src/act.ts index f604325..0e6f989 100644 --- a/src/act.ts +++ b/src/act.ts @@ -1,8 +1,10 @@ +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, 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"; @@ -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': @@ -94,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 = { @@ -109,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 = { @@ -123,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 = { @@ -140,29 +146,35 @@ 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; } } }); // 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); } 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,20 +284,18 @@ 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} ""`) + - (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} ""`) + (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,129 @@ 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`); + } } - ) + + 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: shell, + 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); + 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 || 1); + } 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); } 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 ce85848..b161ef5 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); @@ -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/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/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 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 {