Add support for auto-generating github cli token (#165)
* Add support for auto-generating github cli token Signed-off-by: Sanjula Ganepola <sanjulagane@gmail.com> * Autogenerate Github CLI token when executing act command Signed-off-by: Sanjula Ganepola <sanjulagane@gmail.com> --------- Signed-off-by: Sanjula Ganepola <sanjulagane@gmail.com>
This commit is contained in:
36
package.json
36
package.json
@@ -282,6 +282,18 @@
|
|||||||
"title": "Hide",
|
"title": "Hide",
|
||||||
"icon": "$(eye-closed)"
|
"icon": "$(eye-closed)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"category": "GitHub Local Actions",
|
||||||
|
"command": "githubLocalActions.enableGithubCLIToken",
|
||||||
|
"title": "Enable GitHub CLI Token",
|
||||||
|
"icon": "$(sync)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "GitHub Local Actions",
|
||||||
|
"command": "githubLocalActions.disableGithubCLIToken",
|
||||||
|
"title": "Disable GitHub CLI Token",
|
||||||
|
"icon": "$(sync-ignored)"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"category": "GitHub Local Actions",
|
"category": "GitHub Local Actions",
|
||||||
"command": "githubLocalActions.createVariableFile",
|
"command": "githubLocalActions.createVariableFile",
|
||||||
@@ -472,6 +484,14 @@
|
|||||||
"command": "githubLocalActions.hide",
|
"command": "githubLocalActions.hide",
|
||||||
"when": "never"
|
"when": "never"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "githubLocalActions.enableGithubCLIToken",
|
||||||
|
"when": "never"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "githubLocalActions.disableGithubCLIToken",
|
||||||
|
"when": "never"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "githubLocalActions.createVariableFile",
|
"command": "githubLocalActions.createVariableFile",
|
||||||
"when": "never"
|
"when": "never"
|
||||||
@@ -673,14 +693,24 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "githubLocalActions.show",
|
"command": "githubLocalActions.show",
|
||||||
"when": "view == settings && viewItem =~ /^githubLocalActions.secret(?!s)_hide.*/",
|
"when": "view == settings && viewItem =~ /^githubLocalActions.secret(?!s)_hide(?!_generate).*/",
|
||||||
"group": "inline@0"
|
"group": "inline@0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "githubLocalActions.hide",
|
"command": "githubLocalActions.hide",
|
||||||
"when": "view == settings && viewItem =~ /^githubLocalActions.secret(?!s)_show.*/",
|
"when": "view == settings && viewItem =~ /^githubLocalActions.secret(?!s)_show(?!_generate).*/",
|
||||||
"group": "inline@0"
|
"group": "inline@0"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "githubLocalActions.enableGithubCLIToken",
|
||||||
|
"when": "view == settings && viewItem =~ /^githubLocalActions.secret(?!s).*_manual.*/",
|
||||||
|
"group": "inline@2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "githubLocalActions.disableGithubCLIToken",
|
||||||
|
"when": "view == settings && viewItem =~ /^githubLocalActions.secret(?!s).*_generate.*/",
|
||||||
|
"group": "inline@2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "githubLocalActions.createVariableFile",
|
"command": "githubLocalActions.createVariableFile",
|
||||||
"when": "view == settings && viewItem =~ /^githubLocalActions.variables.*/",
|
"when": "view == settings && viewItem =~ /^githubLocalActions.variables.*/",
|
||||||
@@ -743,7 +773,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "githubLocalActions.editSetting",
|
"command": "githubLocalActions.editSetting",
|
||||||
"when": "view == settings && viewItem =~ /^githubLocalActions.(secret|variable|input|runner)(?!(File|s)).*/",
|
"when": "view == settings && viewItem =~ /^githubLocalActions.(secret|variable|input|runner)(?!(File|s))(?!_(show|hide)_generate).*/",
|
||||||
"group": "inline@1"
|
"group": "inline@1"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
29
src/act.ts
29
src/act.ts
@@ -9,7 +9,7 @@ import { ConfigurationManager, Platform, Section } from "./configurationManager"
|
|||||||
import { componentsTreeDataProvider, historyTreeDataProvider } from './extension';
|
import { componentsTreeDataProvider, historyTreeDataProvider } from './extension';
|
||||||
import { HistoryManager, HistoryStatus } from './historyManager';
|
import { HistoryManager, HistoryStatus } from './historyManager';
|
||||||
import { SecretManager } from "./secretManager";
|
import { SecretManager } from "./secretManager";
|
||||||
import { SettingsManager } from './settingsManager';
|
import { Mode, SettingsManager } from './settingsManager';
|
||||||
import { StorageKey, StorageManager } from './storageManager';
|
import { StorageKey, StorageManager } from './storageManager';
|
||||||
import { Utils } from "./utils";
|
import { Utils } from "./utils";
|
||||||
import { Job, Workflow, WorkflowsManager } from "./workflowsManager";
|
import { Job, Workflow, WorkflowsManager } from "./workflowsManager";
|
||||||
@@ -836,20 +836,29 @@ export class Act {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process environment variables for child process
|
||||||
|
const processedSecrets: Record<string, string> = {};
|
||||||
|
for (const secret of settings.secrets) {
|
||||||
|
if (secret.key === 'GITHUB_TOKEN' && secret.mode === Mode.generate) {
|
||||||
|
const token = await this.settingsManager.githubManager.getGithubCLIToken();
|
||||||
|
if (token) {
|
||||||
|
processedSecrets[secret.key] = token;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
processedSecrets[secret.key] = secret.value!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const envVars = {
|
||||||
|
...process.env,
|
||||||
|
...processedSecrets
|
||||||
|
};
|
||||||
|
|
||||||
const exec = childProcess.spawn(
|
const exec = childProcess.spawn(
|
||||||
command,
|
command,
|
||||||
{
|
{
|
||||||
cwd: commandArgs.path,
|
cwd: commandArgs.path,
|
||||||
shell: shell,
|
shell: shell,
|
||||||
env: {
|
env: envVars
|
||||||
...process.env,
|
|
||||||
...settings.secrets
|
|
||||||
.filter(secret => secret.value)
|
|
||||||
.reduce((previousValue, currentValue) => {
|
|
||||||
previousValue[currentValue.key] = currentValue.value;
|
|
||||||
return previousValue;
|
|
||||||
}, {} as Record<string, string>)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
exec.stdout.on('data', handleIO());
|
exec.stdout.on('data', handleIO());
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import * as childProcess from "child_process";
|
||||||
import { Octokit } from "octokit";
|
import { Octokit } from "octokit";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { authentication, AuthenticationSession, commands, extensions, window, WorkspaceFolder } from "vscode";
|
import { authentication, AuthenticationSession, commands, extensions, ShellExecution, TaskGroup, TaskPanelKind, TaskRevealKind, tasks, TaskScope, window, WorkspaceFolder } from "vscode";
|
||||||
import { GitExtension } from "./import/git";
|
import { GitExtension } from "./import/git";
|
||||||
|
|
||||||
export interface Response<T> {
|
export interface Response<T> {
|
||||||
@@ -152,4 +153,44 @@ export class GitHubManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getGithubCLIToken(): Promise<string | undefined> {
|
||||||
|
return new Promise<string | undefined>((resolve, reject) => {
|
||||||
|
childProcess.exec('gh auth token', (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
const errorMessage = (String(stderr).charAt(0).toUpperCase() + String(stderr).slice(1)).trim();
|
||||||
|
window.showErrorMessage(`${errorMessage}. Authenticate to GitHub and try again.`, 'Authenticate').then(async value => {
|
||||||
|
if (value === 'Authenticate') {
|
||||||
|
await tasks.executeTask({
|
||||||
|
name: 'GitHub CLI',
|
||||||
|
detail: 'Authenticate with a GitHub host',
|
||||||
|
definition: {
|
||||||
|
type: 'Authenticate with a GitHub host'
|
||||||
|
},
|
||||||
|
source: 'GitHub Local Actions',
|
||||||
|
scope: TaskScope.Workspace,
|
||||||
|
isBackground: true,
|
||||||
|
presentationOptions: {
|
||||||
|
reveal: TaskRevealKind.Always,
|
||||||
|
focus: false,
|
||||||
|
clear: true,
|
||||||
|
close: false,
|
||||||
|
echo: true,
|
||||||
|
panel: TaskPanelKind.Shared,
|
||||||
|
showReuseMessage: false
|
||||||
|
},
|
||||||
|
problemMatchers: [],
|
||||||
|
runOptions: {},
|
||||||
|
group: TaskGroup.Build,
|
||||||
|
execution: new ShellExecution('gh auth login')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
resolve(undefined);
|
||||||
|
} else {
|
||||||
|
resolve(stdout.trim());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,8 @@ export interface Setting {
|
|||||||
value: string,
|
value: string,
|
||||||
password: boolean,
|
password: boolean,
|
||||||
selected: boolean,
|
selected: boolean,
|
||||||
visible: Visibility
|
visible: Visibility,
|
||||||
|
mode: Mode
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is either a secret/variable/input/payload file or an option
|
// This is either a secret/variable/input/payload file or an option
|
||||||
@@ -41,6 +42,11 @@ export enum Visibility {
|
|||||||
hide = 'hide'
|
hide = 'hide'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum Mode {
|
||||||
|
generate = 'generate',
|
||||||
|
manual = 'manual'
|
||||||
|
}
|
||||||
|
|
||||||
export enum SettingFileName {
|
export enum SettingFileName {
|
||||||
secretFile = '.secrets',
|
secretFile = '.secrets',
|
||||||
envFile = '.env',
|
envFile = '.env',
|
||||||
@@ -65,7 +71,17 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getSettings(workspaceFolder: WorkspaceFolder, isUserSelected: boolean): Promise<Settings> {
|
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 && secret.value));
|
const defaultSecrets: Setting[] = [
|
||||||
|
{
|
||||||
|
key: 'GITHUB_TOKEN',
|
||||||
|
value: '',
|
||||||
|
password: true,
|
||||||
|
selected: false,
|
||||||
|
visible: Visibility.hide,
|
||||||
|
mode: Mode.manual
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const secrets = (await this.getSetting(workspaceFolder, SettingsManager.secretsRegExp, StorageKey.Secrets, true, Visibility.hide, defaultSecrets)).filter(secret => !isUserSelected || (secret.selected && (secret.value || secret.mode === Mode.generate)));
|
||||||
const secretFiles = (await this.getCustomSettings(workspaceFolder, StorageKey.SecretFiles)).filter(secretFile => !isUserSelected || secretFile.selected);
|
const secretFiles = (await this.getCustomSettings(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 && variable.value));
|
const variables = (await this.getSetting(workspaceFolder, SettingsManager.variablesRegExp, StorageKey.Variables, false, Visibility.show)).filter(variable => !isUserSelected || (variable.selected && variable.value));
|
||||||
const variableFiles = (await this.getCustomSettings(workspaceFolder, StorageKey.VariableFiles)).filter(variableFile => !isUserSelected || variableFile.selected);
|
const variableFiles = (await this.getCustomSettings(workspaceFolder, StorageKey.VariableFiles)).filter(variableFile => !isUserSelected || variableFile.selected);
|
||||||
@@ -90,8 +106,8 @@ export class SettingsManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSetting(workspaceFolder: WorkspaceFolder, regExp: RegExp, storageKey: StorageKey, password: boolean, visible: Visibility): Promise<Setting[]> {
|
async getSetting(workspaceFolder: WorkspaceFolder, regExp: RegExp, storageKey: StorageKey, password: boolean, visible: Visibility, defaultSettings: Setting[] = []): Promise<Setting[]> {
|
||||||
const settings: Setting[] = [];
|
const settings: Setting[] = defaultSettings;
|
||||||
|
|
||||||
const workflows = await act.workflowsManager.getWorkflows(workspaceFolder);
|
const workflows = await act.workflowsManager.getWorkflows(workspaceFolder);
|
||||||
for (const workflow of workflows) {
|
for (const workflow of workflows) {
|
||||||
@@ -125,7 +141,8 @@ export class SettingsManager {
|
|||||||
value: value,
|
value: value,
|
||||||
password: existingSetting.password,
|
password: existingSetting.password,
|
||||||
selected: existingSetting.selected,
|
selected: existingSetting.selected,
|
||||||
visible: existingSetting.visible
|
visible: existingSetting.visible,
|
||||||
|
mode: existingSetting.mode || Mode.manual
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +178,8 @@ export class SettingsManager {
|
|||||||
value: '',
|
value: '',
|
||||||
password: false,
|
password: false,
|
||||||
selected: false,
|
selected: false,
|
||||||
visible: Visibility.show
|
visible: Visibility.show,
|
||||||
|
mode: Mode.manual
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,7 +316,7 @@ export class SettingsManager {
|
|||||||
|
|
||||||
const matches = content.matchAll(regExp);
|
const matches = content.matchAll(regExp);
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
results.push({ key: match[1], value: '', password: password, selected: false, visible: visible });
|
results.push({ key: match[1], value: '', password: password, selected: false, visible: visible, mode: Mode.manual });
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ThemeIcon, TreeItem, TreeItemCheckboxState, TreeItemCollapsibleState, Uri, WorkspaceFolder } from "vscode";
|
import { ThemeIcon, TreeItem, TreeItemCheckboxState, TreeItemCollapsibleState, Uri, WorkspaceFolder } from "vscode";
|
||||||
import { Setting, Visibility } from "../../settingsManager";
|
import { Mode, Setting, Visibility } from "../../settingsManager";
|
||||||
import { StorageKey } from "../../storageManager";
|
import { StorageKey } from "../../storageManager";
|
||||||
import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem";
|
import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem";
|
||||||
|
|
||||||
@@ -19,11 +19,15 @@ export default class SettingTreeItem extends TreeItem implements GithubLocalActi
|
|||||||
this.setting = setting;
|
this.setting = setting;
|
||||||
this.storageKey = storageKey;
|
this.storageKey = storageKey;
|
||||||
if (setting.password) {
|
if (setting.password) {
|
||||||
|
if (setting.mode === Mode.generate) {
|
||||||
|
this.description = 'Generated by GitHub CLI';
|
||||||
|
} else {
|
||||||
this.description = (setting.visible === Visibility.hide && setting.value) ? '••••••••' : setting.value;
|
this.description = (setting.visible === Visibility.hide && setting.value) ? '••••••••' : setting.value;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.description = setting.value;
|
this.description = setting.value;
|
||||||
}
|
}
|
||||||
this.contextValue = `${treeItem.contextValue}_${setting.password ? setting.visible : ''}`;
|
this.contextValue = `${treeItem.contextValue}${setting.password ? `_${setting.visible}` : ''}${setting.key === 'GITHUB_TOKEN' ? `_${setting.mode}` : ''}`;
|
||||||
this.iconPath = treeItem.iconPath;
|
this.iconPath = treeItem.iconPath;
|
||||||
this.checkboxState = setting.selected ? TreeItemCheckboxState.Checked : TreeItemCheckboxState.Unchecked;
|
this.checkboxState = setting.selected ? TreeItemCheckboxState.Checked : TreeItemCheckboxState.Unchecked;
|
||||||
this.resourceUri = Uri.parse(`${treeItem.contextValue}:${setting.key}?isSelected=${setting.selected}&hasValue=${setting.value !== ''}`, true);
|
this.resourceUri = Uri.parse(`${treeItem.contextValue}:${setting.key}?isSelected=${setting.selected}&hasValue=${setting.value !== ''}`, true);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { CancellationToken, commands, EventEmitter, ExtensionContext, QuickPickItem, QuickPickItemKind, ThemeIcon, TreeCheckboxChangeEvent, TreeDataProvider, TreeItem, TreeItemCheckboxState, Uri, window, workspace } from "vscode";
|
import { CancellationToken, commands, EventEmitter, ExtensionContext, QuickPickItem, QuickPickItemKind, ThemeIcon, TreeCheckboxChangeEvent, TreeDataProvider, TreeItem, TreeItemCheckboxState, Uri, window, workspace } from "vscode";
|
||||||
import { Option } from "../../act";
|
import { Option } from "../../act";
|
||||||
import { act } from "../../extension";
|
import { act } from "../../extension";
|
||||||
import { SettingFileName, Visibility } from "../../settingsManager";
|
import { Mode, SettingFileName, Visibility } from "../../settingsManager";
|
||||||
import { StorageKey } from "../../storageManager";
|
import { StorageKey } from "../../storageManager";
|
||||||
import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem";
|
import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem";
|
||||||
import InputsTreeItem from "./inputs";
|
import InputsTreeItem from "./inputs";
|
||||||
@@ -50,6 +50,23 @@ export default class SettingsTreeDataProvider implements TreeDataProvider<Github
|
|||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
commands.registerCommand('githubLocalActions.enableGithubCLIToken', async (settingTreeItem: SettingTreeItem) => {
|
||||||
|
const token = await act.settingsManager.githubManager.getGithubCLIToken();
|
||||||
|
if (token) {
|
||||||
|
const newSetting = settingTreeItem.setting;
|
||||||
|
newSetting.mode = Mode.generate;
|
||||||
|
newSetting.value = '';
|
||||||
|
newSetting.visible = Visibility.hide;
|
||||||
|
await act.settingsManager.editSetting(settingTreeItem.workspaceFolder, newSetting, settingTreeItem.storageKey);
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
commands.registerCommand('githubLocalActions.disableGithubCLIToken', async (settingTreeItem: SettingTreeItem) => {
|
||||||
|
const newSetting = settingTreeItem.setting;
|
||||||
|
newSetting.mode = Mode.manual;
|
||||||
|
await act.settingsManager.editSetting(settingTreeItem.workspaceFolder, newSetting, settingTreeItem.storageKey);
|
||||||
|
this.refresh();
|
||||||
|
}),
|
||||||
commands.registerCommand('githubLocalActions.createVariableFile', async (variablesTreeItem: VariablesTreeItem) => {
|
commands.registerCommand('githubLocalActions.createVariableFile', async (variablesTreeItem: VariablesTreeItem) => {
|
||||||
const variableFileName = await window.showInputBox({
|
const variableFileName = await window.showInputBox({
|
||||||
prompt: `Enter the name for the variable file`,
|
prompt: `Enter the name for the variable file`,
|
||||||
|
|||||||
Reference in New Issue
Block a user