diff --git a/package-lock.json b/package-lock.json index 085bc63..a90cc4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,10 @@ "": { "name": "github-local-actions", "version": "0.0.1", + "license": "Apache-2.0", + "dependencies": { + "yaml": "^2.5.1" + }, "devDependencies": { "@types/mocha": "^10.0.7", "@types/node": "20.x", @@ -391,9 +395,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.16.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.6.tgz", - "integrity": "sha512-T7PpxM/6yeDE+AdlVysT62BX6/bECZOmQAgiFg5NoBd5MQheZ3tzal7f1wvzfiEcmrcJNRi2zRr2nY2zF+0uqw==", + "version": "20.16.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.9.tgz", + "integrity": "sha512-rkvIVJxsOfBejxK7I0FO5sa2WxFmJCzoDwcd88+fq/CUfynNywTo/1/T6hyFz22CyztsnLS9nVlHOnTI36RH5w==", "dev": true, "dependencies": { "undici-types": "~6.19.2" @@ -1038,9 +1042,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", "dev": true, "funding": [ { @@ -1057,8 +1061,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", "node-releases": "^2.0.18", "update-browserslist-db": "^1.1.0" }, @@ -1146,9 +1150,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001663", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001663.tgz", - "integrity": "sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA==", + "version": "1.0.30001664", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz", + "integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==", "dev": true, "funding": [ { @@ -1431,9 +1435,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.5.28", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.28.tgz", - "integrity": "sha512-VufdJl+rzaKZoYVUijN13QcXVF5dWPZANeFTLNy+OSpHdDL5ynXTF35+60RSBbaQYB1ae723lQXHCrf4pyLsMw==", + "version": "1.5.29", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz", + "integrity": "sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw==", "dev": true }, "node_modules/emoji-regex": { @@ -2967,9 +2971,9 @@ } }, "node_modules/package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true }, "node_modules/pako": { @@ -3608,9 +3612,9 @@ } }, "node_modules/terser": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.33.0.tgz", - "integrity": "sha512-JuPVaB7s1gdFKPKTelwUyRq5Sid2A3Gko2S0PncwdBq7kN9Ti9HPWDQ06MPsEDGsZeVESjKEnyGy68quBk1w6g==", + "version": "5.34.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.0.tgz", + "integrity": "sha512-y5NUX+U9HhVsK/zihZwoq4r9dICLyV2jXGOriDAVOeKhq3LKVjgJbGO90FisozXLlJfvjHqgckGmJFBb9KYoWQ==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -3870,9 +3874,9 @@ } }, "node_modules/webpack": { - "version": "5.94.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", - "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "version": "5.95.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", + "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", "dev": true, "dependencies": { "@types/estree": "^1.0.5", @@ -4159,6 +4163,17 @@ "node": ">=10" } }, + "node_modules/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index cf9406a..44be547 100644 --- a/package.json +++ b/package.json @@ -62,8 +62,7 @@ } ] }, - "commands": [ - ], + "commands": [], "colors": [ { "id": "GitHubLocalActions.enabled", @@ -102,18 +101,21 @@ "lint": "eslint src", "test": "vscode-test" }, + "dependencies": { + "yaml": "^2.5.1" + }, "devDependencies": { - "@types/vscode": "^1.93.0", "@types/mocha": "^10.0.7", "@types/node": "20.x", + "@types/vscode": "^1.93.0", "@typescript-eslint/eslint-plugin": "^8.3.0", "@typescript-eslint/parser": "^8.3.0", - "eslint": "^9.9.1", - "typescript": "^5.5.4", - "ts-loader": "^9.5.1", - "webpack": "^5.94.0", - "webpack-cli": "^5.1.4", "@vscode/test-cli": "^0.0.10", - "@vscode/test-electron": "^2.4.1" + "@vscode/test-electron": "^2.4.1", + "eslint": "^9.9.1", + "ts-loader": "^9.5.1", + "typescript": "^5.5.4", + "webpack": "^5.94.0", + "webpack-cli": "^5.1.4" } } diff --git a/src/extension.ts b/src/extension.ts index 3b50630..3510b2f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,14 +1,22 @@ import * as vscode from 'vscode'; import { window } from 'vscode'; import ComponentsTreeDataProvider from './views/components/componentsTreeDataProvider'; +import { DecorationProvider } from './views/decorationProvider'; +import WorkflowsTreeDataProvider from './views/workflows/workflowsTreeDataProvider'; export function activate(context: vscode.ExtensionContext) { console.log('Congratulations, your extension "github-local-actions" is now active!'); + + const decorationProvider = new DecorationProvider(); const componentsTreeDataProvider = new ComponentsTreeDataProvider(context); const componentsTreeView = window.createTreeView(ComponentsTreeDataProvider.VIEW_ID, { treeDataProvider: componentsTreeDataProvider }); + const workflowsTreeDataProvider = new WorkflowsTreeDataProvider(context); + const workflowsTreeView = window.createTreeView(WorkflowsTreeDataProvider.VIEW_ID, { treeDataProvider: workflowsTreeDataProvider }); context.subscriptions.push( - componentsTreeView + componentsTreeView, + workflowsTreeView, + window.registerFileDecorationProvider(decorationProvider) ); } diff --git a/src/types.ts b/src/types.ts index d99adcb..04ecabd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,4 +9,11 @@ export enum Status { Enabled = 'Enabled', Warning = 'Warning', Disabled = 'Disabled' +} + +export interface Workflow { + name: string, + path: string, + content?: any, + error?: string } \ No newline at end of file diff --git a/src/views/components/component.ts b/src/views/components/component.ts index 20ce3dd..e3b6306 100644 --- a/src/views/components/component.ts +++ b/src/views/components/component.ts @@ -7,9 +7,8 @@ export default class ComponentTreeItem extends TreeItem implements GithubLocalAc component: Component; constructor(component: Component) { - super(component.name, TreeItemCollapsibleState.Collapsed); + super(component.name, TreeItemCollapsibleState.None); this.component = component; - this.collapsibleState = TreeItemCollapsibleState.None; this.contextValue = ComponentTreeItem.contextValue; this.iconPath = new ThemeIcon(component.icon); this.resourceUri = Uri.parse(`${ComponentTreeItem.contextValue}:${component.name}?status=${component.status}`, true); diff --git a/src/views/components/componentsTreeDataProvider.ts b/src/views/components/componentsTreeDataProvider.ts index 84cef07..bc5503d 100644 --- a/src/views/components/componentsTreeDataProvider.ts +++ b/src/views/components/componentsTreeDataProvider.ts @@ -1,6 +1,5 @@ -import { CancellationToken, EventEmitter, ExtensionContext, TreeDataProvider, TreeItem, window } from "vscode"; +import { CancellationToken, EventEmitter, ExtensionContext, TreeDataProvider, TreeItem } from "vscode"; import { ComponentManager } from "../componentManager"; -import { DecorationProvider } from "../decorationProvider"; import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem"; import ComponentTreeItem from "./component"; @@ -12,11 +11,6 @@ export default class ComponentsTreeDataProvider implements TreeDataProvider | undefined; provideFileDecoration(uri: Uri, token: CancellationToken): ProviderResult { + const params = new URLSearchParams(uri.query); + if (uri.scheme === ComponentTreeItem.contextValue) { - const params = new URLSearchParams(uri.query); if (params.get('status') === Status.Enabled) { return { badge: '✅', @@ -23,6 +25,14 @@ export class DecorationProvider implements FileDecorationProvider { color: new ThemeColor('GitHubLocalActions.disabled') }; } + } else if (uri.scheme === WorkflowTreeItem.contextValue) { + // TODO: Fix color + if (params.get('error')) { + return { + badge: '❌', + color: new ThemeColor('GitHubLocalActions.disabled') + }; + } } } } \ No newline at end of file diff --git a/src/views/workflowManager.ts b/src/views/workflowManager.ts new file mode 100644 index 0000000..2f459b1 --- /dev/null +++ b/src/views/workflowManager.ts @@ -0,0 +1,36 @@ +import * as fs from "fs/promises"; +import * as path from "path"; +import { workspace } from "vscode"; +import * as yaml from "yaml"; +import { Workflow } from "../types"; + +export class WorkflowManager { + async getWorkflows(): Promise { + const workflows: Workflow[] = []; + + const workspaceFolders = workspace.workspaceFolders; + if (workspaceFolders && workspaceFolders.length > 0) { + const workflowFiles = await workspace.findFiles(`.github/workflows/*.{yml,yaml}`); + + for await (const workflowFile of workflowFiles) { + try { + const fileContent = await fs.readFile(workflowFile.fsPath, 'utf8'); + + workflows.push({ + name: path.parse(workflowFile.fsPath).name, + path: workflowFile.fsPath, + content: yaml.parse(fileContent) + }); + } catch (error) { + workflows.push({ + name: path.parse(workflowFile.fsPath).name, + path: workflowFile.fsPath, + error: 'Failed to parse workflow file' + }); + } + } + } + + return workflows; + } +} \ No newline at end of file diff --git a/src/views/workflows/workflow.ts b/src/views/workflows/workflow.ts new file mode 100644 index 0000000..774a94b --- /dev/null +++ b/src/views/workflows/workflow.ts @@ -0,0 +1,25 @@ +import { ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri } from "vscode"; +import { Workflow } from "../../types"; +import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem"; + +export default class WorkflowTreeItem extends TreeItem implements GithubLocalActionsTreeItem { + static contextValue = 'workflow'; + workflow: Workflow; + + constructor(workflow: Workflow) { + super(workflow.content.name || workflow.name, workflow.error ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed); + this.workflow = workflow; + this.contextValue = WorkflowTreeItem.contextValue; + this.iconPath = new ThemeIcon('layers'); + this.tooltip = `Name: ${workflow.name}\n` + + `Path: ${workflow.path}`; + + if(workflow.error) { + this.resourceUri = Uri.parse(`${WorkflowTreeItem.contextValue}:${workflow.name}?error=${workflow.error}`, true); + } + } + + async getChildren(): Promise { + return []; + } +} \ No newline at end of file diff --git a/src/views/workflows/workflowsTreeDataProvider.ts b/src/views/workflows/workflowsTreeDataProvider.ts new file mode 100644 index 0000000..2b26dc4 --- /dev/null +++ b/src/views/workflows/workflowsTreeDataProvider.ts @@ -0,0 +1,40 @@ +import { CancellationToken, EventEmitter, ExtensionContext, TreeDataProvider, TreeItem } from "vscode"; +import { GithubLocalActionsTreeItem } from "../githubLocalActionsTreeItem"; +import { WorkflowManager } from "../workflowManager"; +import WorkflowTreeItem from "./workflow"; + +export default class WorkflowsTreeDataProvider implements TreeDataProvider { + private _onDidChangeTreeData = new EventEmitter(); + readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + public static VIEW_ID = 'workflows'; + private workflowManager: WorkflowManager; + + constructor(context: ExtensionContext) { + this.workflowManager = new WorkflowManager(); + } + + refresh(element?: GithubLocalActionsTreeItem) { + this._onDidChangeTreeData.fire(element); + } + + getTreeItem(element: GithubLocalActionsTreeItem): GithubLocalActionsTreeItem | Thenable { + return element; + } + + async resolveTreeItem(item: TreeItem, element: GithubLocalActionsTreeItem, token: CancellationToken): Promise { + if (element.getToolTip) { + element.tooltip = await element.getToolTip(); + } + + return element; + } + + async getChildren(element?: GithubLocalActionsTreeItem): Promise { + if (element) { + return element.getChildren(); + } else { + const workflows = await this.workflowManager.getWorkflows(); + return workflows.map(workflow => new WorkflowTreeItem(workflow)); + } + } +} \ No newline at end of file