Merge pull request #64 from SanjulaGanepola/feature/input-variable-secret-file

Support secrets, variable, and inputs files
This commit is contained in:
Sanjula Ganepola
2024-11-22 21:23:12 -05:00
committed by GitHub
15 changed files with 554 additions and 68 deletions

View File

@@ -240,6 +240,18 @@
"title": "Refresh",
"icon": "$(refresh)"
},
{
"category": "GitHub Local Actions",
"command": "githubLocalActions.createSecretFile",
"title": "Create Secret File",
"icon": "$(add)"
},
{
"category": "GitHub Local Actions",
"command": "githubLocalActions.locateSecretFiles",
"title": "Locate Secret Files",
"icon": "$(search)"
},
{
"category": "GitHub Local Actions",
"command": "githubLocalActions.show",
@@ -252,12 +264,54 @@
"title": "Hide",
"icon": "$(eye-closed)"
},
{
"category": "GitHub Local Actions",
"command": "githubLocalActions.createVariableFile",
"title": "Create Variable File",
"icon": "$(add)"
},
{
"category": "GitHub Local Actions",
"command": "githubLocalActions.locateVariableFiles",
"title": "Locate Variable Files",
"icon": "$(search)"
},
{
"category": "GitHub Local Actions",
"command": "githubLocalActions.createInputFile",
"title": "Create Input File",
"icon": "$(add)"
},
{
"category": "GitHub Local Actions",
"command": "githubLocalActions.locateInputFiles",
"title": "Locate Input Files",
"icon": "$(search)"
},
{
"category": "GitHub Local Actions",
"command": "githubLocalActions.importFromGithub",
"title": "Import from GitHub",
"icon": "$(github)"
},
{
"category": "GitHub Local Actions",
"command": "githubLocalActions.openSettingFile",
"title": "Open",
"icon": "$(go-to-file)"
},
{
"category": "GitHub Local Actions",
"command": "githubLocalActions.removeSettingFile",
"title": "Remove",
"icon": "$(close)"
},
{
"category": "GitHub Local Actions",
"command": "githubLocalActions.deleteSettingFile",
"title": "Delete",
"icon": "$(trash)"
},
{
"category": "GitHub Local Actions",
"command": "githubLocalActions.editSetting",
@@ -352,6 +406,14 @@
"command": "githubLocalActions.refreshSettings",
"when": "never"
},
{
"command": "githubLocalActions.createSecretFile",
"when": "never"
},
{
"command": "githubLocalActions.locateSecretFiles",
"when": "never"
},
{
"command": "githubLocalActions.show",
"when": "never"
@@ -360,10 +422,38 @@
"command": "githubLocalActions.hide",
"when": "never"
},
{
"command": "githubLocalActions.createVariableFile",
"when": "never"
},
{
"command": "githubLocalActions.locateVariableFiles",
"when": "never"
},
{
"command": "githubLocalActions.createInputFile",
"when": "never"
},
{
"command": "githubLocalActions.locateInputFiles",
"when": "never"
},
{
"command": "githubLocalActions.importFromGithub",
"when": "never"
},
{
"command": "githubLocalActions.openSettingFile",
"when": "never"
},
{
"command": "githubLocalActions.removeSettingFile",
"when": "never"
},
{
"command": "githubLocalActions.deleteSettingFile",
"when": "never"
},
{
"command": "githubLocalActions.editSetting",
"when": "never"
@@ -495,6 +585,16 @@
"when": "view == history && viewItem =~ /^githubLocalActions.history.*/",
"group": "inline@3"
},
{
"command": "githubLocalActions.createSecretFile",
"when": "view == settings && viewItem =~ /^githubLocalActions.secrets.*/",
"group": "inline@0"
},
{
"command": "githubLocalActions.locateSecretFiles",
"when": "view == settings && viewItem =~ /^githubLocalActions.secrets.*/",
"group": "inline@1"
},
{
"command": "githubLocalActions.show",
"when": "view == settings && viewItem =~ /^githubLocalActions.secret(?!s)_hide.*/",
@@ -506,13 +606,48 @@
"group": "inline@0"
},
{
"command": "githubLocalActions.importFromGithub",
"command": "githubLocalActions.createVariableFile",
"when": "view == settings && viewItem =~ /^githubLocalActions.variables.*/",
"group": "inline@0"
},
{
"command": "githubLocalActions.locateVariableFiles",
"when": "view == settings && viewItem =~ /^githubLocalActions.variables.*/",
"group": "inline@1"
},
{
"command": "githubLocalActions.createInputFile",
"when": "view == settings && viewItem =~ /^githubLocalActions.inputs.*/",
"group": "inline@0"
},
{
"command": "githubLocalActions.locateInputFiles",
"when": "view == settings && viewItem =~ /^githubLocalActions.inputs.*/",
"group": "inline@1"
},
{
"command": "githubLocalActions.importFromGithub",
"when": "view == settings && viewItem =~ /^githubLocalActions.variables.*/",
"group": "inline@2"
},
{
"command": "githubLocalActions.openSettingFile",
"when": "view == settings && viewItem =~ /^githubLocalActions.(secret|variable|input)File.*/",
"group": "inline@0"
},
{
"command": "githubLocalActions.removeSettingFile",
"when": "view == settings && viewItem =~ /^githubLocalActions.(secret|variable|input)File.*/",
"group": "inline@1"
},
{
"command": "githubLocalActions.deleteSettingFile",
"when": "view == settings && viewItem =~ /^githubLocalActions.(secret|variable|input)File.*/",
"group": "inline@2"
},
{
"command": "githubLocalActions.editSetting",
"when": "view == settings && viewItem =~ /^githubLocalActions.(secret|variable|input|runner)(?!s).*/",
"when": "view == settings && viewItem =~ /^githubLocalActions.(secret|variable|input|runner)(?!(File|s)).*/",
"group": "inline@1"
}
]

View File

@@ -46,16 +46,19 @@ export enum Event {
}
export enum Option {
Input = '--input',
Workflows = '-W',
Job = '-j',
Platform = '-P',
Secret = '--secret',
SecretFile = '--secret-file',
Variable = '--var',
Workflows = '-W'
VariableFile = '--var-file',
Input = '--input',
InputFile = '--input-file'
}
export interface CommandArgs {
fsPath: string,
path: string,
options: string,
name: string,
extraHeader: { key: string, value: string }[]
@@ -168,7 +171,7 @@ export class Act {
const historyIndex = taskDefinition.historyIndex;
// Add new entry to workspace history
this.historyManager.workspaceHistory[commandArgs.fsPath].push({
this.historyManager.workspaceHistory[commandArgs.path].push({
index: historyIndex,
count: taskDefinition.count,
name: `${commandArgs.name}`,
@@ -191,18 +194,18 @@ export class Act {
const historyIndex = taskDefinition.historyIndex;
// Set end status
if (this.historyManager.workspaceHistory[commandArgs.fsPath][historyIndex].status === HistoryStatus.Running) {
if (this.historyManager.workspaceHistory[commandArgs.path][historyIndex].status === HistoryStatus.Running) {
if (e.exitCode === 0) {
this.historyManager.workspaceHistory[commandArgs.fsPath][historyIndex].status = HistoryStatus.Success;
this.historyManager.workspaceHistory[commandArgs.path][historyIndex].status = HistoryStatus.Success;
} else if (!e.exitCode) {
this.historyManager.workspaceHistory[commandArgs.fsPath][historyIndex].status = HistoryStatus.Cancelled;
this.historyManager.workspaceHistory[commandArgs.path][historyIndex].status = HistoryStatus.Cancelled;
} else {
this.historyManager.workspaceHistory[commandArgs.fsPath][historyIndex].status = HistoryStatus.Failed;
this.historyManager.workspaceHistory[commandArgs.path][historyIndex].status = HistoryStatus.Failed;
}
}
// Set end time
this.historyManager.workspaceHistory[commandArgs.fsPath][historyIndex].date.end = new Date().toString();
this.historyManager.workspaceHistory[commandArgs.path][historyIndex].date.end = new Date().toString();
historyTreeDataProvider.refresh();
this.storageManager.update(StorageKey.WorkspaceHistory, this.historyManager.workspaceHistory);
@@ -212,7 +215,7 @@ export class Act {
async runAllWorkflows(workspaceFolder: WorkspaceFolder) {
return await this.runCommand({
fsPath: workspaceFolder.uri.fsPath,
path: workspaceFolder.uri.fsPath,
options: ``,
name: workspaceFolder.name,
extraHeader: []
@@ -221,7 +224,7 @@ export class Act {
async runWorkflow(workspaceFolder: WorkspaceFolder, workflow: Workflow) {
return await this.runCommand({
fsPath: workspaceFolder.uri.fsPath,
path: workspaceFolder.uri.fsPath,
options: `${Option.Workflows} ".github/workflows/${path.parse(workflow.uri.fsPath).base}"`,
name: workflow.name,
extraHeader: [
@@ -232,7 +235,7 @@ export class Act {
async runJob(workspaceFolder: WorkspaceFolder, workflow: Workflow, job: Job) {
return await this.runCommand({
fsPath: workspaceFolder.uri.fsPath,
path: workspaceFolder.uri.fsPath,
options: `${Option.Workflows} ".github/workflows/${path.parse(workflow.uri.fsPath).base}" ${Option.Job} "${job.id}"`,
name: `${workflow.name}/${job.name}`,
extraHeader: [
@@ -244,7 +247,7 @@ export class Act {
async runEvent(workspaceFolder: WorkspaceFolder, event: Event) {
return await this.runCommand({
fsPath: workspaceFolder.uri.fsPath,
path: workspaceFolder.uri.fsPath,
options: event,
name: event,
extraHeader: [
@@ -266,21 +269,21 @@ export class Act {
// }
// Map to workspace folder
const workspaceFolder = workspace.getWorkspaceFolder(Uri.file(commandArgs.fsPath));
const workspaceFolder = workspace.getWorkspaceFolder(Uri.file(commandArgs.path));
if (!workspaceFolder) {
window.showErrorMessage(`Failed to locate workspace folder for ${commandArgs.fsPath}`);
window.showErrorMessage(`Failed to locate workspace folder for ${commandArgs.path}`);
return;
}
// Initialize history for workspace
if (!this.historyManager.workspaceHistory[commandArgs.fsPath]) {
this.historyManager.workspaceHistory[commandArgs.fsPath] = [];
if (!this.historyManager.workspaceHistory[commandArgs.path]) {
this.historyManager.workspaceHistory[commandArgs.path] = [];
this.storageManager.update(StorageKey.WorkspaceHistory, this.historyManager.workspaceHistory);
}
// Process task count suffix
const historyIndex = this.historyManager.workspaceHistory[commandArgs.fsPath].length;
const matchingTasks = this.historyManager.workspaceHistory[commandArgs.fsPath]
const historyIndex = this.historyManager.workspaceHistory[commandArgs.path].length;
const matchingTasks = this.historyManager.workspaceHistory[commandArgs.path]
.filter(history => history.name === commandArgs.name)
.sort((a, b) => b.count - a.count);
const count = matchingTasks && matchingTasks.length > 0 ? matchingTasks[0].count + 1 : 1;
@@ -298,7 +301,7 @@ export class Act {
try {
await workspace.fs.createDirectory(this.context.globalStorageUri);
} catch (error) { }
} catch (error: any) { }
// Build command with settings
const settings = await this.settingsManager.getSettings(workspaceFolder, true);
@@ -306,8 +309,11 @@ export class Act {
`set -o pipefail; ` +
`${Act.base} ${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.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} `)}` : ``) +
` 2>&1 | tee "${logPath}"`;
@@ -341,7 +347,7 @@ export class Act {
execution: new ShellExecution(
command,
{
cwd: commandArgs.fsPath,
cwd: commandArgs.path,
env: settings.secrets
.filter(secret => secret.value)
.reduce((previousValue, currentValue) => {

View File

@@ -147,7 +147,7 @@ export class GitHubManager {
private async getSession(): Promise<AuthenticationSession | undefined> {
try {
return await authentication.getSession('github', ['repo'], { createIfNone: true });
} catch (error) {
} catch (error: any) {
window.showErrorMessage(`Failed to authenticate to GitHub. Error ${error}`);
return;
}

View File

@@ -48,7 +48,7 @@ export class HistoryManager {
for (const history of existingHistory) {
try {
await workspace.fs.delete(Uri.file(history.logPath));
} catch (error) { }
} catch (error: any) { }
}
this.workspaceHistory[workspaceFolder.uri.fsPath] = [];
@@ -60,7 +60,7 @@ export class HistoryManager {
try {
const document = await workspace.openTextDocument(history.logPath);
await window.showTextDocument(document);
} catch (error) {
} catch (error: any) {
window.showErrorMessage(`${history.name} #${history.count} log file not found`);
}
}
@@ -74,12 +74,14 @@ export class HistoryManager {
}
async remove(history: History) {
const historyIndex = this.workspaceHistory[history.commandArgs.fsPath].findIndex(workspaceHistory => workspaceHistory.index === history.index);
this.workspaceHistory[history.commandArgs.fsPath].splice(historyIndex, 1);
this.storageManager.update(StorageKey.WorkspaceHistory, this.workspaceHistory);
const historyIndex = this.workspaceHistory[history.commandArgs.path].findIndex(workspaceHistory => workspaceHistory.index === history.index);
if (historyIndex > -1) {
this.workspaceHistory[history.commandArgs.path].splice(historyIndex, 1);
this.storageManager.update(StorageKey.WorkspaceHistory, this.workspaceHistory);
try {
await workspace.fs.delete(Uri.file(history.logPath));
} catch (error) { }
try {
await workspace.fs.delete(Uri.file(history.logPath));
} catch (error: any) { }
}
}
}

View File

@@ -1,9 +1,21 @@
import { WorkspaceFolder } from "vscode";
import * as path from "path";
import { Uri, window, workspace, WorkspaceFolder } from "vscode";
import { act } from "./extension";
import { GitHubManager } from "./githubManager";
import { SecretManager } from "./secretManager";
import { StorageKey, StorageManager } from "./storageManager";
export interface Settings {
secrets: Setting[];
secretFiles: SettingFile[];
variables: Setting[];
variableFiles: SettingFile[];
inputs: Setting[];
inputFiles: SettingFile[];
runners: Setting[];
environments: Setting[];
}
export interface Setting {
key: string,
value: string,
@@ -12,11 +24,23 @@ export interface Setting {
visible: Visibility
}
export interface SettingFile {
name: string,
path: string,
selected: boolean
}
export enum Visibility {
show = 'show',
hide = 'hide'
}
export enum SettingFileName {
secretFile = '.secrets',
variableFile = '.env',
inputFile = '.input'
}
export class SettingsManager {
storageManager: StorageManager;
secretManager: SecretManager;
@@ -32,17 +56,23 @@ export class SettingsManager {
this.githubManager = new GitHubManager();
}
async getSettings(workspaceFolder: WorkspaceFolder, isUserSelected: boolean) {
async getSettings(workspaceFolder: WorkspaceFolder, isUserSelected: boolean): Promise<Settings> {
const secrets = (await this.getSetting(workspaceFolder, SettingsManager.secretsRegExp, StorageKey.Secrets, true, Visibility.hide)).filter(secret => !isUserSelected || secret.selected);
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 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 environments = await this.getEnvironments(workspaceFolder);
return {
secrets: secrets,
secretFiles: secretFiles,
variables: variables,
variableFiles: variableFiles,
inputs: inputs,
inputFiles: inputFiles,
runners: runners,
environments: environments
};
@@ -94,6 +124,11 @@ export class SettingsManager {
return settings;
}
async getSettingFiles(workspaceFolder: WorkspaceFolder, storageKey: StorageKey): Promise<SettingFile[]> {
const existingSettingFiles = this.storageManager.get<{ [path: string]: SettingFile[] }>(storageKey) || {};
return existingSettingFiles[workspaceFolder.uri.fsPath] || [];
}
async getEnvironments(workspaceFolder: WorkspaceFolder): Promise<Setting[]> {
const environments: Setting[] = [];
@@ -125,6 +160,90 @@ export class SettingsManager {
return environments;
}
async createSettingFile(workspaceFolder: WorkspaceFolder, storageKey: StorageKey, settingFileName: string) {
const settingFileUri = Uri.file(path.join(workspaceFolder.uri.fsPath, settingFileName));
try {
await workspace.fs.stat(settingFileUri);
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 this.locateSettingFile(workspaceFolder, storageKey, [settingFileUri]);
const document = await workspace.openTextDocument(settingFileUri);
await window.showTextDocument(document);
} catch (error: any) {
window.showErrorMessage(`Failed to create ${settingFileName}. Error: ${error}`)
}
}
}
async locateSettingFile(workspaceFolder: WorkspaceFolder, storageKey: StorageKey, settingFilesUris: Uri[]) {
const settingFilesPaths = (await act.settingsManager.getSettingFiles(workspaceFolder, storageKey)).map(settingFile => settingFile.path);
const existingSettingFileNames: string[] = [];
for await (const uri of settingFilesUris) {
const settingFileName = path.parse(uri.fsPath).name;
if (settingFilesPaths.includes(uri.fsPath)) {
existingSettingFileNames.push(settingFileName);
} else {
const newSettingFile: SettingFile = {
name: path.parse(uri.fsPath).base,
path: uri.fsPath,
selected: false
};
await act.settingsManager.editSettingFile(workspaceFolder, newSettingFile, storageKey);
}
}
if (existingSettingFileNames.length > 0) {
window.showErrorMessage(`The following file(s) have already been added: ${existingSettingFileNames.join(', ')}`);
}
}
async editSettingFile(workspaceFolder: WorkspaceFolder, newSettingFile: SettingFile, storageKey: StorageKey) {
const existingSettingFiles = this.storageManager.get<{ [path: string]: SettingFile[] }>(storageKey) || {};
if (existingSettingFiles[workspaceFolder.uri.fsPath]) {
const index = existingSettingFiles[workspaceFolder.uri.fsPath].findIndex(settingFile => settingFile.path === newSettingFile.path);
if (index > -1) {
existingSettingFiles[workspaceFolder.uri.fsPath][index] = newSettingFile;
} else {
existingSettingFiles[workspaceFolder.uri.fsPath].push(newSettingFile);
}
} else {
existingSettingFiles[workspaceFolder.uri.fsPath] = [newSettingFile];
}
await this.storageManager.update(storageKey, existingSettingFiles);
}
async removeSettingFile(workspaceFolder: WorkspaceFolder, settingFile: SettingFile, storageKey: StorageKey) {
const existingSettingFiles = this.storageManager.get<{ [path: string]: SettingFile[] }>(storageKey) || {};
if (existingSettingFiles[workspaceFolder.uri.fsPath]) {
const settingFileIndex = existingSettingFiles[workspaceFolder.uri.fsPath].findIndex(settingFile => settingFile.path === settingFile.path);
if (settingFileIndex > -1) {
existingSettingFiles[workspaceFolder.uri.fsPath].splice(settingFileIndex, 1);
}
}
await this.storageManager.update(storageKey, existingSettingFiles);
}
async deleteSettingFile(workspaceFolder: WorkspaceFolder, settingFile: SettingFile, storageKey: StorageKey) {
try {
await workspace.fs.delete(Uri.file(settingFile.path));
} catch (error: any) {
try {
await workspace.fs.stat(Uri.file(settingFile.path));
window.showErrorMessage(`Failed to delete file. Error ${error}`);
return;
} catch (error) { }
}
await this.removeSettingFile(workspaceFolder, settingFile, storageKey);
}
async editSetting(workspaceFolder: WorkspaceFolder, newSetting: Setting, storageKey: StorageKey) {
const value = newSetting.value;
if (storageKey === StorageKey.Secrets) {

View File

@@ -3,8 +3,11 @@ import { ExtensionContext } from "vscode";
export enum StorageKey {
WorkspaceHistory = 'workspaceHistory',
Secrets = 'secrets',
SecretFiles = 'secretFiles',
Variables = 'variables',
VariableFiles = 'variableFiles',
Inputs = 'inputs',
InputFiles = 'inputFiles',
Runners = 'runners'
}

View File

@@ -40,7 +40,7 @@ export default class HistoryTreeItem extends TreeItem implements GithubLocalActi
}
this.tooltip = `Name: ${history.name} #${history.count}\n` +
`${history.commandArgs.extraHeader.map(header => `${header.key}: ${header.value}`).join('\n')}\n` +
`Path: ${history.commandArgs.fsPath}\n` +
`Path: ${history.commandArgs.path}\n` +
`Log File: ${path.parse(history.logPath).base}\n` +
`Status: ${history.status}\n` +
`Started: ${Utils.getDateString(history.date.start)}\n` +

View File

@@ -1,15 +1,20 @@
import { ThemeIcon, TreeItem, TreeItemCollapsibleState, WorkspaceFolder } from "vscode";
import { act } from "../../extension";
import { Setting } from "../../settingsManager";
import { Setting, SettingFile } from "../../settingsManager";
import { StorageKey } from "../../storageManager";
import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem";
import SettingTreeItem from "./setting";
import SettingFileTreeItem from "./settingFile";
export default class InputsTreeItem extends TreeItem implements GithubLocalActionsTreeItem {
static contextValue = 'githubLocalActions.inputs';
storageKey = StorageKey.InputFiles;
constructor(public workspaceFolder: WorkspaceFolder, inputs: Setting[]) {
constructor(public workspaceFolder: WorkspaceFolder, inputs: Setting[], inputFiles: SettingFile[]) {
super('Inputs', TreeItemCollapsibleState.Collapsed);
this.description = `${inputs.filter(input => input.selected).length}/${inputs.length}`;
const selectedInputFiles = inputFiles.filter(inputFile => inputFile.selected);
this.description = `${inputs.filter(input => input.selected).length}/${inputs.length}` +
(selectedInputFiles.length > 0 ? ` + ${selectedInputFiles[0].name}` : ``);
this.contextValue = InputsTreeItem.contextValue;
this.iconPath = new ThemeIcon('record-keys');
}
@@ -18,10 +23,19 @@ export default class InputsTreeItem extends TreeItem implements GithubLocalActio
const items: GithubLocalActionsTreeItem[] = [];
const settings = await act.settingsManager.getSettings(this.workspaceFolder, false);
for (const input of settings.inputs) {
items.push(SettingTreeItem.getInputTreeItem(this.workspaceFolder, input));
}
return items.sort((a, b) => a.label!.toString().localeCompare(b.label!.toString()));
const inputTreeItems: GithubLocalActionsTreeItem[] = [];
for (const input of settings.inputs) {
inputTreeItems.push(SettingTreeItem.getInputTreeItem(this.workspaceFolder, input));
}
items.push(...inputTreeItems.sort((a, b) => a.label!.toString().localeCompare(b.label!.toString())));
const inputFileTreeItems: GithubLocalActionsTreeItem[] = [];
for (const inputFile of settings.inputFiles) {
inputFileTreeItems.push(SettingFileTreeItem.getInputTreeItem(this.workspaceFolder, inputFile));
}
items.push(...inputFileTreeItems.sort((a, b) => a.label!.toString().localeCompare(b.label!.toString())));
return items;
}
}

View File

@@ -1,15 +1,20 @@
import { ThemeIcon, TreeItem, TreeItemCollapsibleState, WorkspaceFolder } from "vscode";
import { act } from "../../extension";
import { Setting } from "../../settingsManager";
import { Setting, SettingFile } from "../../settingsManager";
import { StorageKey } from "../../storageManager";
import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem";
import SettingTreeItem from "./setting";
import SettingFileTreeItem from "./settingFile";
export default class SecretsTreeItem extends TreeItem implements GithubLocalActionsTreeItem {
static contextValue = 'githubLocalActions.secrets';
storageKey = StorageKey.SecretFiles;
constructor(public workspaceFolder: WorkspaceFolder, secrets: Setting[]) {
constructor(public workspaceFolder: WorkspaceFolder, secrets: Setting[], secretFiles: SettingFile[]) {
super('Secrets', TreeItemCollapsibleState.Collapsed);
this.description = `${secrets.filter(secret => secret.selected).length}/${secrets.length}`;
const selectedSecretFiles = secretFiles.filter(secretFile => secretFile.selected);
this.description = `${secrets.filter(secret => secret.selected).length}/${secrets.length}` +
(selectedSecretFiles.length > 0 ? ` + ${selectedSecretFiles[0].name}` : ``);
this.contextValue = SecretsTreeItem.contextValue;
this.iconPath = new ThemeIcon('lock');
}
@@ -18,10 +23,19 @@ export default class SecretsTreeItem extends TreeItem implements GithubLocalActi
const items: GithubLocalActionsTreeItem[] = [];
const settings = await act.settingsManager.getSettings(this.workspaceFolder, false);
for (const secret of settings.secrets) {
items.push(SettingTreeItem.getSecretTreeItem(this.workspaceFolder, secret));
}
return items.sort((a, b) => a.label!.toString().localeCompare(b.label!.toString()));
const secretTreeItems: GithubLocalActionsTreeItem[] = [];
for (const secret of settings.secrets) {
secretTreeItems.push(SettingTreeItem.getSecretTreeItem(this.workspaceFolder, secret));
}
items.push(...secretTreeItems.sort((a, b) => a.label!.toString().localeCompare(b.label!.toString())));
const secretFileTreeItems: GithubLocalActionsTreeItem[] = [];
for (const secretFile of settings.secretFiles) {
secretFileTreeItems.push(SettingFileTreeItem.getSecretTreeItem(this.workspaceFolder, secretFile));
}
items.push(...secretFileTreeItems.sort((a, b) => a.label!.toString().localeCompare(b.label!.toString())));
return items;
}
}

View File

@@ -36,7 +36,8 @@ export default class SettingTreeItem extends TreeItem implements GithubLocalActi
static getVariableTreeItem(workspaceFolder: WorkspaceFolder, variable: Setting): SettingTreeItem {
return new SettingTreeItem(
workspaceFolder,
variable, StorageKey.Variables,
variable,
StorageKey.Variables,
{
contextValue: 'githubLocalActions.variable',
iconPath: new ThemeIcon('symbol-variable')
@@ -47,7 +48,8 @@ export default class SettingTreeItem extends TreeItem implements GithubLocalActi
static getInputTreeItem(workspaceFolder: WorkspaceFolder, input: Setting): SettingTreeItem {
return new SettingTreeItem(
workspaceFolder,
input, StorageKey.Inputs,
input,
StorageKey.Inputs,
{
contextValue: 'githubLocalActions.input',
iconPath: new ThemeIcon('symbol-parameter')
@@ -58,7 +60,8 @@ export default class SettingTreeItem extends TreeItem implements GithubLocalActi
static getRunnerTreeItem(workspaceFolder: WorkspaceFolder, runner: Setting): SettingTreeItem {
return new SettingTreeItem(
workspaceFolder,
runner, StorageKey.Runners,
runner,
StorageKey.Runners,
{
contextValue: 'githubLocalActions.runner',
iconPath: new ThemeIcon('vm-connect')

View File

@@ -0,0 +1,61 @@
import { ThemeIcon, TreeItem, TreeItemCheckboxState, TreeItemCollapsibleState, WorkspaceFolder } from "vscode";
import { SettingFile } from "../../settingsManager";
import { StorageKey } from "../../storageManager";
import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem";
export default class SettingFileTreeItem extends TreeItem implements GithubLocalActionsTreeItem {
settingFile: SettingFile;
storageKey: StorageKey;
constructor(public workspaceFolder: WorkspaceFolder, settingFile: SettingFile, storageKey: StorageKey, treeItem: { contextValue: string, iconPath: ThemeIcon }) {
super(settingFile.name, TreeItemCollapsibleState.None);
this.settingFile = settingFile;
this.storageKey = storageKey;
this.description = settingFile.path;
this.contextValue = treeItem.contextValue;
this.iconPath = treeItem.iconPath;
this.checkboxState = settingFile.selected ? TreeItemCheckboxState.Checked : TreeItemCheckboxState.Unchecked;
this.tooltip = `Name: ${settingFile.name}\n` +
`Path: ${settingFile.path}\n`;
}
static getSecretTreeItem(workspaceFolder: WorkspaceFolder, secretFile: SettingFile): SettingFileTreeItem {
return new SettingFileTreeItem(
workspaceFolder,
secretFile,
StorageKey.SecretFiles,
{
contextValue: 'githubLocalActions.secretFile',
iconPath: new ThemeIcon('gist-secret')
}
);
}
static getVariableTreeItem(workspaceFolder: WorkspaceFolder, variableFile: SettingFile): SettingFileTreeItem {
return new SettingFileTreeItem(
workspaceFolder,
variableFile,
StorageKey.VariableFiles,
{
contextValue: 'githubLocalActions.variableFile',
iconPath: new ThemeIcon('file')
}
);
}
static getInputTreeItem(workspaceFolder: WorkspaceFolder, inputFile: SettingFile): SettingFileTreeItem {
return new SettingFileTreeItem(
workspaceFolder,
inputFile,
StorageKey.InputFiles,
{
contextValue: 'githubLocalActions.inputFile',
iconPath: new ThemeIcon('file')
}
);
}
async getChildren(): Promise<GithubLocalActionsTreeItem[]> {
return [];
}
}

View File

@@ -1,9 +1,13 @@
import { CancellationToken, commands, EventEmitter, ExtensionContext, QuickPickItem, QuickPickItemKind, ThemeIcon, TreeCheckboxChangeEvent, TreeDataProvider, TreeItem, TreeItemCheckboxState, window, workspace } from "vscode";
import { act } from "../../extension";
import { Visibility } from "../../settingsManager";
import { SettingFileName, Visibility } from "../../settingsManager";
import { StorageKey } from "../../storageManager";
import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem";
import InputsTreeItem from "./inputs";
import SecretsTreeItem from "./secrets";
import SettingTreeItem from "./setting";
import SettingFileTreeItem from "./settingFile";
import VariablesTreeItem from "./variables";
import WorkspaceFolderSettingsTreeItem from "./workspaceFolderSettings";
export default class SettingsTreeDataProvider implements TreeDataProvider<GithubLocalActionsTreeItem> {
@@ -16,6 +20,96 @@ export default class SettingsTreeDataProvider implements TreeDataProvider<Github
commands.registerCommand('githubLocalActions.refreshSettings', async () => {
this.refresh();
}),
commands.registerCommand('githubLocalActions.createSecretFile', async (secretsTreeItem: SecretsTreeItem) => {
const secretFileName = await window.showInputBox({
prompt: `Enter the name for the secret file`,
placeHolder: `Secret File Name`,
value: SettingFileName.secretFile
});
if (secretFileName) {
await act.settingsManager.createSettingFile(secretsTreeItem.workspaceFolder, secretsTreeItem.storageKey, secretFileName);
this.refresh();
}
}),
commands.registerCommand('githubLocalActions.locateSecretFiles', async (secretsTreeItem: SecretsTreeItem) => {
const secretFilesUris = await window.showOpenDialog({
title: 'Locate Secret Files',
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: true,
defaultUri: secretsTreeItem.workspaceFolder.uri
});
if (secretFilesUris) {
await act.settingsManager.locateSettingFile(secretsTreeItem.workspaceFolder, secretsTreeItem.storageKey, secretFilesUris);
this.refresh();
}
}),
commands.registerCommand('githubLocalActions.createVariableFile', async (variablesTreeItem: VariablesTreeItem) => {
const variableFileName = await window.showInputBox({
prompt: `Enter the name for the variable file`,
placeHolder: `Variable File Name`,
value: SettingFileName.variableFile
});
if (variableFileName) {
await act.settingsManager.createSettingFile(variablesTreeItem.workspaceFolder, variablesTreeItem.storageKey, variableFileName);
this.refresh();
}
}),
commands.registerCommand('githubLocalActions.locateVariableFiles', async (variablesTreeItem: VariablesTreeItem) => {
const variableFilesUris = await window.showOpenDialog({
title: 'Locate Variable Files',
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: true,
defaultUri: variablesTreeItem.workspaceFolder.uri
});
if (variableFilesUris) {
await act.settingsManager.locateSettingFile(variablesTreeItem.workspaceFolder, variablesTreeItem.storageKey, variableFilesUris);
this.refresh();
}
}),
commands.registerCommand('githubLocalActions.createInputFile', async (inputsTreeItem: InputsTreeItem) => {
const inputFileName = await window.showInputBox({
prompt: `Enter the name for the input file`,
placeHolder: `Input File Name`,
value: SettingFileName.inputFile
});
if (inputFileName) {
await act.settingsManager.createSettingFile(inputsTreeItem.workspaceFolder, inputsTreeItem.storageKey, inputFileName);
this.refresh();
}
}),
commands.registerCommand('githubLocalActions.locateInputFiles', async (inputsTreeItem: InputsTreeItem) => {
const inputFilesUris = await window.showOpenDialog({
title: 'Locate Variable Files',
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: true,
defaultUri: inputsTreeItem.workspaceFolder.uri
});
if (inputFilesUris) {
await act.settingsManager.locateSettingFile(inputsTreeItem.workspaceFolder, inputsTreeItem.storageKey, inputFilesUris);
this.refresh();
}
}),
commands.registerCommand('githubLocalActions.openSettingFile', async (settingFileTreeItem: SettingFileTreeItem) => {
const document = await workspace.openTextDocument(settingFileTreeItem.settingFile.path);
await window.showTextDocument(document);
}),
commands.registerCommand('githubLocalActions.removeSettingFile', async (settingFileTreeItem: SettingFileTreeItem) => {
await act.settingsManager.removeSettingFile(settingFileTreeItem.workspaceFolder, settingFileTreeItem.settingFile, settingFileTreeItem.storageKey);
this.refresh();
}),
commands.registerCommand('githubLocalActions.deleteSettingFile', async (settingFileTreeItem: SettingFileTreeItem) => {
await act.settingsManager.deleteSettingFile(settingFileTreeItem.workspaceFolder, settingFileTreeItem.settingFile, settingFileTreeItem.storageKey);
this.refresh();
}),
commands.registerCommand('githubLocalActions.show', async (settingTreeItem: SettingTreeItem) => {
const newSetting = settingTreeItem.setting;
newSetting.visible = Visibility.show;
@@ -167,11 +261,32 @@ export default class SettingsTreeDataProvider implements TreeDataProvider<Github
return element;
}
async onDidChangeCheckboxState(event: TreeCheckboxChangeEvent<SettingTreeItem>) {
async onDidChangeCheckboxState(event: TreeCheckboxChangeEvent<SettingTreeItem | SettingFileTreeItem>) {
for await (const [treeItem, state] of event.items) {
const newSetting = treeItem.setting;
newSetting.selected = (state === TreeItemCheckboxState.Checked);
await act.settingsManager.editSetting(treeItem.workspaceFolder, newSetting, treeItem.storageKey);
if (treeItem instanceof SettingTreeItem) {
const newSetting = treeItem.setting;
newSetting.selected = (state === TreeItemCheckboxState.Checked);
await act.settingsManager.editSetting(treeItem.workspaceFolder, newSetting, treeItem.storageKey);
} else {
const isSelected = (state === TreeItemCheckboxState.Checked);
// Update check box state for current setting file tree item
const newSettingFile = treeItem.settingFile;
newSettingFile.selected = isSelected;
await act.settingsManager.editSettingFile(treeItem.workspaceFolder, newSettingFile, treeItem.storageKey);
// Update check box state for other setting file tree items
if (isSelected) {
const settingFiles = await act.settingsManager.getSettingFiles(treeItem.workspaceFolder, treeItem.storageKey);
for (const settingFile of settingFiles) {
if (settingFile.selected && settingFile.path !== treeItem.settingFile.path) {
const newSettingFile = settingFile;
newSettingFile.selected = false;
await act.settingsManager.editSettingFile(treeItem.workspaceFolder, newSettingFile, treeItem.storageKey);
}
}
}
}
}
this.refresh();
}

View File

@@ -1,15 +1,20 @@
import { ThemeIcon, TreeItem, TreeItemCollapsibleState, WorkspaceFolder } from "vscode";
import { act } from "../../extension";
import { Setting } from "../../settingsManager";
import { Setting, SettingFile } from "../../settingsManager";
import { StorageKey } from "../../storageManager";
import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem";
import SettingTreeItem from "./setting";
import SettingFileTreeItem from "./settingFile";
export default class VariablesTreeItem extends TreeItem implements GithubLocalActionsTreeItem {
static contextValue = 'githubLocalActions.variables';
storageKey = StorageKey.VariableFiles;
constructor(public workspaceFolder: WorkspaceFolder, variables: Setting[]) {
constructor(public workspaceFolder: WorkspaceFolder, variables: Setting[], variableFiles: SettingFile[]) {
super('Variables', TreeItemCollapsibleState.Collapsed);
this.description = `${variables.filter(variable => variable.selected).length}/${variables.length}`;
const selectedVariableFiles = variableFiles.filter(variableFile => variableFile.selected);
this.description = `${variables.filter(variable => variable.selected).length}/${variables.length}` +
(selectedVariableFiles.length > 0 ? ` + ${selectedVariableFiles[0].name}` : ``);
this.contextValue = VariablesTreeItem.contextValue;
this.iconPath = new ThemeIcon('symbol-key');
}
@@ -18,10 +23,19 @@ export default class VariablesTreeItem extends TreeItem implements GithubLocalAc
const items: GithubLocalActionsTreeItem[] = [];
const settings = await act.settingsManager.getSettings(this.workspaceFolder, false);
for (const variable of settings.variables) {
items.push(SettingTreeItem.getVariableTreeItem(this.workspaceFolder, variable));
}
return items.sort((a, b) => a.label!.toString().localeCompare(b.label!.toString()));
const variableTreeItems: GithubLocalActionsTreeItem[] = [];
for (const variable of settings.variables) {
variableTreeItems.push(SettingTreeItem.getVariableTreeItem(this.workspaceFolder, variable));
}
items.push(...variableTreeItems.sort((a, b) => a.label!.toString().localeCompare(b.label!.toString())));
const variableFileTreeItems: GithubLocalActionsTreeItem[] = [];
for (const variableFile of settings.variableFiles) {
variableFileTreeItems.push(SettingFileTreeItem.getVariableTreeItem(this.workspaceFolder, variableFile));
}
items.push(...variableFileTreeItems.sort((a, b) => a.label!.toString().localeCompare(b.label!.toString())));
return items;
}
}

View File

@@ -20,9 +20,9 @@ export default class WorkspaceFolderSettingsTreeItem extends TreeItem implements
const settings = await act.settingsManager.getSettings(this.workspaceFolder, false);
items.push(...[
new SecretsTreeItem(this.workspaceFolder, settings.secrets),
new VariablesTreeItem(this.workspaceFolder, settings.variables),
new InputsTreeItem(this.workspaceFolder, settings.inputs),
new SecretsTreeItem(this.workspaceFolder, settings.secrets, settings.secretFiles),
new VariablesTreeItem(this.workspaceFolder, settings.variables, settings.variableFiles),
new InputsTreeItem(this.workspaceFolder, settings.inputs, settings.inputFiles),
new RunnersTreeItem(this.workspaceFolder, settings.runners)
]);

View File

@@ -34,7 +34,7 @@ export class WorkflowsManager {
fileContent: fileContent,
yaml: yaml.parse(fileContent)
});
} catch (error) {
} catch (error: any) {
workflows.push({
name: (yamlContent ? yamlContent.name : undefined) || path.parse(workflowFileUri.fsPath).name,
uri: workflowFileUri,