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:
Sanjula Ganepola
2025-02-11 22:02:35 -05:00
committed by GitHub
parent a299e7d1e7
commit 64b16d4e39
6 changed files with 144 additions and 25 deletions

View File

@@ -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"
} }
] ]

View File

@@ -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());

View File

@@ -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());
}
});
});
}
} }

View File

@@ -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;

View File

@@ -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) {
this.description = (setting.visible === Visibility.hide && setting.value) ? '••••••••' : setting.value; if (setting.mode === Mode.generate) {
this.description = 'Generated by GitHub CLI';
} else {
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);

View File

@@ -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`,