diff --git a/src/act.ts b/src/act.ts index 91ee94b..0277b2c 100644 --- a/src/act.ts +++ b/src/act.ts @@ -47,11 +47,11 @@ export enum Event { export enum Option { ActionCachePath = '--action-cache-path', ActionOfflineMode = '--action-offline-mode', - Actor = '--actor', + Actor = '--a', ArtifactServerAddr = '--artifact-server-addr', ArtifactServerPath = '--artifact-server-path', ArtifactServerPort = '--artifact-server-port', - Bind = '--bind', + Bind = '--b', BugReport = '--bug-report', CacheServerAddr = '--cache-server-addr', CacheServerPath = '--cache-server-path', @@ -63,20 +63,20 @@ export enum Option { ContainerOptions = '--container-options', DefaultBranch = '--defaultbranch', DetectEvent = '--detect-event', - Directory = '--directory', - DryRun = '--dryrun', + Directory = '--C', + DryRun = '--n', Env = '--env', EnvFile = '--env-file', - EventPath = '--eventpath', + EventPath = '--e', GitHubInstance = '--github-instance', - Graph = '--graph', - Help = '--help', + Graph = '--g', + Help = '--h', Input = '--input', InputFile = '--input-file', InsecureSecrets = '--insecure-secrets', - Job = '--job', + Job = '--j', Json = '--json', - List = '--list', + List = '--l', LocalRepository = '--local-repository', LogPrefixJobId = '--log-prefix-job-id', ManPage = '--man-page', @@ -85,15 +85,15 @@ export enum Option { NoCacheServer = '--no-cache-server', NoRecurse = '--no-recurse', NoSkipCheckout = '--no-skip-checkout', - Platform = '--platform', + Platform = '--P', Privileged = '--privileged', - Pull = '--pull', - Quiet = '--quiet', + Pull = '--p', + Quiet = '--q', Rebuild = '--rebuild', RemoteName = '--remote-name', ReplaceGHEActionTokenWithGitHubCom = '--replace-ghe-action-token-with-github-com', ReplaceGHEActionWithGitHubCom = '--replace-ghe-action-with-github-com', - Reuse = '--reuse', + Reuse = '--r', Rm = '--rm', Secret = '--secret', SecretFile = '--secret-file', @@ -102,17 +102,17 @@ export enum Option { Userns = '--userns', Var = '--var', VarFile = '--var-file', - Verbose = '--verbose', + Verbose = '--v', Version = '--version', - Watch = '--watch', - Workflows = '--workflows' + Watch = '--w', + Workflows = '--W' } export interface CommandArgs { workspaceFolder: WorkspaceFolder, options: string, name: string, - typeText: string[] + extraHeader: { key: string, value: string }[] } export class Act { @@ -189,7 +189,7 @@ export class Act { workspaceFolder: workspaceFolder, options: ``, name: workspaceFolder.name, - typeText: [] + extraHeader: [] }); } @@ -198,8 +198,8 @@ export class Act { workspaceFolder: workspaceFolder, options: `${Option.Workflows} ".github/workflows/${path.parse(workflow.uri.fsPath).base}"`, name: workflow.name, - typeText: [ - `Workflow: ${workflow.name}` + extraHeader: [ + { key: 'Workflow', value: workflow.name } ] }); } @@ -209,9 +209,9 @@ export class Act { workspaceFolder: workspaceFolder, options: `${Option.Workflows} ".github/workflows/${path.parse(workflow.uri.fsPath).base}" ${Option.Job} "${job.id}"`, name: `${workflow.name}/${job.name}`, - typeText: [ - `Workflow: ${workflow.name}`, - `Job: ${job.name}` + extraHeader: [ + { key: 'Workflow', value: workflow.name }, + { key: 'Job', value: job.name } ] }); } @@ -221,15 +221,13 @@ export class Act { workspaceFolder: workspaceFolder, options: event, name: event, - typeText: [ - `Event: ${event}` + extraHeader: [ + { key: 'Event', value: event } ] }); } async runCommand(commandArgs: CommandArgs) { - const command = `${Act.base} ${Option.Json} ${commandArgs.options}`; - const unreadyComponents = await this.componentsManager.getUnreadyComponents(); if (unreadyComponents.length > 0) { window.showErrorMessage(`The following required components are not ready: ${unreadyComponents.map(component => component.name).join(', ')}`, 'Fix...').then(async value => { @@ -240,12 +238,6 @@ export class Act { return; } - if (!this.historyManager.workspaceHistory[commandArgs.workspaceFolder.uri.fsPath]) { - this.historyManager.workspaceHistory[commandArgs.workspaceFolder.uri.fsPath] = []; - this.storageManager.update(StorageKey.WorkspaceHistory, this.historyManager.workspaceHistory); - } - - const historyIndex = this.historyManager.workspaceHistory[commandArgs.workspaceFolder.uri.fsPath].length; const taskExecution = await tasks.executeTask({ name: commandArgs.name, detail: 'Run workflow', @@ -266,6 +258,48 @@ export class Act { runOptions: {}, group: TaskGroup.Build, execution: new CustomExecution(async (resolvedDefinition: TaskDefinition): Promise => { + const environments = await this.settingsManager.getEnvironments(commandArgs.workspaceFolder); + const secrets = (await this.settingsManager.getSetting(commandArgs.workspaceFolder, SettingsManager.secretsRegExp, StorageKey.Secrets)).filter(secret => secret.selected && secret.value); + const variables = (await this.settingsManager.getSetting(commandArgs.workspaceFolder, SettingsManager.variablesRegExp, StorageKey.Variables)).filter(variable => variable.selected && variable.value); + const inputs = (await this.settingsManager.getSetting(commandArgs.workspaceFolder, SettingsManager.inputsRegExp, StorageKey.Inputs)).filter(input => input.selected && input.value); + + + + // TODO: Fix secrets, variables, and inputs in below command + // How to pass in secrets + // Is there any point to show environments in the header? Is it needed in the tree view? + + + const command = `${Act.base} ${Option.Json} ${commandArgs.options}`; + + + + + + + + + + + + + + const historyIndex = this.historyManager.workspaceHistory[commandArgs.workspaceFolder.uri.fsPath].length; + if (!this.historyManager.workspaceHistory[commandArgs.workspaceFolder.uri.fsPath]) { + this.historyManager.workspaceHistory[commandArgs.workspaceFolder.uri.fsPath] = []; + this.storageManager.update(StorageKey.WorkspaceHistory, this.historyManager.workspaceHistory); + } + + this.historyManager.workspaceHistory[commandArgs.workspaceFolder.uri.fsPath].push({ + index: historyIndex, + name: `${commandArgs.name} #${this.historyManager.workspaceHistory[commandArgs.workspaceFolder.uri.fsPath].length + 1}`, + status: HistoryStatus.Running, + taskExecution: taskExecution, + commandArgs: commandArgs + }); + historyTreeDataProvider.refresh(); + this.storageManager.update(StorageKey.WorkspaceHistory, this.historyManager.workspaceHistory); + const writeEmitter = new EventEmitter(); const closeEmitter = new EventEmitter(); @@ -292,18 +326,6 @@ export class Act { } } const handleIO = (data: any) => { - if (typeof this.historyManager.workspaceHistory[commandArgs.workspaceFolder.uri.fsPath][historyIndex] === 'undefined') { - this.historyManager.workspaceHistory[commandArgs.workspaceFolder.uri.fsPath].push({ - index: historyIndex, - name: `${commandArgs.name} #${this.historyManager.workspaceHistory[commandArgs.workspaceFolder.uri.fsPath].length + 1}`, - status: HistoryStatus.Running, - taskExecution: taskExecution, - commandArgs: commandArgs - }); - historyTreeDataProvider.refresh(); - this.storageManager.update(StorageKey.WorkspaceHistory, this.historyManager.workspaceHistory); - } - const lines: string[] = data.toString().split('\n').filter((line: string) => line != ''); for (const line of lines) { let jsonLine: any; @@ -351,16 +373,36 @@ export class Act { onDidWrite: writeEmitter.event, onDidClose: closeEmitter.event, open: async (initialDimensions: TerminalDimensions | undefined): Promise => { - writeEmitter.fire(`Name: ${commandArgs.name}\r\n`); - writeEmitter.fire(`Path: ${commandArgs.workspaceFolder.uri.fsPath}\r\n`); - for (const text of commandArgs.typeText) { - writeEmitter.fire(`${text}\r\n`); + let headerText: string = ''; + const addMultipleEntries = (key: string, values: string[]): { key: string, value: string }[] => { + if (values.length === 0) return []; + return values.map((value, index) => ({ + key: index === 0 ? key : '', // Show the key only for the first entry + value + })); + }; + + const header: { key: string, value: string }[] = [ + { key: 'Name', value: commandArgs.name }, + { key: 'Path', value: commandArgs.workspaceFolder.uri.fsPath }, + ...commandArgs.extraHeader, + ...addMultipleEntries('Environments', environments.map(env => env.name)), + ...addMultipleEntries('Variables', variables.map(variable => `${variable.key}=${variable.value}`)), + ...addMultipleEntries('Secrets', secrets.map(secret => `${secret.key}=••••••••`)), + ...addMultipleEntries('Inputs', inputs.map(input => `${input.key}=${input.value}`)), + { key: 'Command', value: command.replace(` ${Option.Json}`, ``) } + ]; + + const maxKeyLength = Math.max(...header.map(item => item.key.length)); + for (const { key, value } of header) { + const spaces = ' '.repeat(maxKeyLength - key.length + 1); + headerText += key ? `${key}:${spaces}${value}\r\n` : ` ${spaces}${value}\r\n`; } - writeEmitter.fire(`Environments: OSSBUILD\r\n`); - writeEmitter.fire(`Variables: VARIABLE1=ABC, VARIABLE2=DEF\r\n`); - writeEmitter.fire(`Secrets: SECRET1=ABC, SECRET2=DEF\r\n`); - writeEmitter.fire(`Command: ${command.replace(` ${Option.Json}`, ``)}\r\n`); - writeEmitter.fire(`\r\n`); + + const maxValueLength = Math.max(...header.map(item => item.value.length)); + const borderText = '-'.repeat(maxKeyLength + 2 + maxValueLength); + + writeEmitter.fire(`${borderText}\r\n${headerText}${borderText}\r\n\r\n`); }, close: () => { diff --git a/src/extension.ts b/src/extension.ts index 7f81e9a..dd27e28 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,10 +1,14 @@ import * as vscode from 'vscode'; -import { window, workspace } from 'vscode'; +import { TreeCheckboxChangeEvent, window, workspace } from 'vscode'; import { Act } from './act'; import ComponentsTreeDataProvider from './views/components/componentsTreeDataProvider'; import { DecorationProvider } from './views/decorationProvider'; +import { GithubLocalActionsTreeItem } from './views/githubLocalActionsTreeItem'; import HistoryTreeDataProvider from './views/history/historyTreeDataProvider'; +import InputTreeItem from './views/settings/input'; +import SecretTreeItem from './views/settings/secret'; import SettingsTreeDataProvider from './views/settings/settingsTreeDataProvider'; +import VariableTreeItem from './views/settings/variable'; import WorkflowsTreeDataProvider from './views/workflows/workflowsTreeDataProvider'; export let act: Act; @@ -28,6 +32,9 @@ export function activate(context: vscode.ExtensionContext) { const historyTreeView = window.createTreeView(HistoryTreeDataProvider.VIEW_ID, { treeDataProvider: historyTreeDataProvider }); settingsTreeDataProvider = new SettingsTreeDataProvider(context); const settingsTreeView = window.createTreeView(SettingsTreeDataProvider.VIEW_ID, { treeDataProvider: settingsTreeDataProvider }); + settingsTreeView.onDidChangeCheckboxState(async (event: TreeCheckboxChangeEvent) => { + await settingsTreeDataProvider.onDidChangeCheckboxState(event as TreeCheckboxChangeEvent); + }); // Create file watcher const workflowsFileWatcher = workspace.createFileSystemWatcher('**/.github/workflows/*.{yml,yaml}'); diff --git a/src/settingsManager.ts b/src/settingsManager.ts index c2ffe3f..7c7853e 100644 --- a/src/settingsManager.ts +++ b/src/settingsManager.ts @@ -32,6 +32,9 @@ export interface Runner { export class SettingsManager { storageManager: StorageManager; + static secretsRegExp: RegExp = /\${{\s*secrets\.(.*?)\s*}}/g; + static variablesRegExp: RegExp = /\${{\s*vars\.(.*?)(?:\s*==\s*(.*?))?\s*}}/g; + static inputsRegExp: RegExp = /\${{\s*(?:inputs|github\.event\.inputs)\.(.*?)(?:\s*==\s*(.*?))?\s*}}/g; constructor(storageManager: StorageManager) { this.storageManager = storageManager; @@ -63,7 +66,7 @@ export class SettingsManager { return environments; } - + async getSetting(workspaceFolder: WorkspaceFolder, regExp: RegExp, storageKey: StorageKey): Promise { const existingSettings = this.storageManager.get<{ [path: string]: T[] }>(storageKey) || {}; @@ -76,7 +79,13 @@ export class SettingsManager { continue; } - settings.push(...this.findInWorkflow(workflow.fileContent, regExp)); + const workflowSettings = this.findInWorkflow(workflow.fileContent, regExp); + for (const workflowSetting of workflowSettings) { + const existingSetting = settings.find(setting => setting.key === workflowSetting.key); + if (!existingSetting) { + settings.push(workflowSetting); + } + } } if (existingSettings[workspaceFolder.uri.fsPath]) { @@ -97,7 +106,7 @@ export class SettingsManager { return settings; } - editSetting(workspaceFolder: WorkspaceFolder, newSetting: T, storageKey: StorageKey) { + async editSetting(workspaceFolder: WorkspaceFolder, newSetting: T, storageKey: StorageKey) { const existingSettings = this.storageManager.get<{ [path: string]: T[] }>(storageKey) || {}; if (existingSettings[workspaceFolder.uri.fsPath]) { const index = existingSettings[workspaceFolder.uri.fsPath].findIndex(setting => setting.key === newSetting.key); @@ -110,7 +119,7 @@ export class SettingsManager { existingSettings[workspaceFolder.uri.fsPath] = [newSetting]; } - this.storageManager.update(storageKey, existingSettings); + await this.storageManager.update(storageKey, existingSettings); } private findInWorkflow(content: string, regExp: RegExp) { diff --git a/src/views/settings/inputs.ts b/src/views/settings/inputs.ts index f6fb1de..b26a0be 100644 --- a/src/views/settings/inputs.ts +++ b/src/views/settings/inputs.ts @@ -1,5 +1,6 @@ import { ThemeIcon, TreeItem, TreeItemCollapsibleState, WorkspaceFolder } from "vscode"; import { act } from "../../extension"; +import { SettingsManager } from "../../settingsManager"; import { StorageKey } from "../../storageManager"; import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem"; import InputTreeItem from "./input"; @@ -16,7 +17,7 @@ export default class InputsTreeItem extends TreeItem implements GithubLocalActio async getChildren(): Promise { const items: GithubLocalActionsTreeItem[] = []; - const inputs = await act.settingsManager.getSetting(this.workspaceFolder, /\${{\s*(?:inputs|github\.event\.inputs)\.(.*?)(?:\s*==\s*(.*?))?\s*}}/g, StorageKey.Inputs); + const inputs = await act.settingsManager.getSetting(this.workspaceFolder, SettingsManager.inputsRegExp, StorageKey.Inputs); for (const input of inputs) { items.push(new InputTreeItem(this.workspaceFolder, input)); } diff --git a/src/views/settings/secrets.ts b/src/views/settings/secrets.ts index a87a145..7aa1369 100644 --- a/src/views/settings/secrets.ts +++ b/src/views/settings/secrets.ts @@ -1,5 +1,6 @@ import { ThemeIcon, TreeItem, TreeItemCollapsibleState, WorkspaceFolder } from "vscode"; import { act } from "../../extension"; +import { SettingsManager } from "../../settingsManager"; import { StorageKey } from "../../storageManager"; import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem"; import SecretTreeItem from "./secret"; @@ -16,7 +17,7 @@ export default class SecretsTreeItem extends TreeItem implements GithubLocalActi async getChildren(): Promise { const items: GithubLocalActionsTreeItem[] = []; - const secrets = await act.settingsManager.getSetting(this.workspaceFolder, /\${{\s*secrets\.(.*?)\s*}}/g, StorageKey.Secrets); + const secrets = await act.settingsManager.getSetting(this.workspaceFolder, SettingsManager.secretsRegExp, StorageKey.Secrets); for (const secret of secrets) { items.push(new SecretTreeItem(this.workspaceFolder, secret)); } diff --git a/src/views/settings/settingsTreeDataProvider.ts b/src/views/settings/settingsTreeDataProvider.ts index f84531e..226e3a8 100644 --- a/src/views/settings/settingsTreeDataProvider.ts +++ b/src/views/settings/settingsTreeDataProvider.ts @@ -1,4 +1,4 @@ -import { CancellationToken, commands, EventEmitter, ExtensionContext, TreeDataProvider, TreeItem, window, workspace } from "vscode"; +import { CancellationToken, commands, EventEmitter, ExtensionContext, TreeCheckboxChangeEvent, TreeDataProvider, TreeItem, TreeItemCheckboxState, window, workspace } from "vscode"; import { act } from "../../extension"; import { StorageKey } from "../../storageManager"; import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem"; @@ -26,8 +26,8 @@ export default class SettingsTreeDataProvider implements TreeDataProvider) { + for await (const [treeItem, state] of event.items) { + if (treeItem instanceof SecretTreeItem) { + await act.settingsManager.editSetting(treeItem.workspaceFolder, { key: treeItem.secret.key, value: treeItem.secret.value, selected: state === TreeItemCheckboxState.Checked }, StorageKey.Secrets); + } else if (treeItem instanceof VariableTreeItem) { + await act.settingsManager.editSetting(treeItem.workspaceFolder, { key: treeItem.variable.key, value: treeItem.variable.value, selected: state === TreeItemCheckboxState.Checked }, StorageKey.Variables); + } else { + await act.settingsManager.editSetting(treeItem.workspaceFolder, { key: treeItem.input.key, value: treeItem.input.value, selected: state === TreeItemCheckboxState.Checked }, StorageKey.Inputs); + } + } + } + async getChildren(element?: GithubLocalActionsTreeItem): Promise { if (element) { return element.getChildren(); diff --git a/src/views/settings/variables.ts b/src/views/settings/variables.ts index 2de8e1c..fe6b1cf 100644 --- a/src/views/settings/variables.ts +++ b/src/views/settings/variables.ts @@ -1,5 +1,6 @@ import { ThemeIcon, TreeItem, TreeItemCollapsibleState, WorkspaceFolder } from "vscode"; import { act } from "../../extension"; +import { SettingsManager } from "../../settingsManager"; import { StorageKey } from "../../storageManager"; import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem"; import VariableTreeItem from "./variable"; @@ -16,7 +17,7 @@ export default class VariablesTreeItem extends TreeItem implements GithubLocalAc async getChildren(): Promise { const items: GithubLocalActionsTreeItem[] = []; - const variables = await act.settingsManager.getSetting(this.workspaceFolder, /\${{\s*vars\.(.*?)(?:\s*==\s*(.*?))?\s*}}/g, StorageKey.Variables); + const variables = await act.settingsManager.getSetting(this.workspaceFolder, SettingsManager.variablesRegExp, StorageKey.Variables); for (const variable of variables) { items.push(new VariableTreeItem(this.workspaceFolder, variable)); }