Compare commits
11 Commits
5103c8065f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 513aad88bb | |||
| adf88e431c | |||
|
|
bc25f97d70 | ||
|
|
eebee47f40 | ||
|
|
c505e8af9b | ||
|
|
b7bd8d9600 | ||
|
|
9452872b96 | ||
|
|
6e3b0e7b21 | ||
|
|
e8f3f6c673 | ||
|
|
412914d32a | ||
|
|
431ac5e6a8 |
21
.gitea/workflows/test-gitea.yml
Normal file
21
.gitea/workflows/test-gitea.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: Test Gitea Workflow
|
||||||
|
run-name: Test Gitea Workflow
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: echo "Testing Gitea workflow"
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: echo "Building project"
|
||||||
20
.github/ISSUE_TEMPLATE/1-bug_report.yml
vendored
20
.github/ISSUE_TEMPLATE/1-bug_report.yml
vendored
@@ -3,6 +3,10 @@ description: Report a bug or issue.
|
|||||||
labels:
|
labels:
|
||||||
- 'bug'
|
- 'bug'
|
||||||
body:
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
⭐ Most of the content for this bug report can be automatically generated by selecting `Help and Support` -> `Report an Issue` from any GitHub Local Actions view. ⭐
|
||||||
- type: input
|
- type: input
|
||||||
id: github-local-actions-version
|
id: github-local-actions-version
|
||||||
attributes:
|
attributes:
|
||||||
@@ -76,14 +80,6 @@ body:
|
|||||||
render: sh
|
render: sh
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
- type: textarea
|
|
||||||
id: bug-description
|
|
||||||
attributes:
|
|
||||||
label: Bug Description
|
|
||||||
description: |
|
|
||||||
Describe the bug you encountered and share all steps to reproduce it.
|
|
||||||
placeholder: |
|
|
||||||
Bug description
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: act-bug-report
|
id: act-bug-report
|
||||||
attributes:
|
attributes:
|
||||||
@@ -104,3 +100,11 @@ body:
|
|||||||
render: yml
|
render: yml
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: bug-description
|
||||||
|
attributes:
|
||||||
|
label: Bug Description
|
||||||
|
description: |
|
||||||
|
Describe the bug you encountered and share all steps to reproduce it.
|
||||||
|
placeholder: |
|
||||||
|
Bug description
|
||||||
13
.github/workflows/publish.yaml
vendored
13
.github/workflows/publish.yaml
vendored
@@ -2,6 +2,17 @@ name: Publish to the Marketplace and Open VSX
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
publish_openvsx:
|
||||||
|
description: 'Publish to Open VSX'
|
||||||
|
type: boolean
|
||||||
|
required: true
|
||||||
|
default: true
|
||||||
|
publish_marketplace:
|
||||||
|
description: 'Publish to Marketplace'
|
||||||
|
type: boolean
|
||||||
|
required: true
|
||||||
|
default: true
|
||||||
|
|
||||||
release:
|
release:
|
||||||
types: [created]
|
types: [created]
|
||||||
@@ -30,7 +41,9 @@ jobs:
|
|||||||
npm install -g vsce ovsx
|
npm install -g vsce ovsx
|
||||||
|
|
||||||
- name: Publish to Open VSX
|
- name: Publish to Open VSX
|
||||||
|
if: github.event_name == 'release' || inputs.publish_openvsx == true
|
||||||
run: npx ovsx publish -p ${{ secrets.OPEN_VSX_TOKEN }}
|
run: npx ovsx publish -p ${{ secrets.OPEN_VSX_TOKEN }}
|
||||||
|
|
||||||
- name: Publish to Marketplace
|
- name: Publish to Marketplace
|
||||||
|
if: github.event_name == 'release' || inputs.publish_marketplace == true
|
||||||
run: vsce publish -p ${{ secrets.VS_MARKETPLACE_TOKEN }}
|
run: vsce publish -p ${{ secrets.VS_MARKETPLACE_TOKEN }}
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
All notable changes to the "github-local-actions" extension will be documented in this file.
|
All notable changes to the "github-local-actions" extension will be documented in the [GitHub release notes](https://github.com/SanjulaGanepola/github-local-actions/releases).
|
||||||
|
|
||||||
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
|
||||||
|
|
||||||
## [1.0.0]
|
|
||||||
|
|
||||||
- Initial release
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Thank you for your interest in contributing to **GitHub Local Actions**! Whether you're fixing a bug, adding a new feature, or improving the documentation, your contributions are highly valued and help make this project better for everyone.
|
Thank you for your interest in contributing to **GitHub Local Actions**! Whether you're fixing a bug, adding a new feature, or improving the documentation, your contributions are highly valued and help make this project better for everyone.
|
||||||
|
|
||||||
No contribution is too small—every bit helps! If you're unsure where to start, check out our [open issues](https://github.com/SanjulaGanepola/github-local-actions/issues) or reach out on our [discussion board](https://github.com/SanjulaGanepola/github-local-actions/discussions) to discuss your ideas. By contributing, you are agreeing to follow our [Code of Conduct](https://github.com/SanjulaGanepola/github-local-actions/blob/main/CODE_OF_CONDUCT.md) guidelines. Let’s keep this a welcoming and fun space for everyone to collaborate!
|
No contribution is too small—every bit helps! If you're unsure where to start, check out our [open issues](https://github.com/SanjulaGanepola/github-local-actions/issues) (look out for the [good first issue](https://github.com/SanjulaGanepola/github-local-actions/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) label) or reach out on our [discussion board](https://github.com/SanjulaGanepola/github-local-actions/discussions) to discuss your ideas. By contributing, you are agreeing to follow our [Code of Conduct](https://github.com/SanjulaGanepola/github-local-actions/blob/main/CODE_OF_CONDUCT.md) guidelines. Let’s keep this a welcoming and fun space for everyone to collaborate!
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@@ -25,5 +25,7 @@ Thanks so much to everyone [who has contributed](https://github.com/SanjulaGanep
|
|||||||
|
|
||||||
* [@SanjulaGanepola](https://github.com/SanjulaGanepola)
|
* [@SanjulaGanepola](https://github.com/SanjulaGanepola)
|
||||||
* [@ChristopherHX](https://github.com/ChristopherHX)
|
* [@ChristopherHX](https://github.com/ChristopherHX)
|
||||||
|
* [@a11rew](https://github.com/a11rew)
|
||||||
|
* [@atoko](https://github.com/atoko)
|
||||||
|
|
||||||
Want to see your name on this list? Join us and contribute!
|
Want to see your name on this list? Join us and contribute!
|
||||||
@@ -36,6 +36,8 @@ The `Workflows` view is where you can manage and run workflows locally. You have
|
|||||||
2. **Run Single Workflow**: Run a single workflow in the workspace.
|
2. **Run Single Workflow**: Run a single workflow in the workspace.
|
||||||
3. **Run Job**: Run a specific job in a workflow.
|
3. **Run Job**: Run a specific job in a workflow.
|
||||||
4. **Run Event**: Run multiple workflows using a [GitHub event](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows).
|
4. **Run Event**: Run multiple workflows using a [GitHub event](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows).
|
||||||
|
5. **Run Workflow Event**: Run a specific event on a workflow.
|
||||||
|
6. **Run Job Event**: Run a specific event on a job.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "github-local-actions",
|
"name": "github-local-actions",
|
||||||
"version": "1.2.2",
|
"version": "1.2.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "github-local-actions",
|
"name": "github-local-actions",
|
||||||
"version": "1.2.2",
|
"version": "1.2.5",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"child_process": "^1.0.2",
|
"child_process": "^1.0.2",
|
||||||
|
|||||||
93
package.json
93
package.json
@@ -9,11 +9,11 @@
|
|||||||
},
|
},
|
||||||
"publisher": "SanjulaGanepola",
|
"publisher": "SanjulaGanepola",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"version": "1.2.2",
|
"version": "1.2.5",
|
||||||
"repository": {
|
"repository": {
|
||||||
"url": "https://github.com/SanjulaGanepola/github-local-actions"
|
"url": "https://github.com/SanjulaGanepola/github-local-actions"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/SanjulaGanepola/github-local-actions/blob/main/README.md",
|
"homepage": "https://sanjulaganepola.github.io/github-local-actions-docs",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/SanjulaGanepola/github-local-actions/issues"
|
"url": "https://github.com/SanjulaGanepola/github-local-actions/issues"
|
||||||
},
|
},
|
||||||
@@ -131,13 +131,6 @@
|
|||||||
"when": "githubLocalActions:noSettings && workspaceFolderCount > 0"
|
"when": "githubLocalActions:noSettings && workspaceFolderCount > 0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"keybindings": [
|
|
||||||
{
|
|
||||||
"command": "githubLocalActions.runWorkflow",
|
|
||||||
"key": "ctrl+g",
|
|
||||||
"mac": "cmd+g"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"commands": [
|
"commands": [
|
||||||
{
|
{
|
||||||
"category": "GitHub Local Actions",
|
"category": "GitHub Local Actions",
|
||||||
@@ -189,24 +182,36 @@
|
|||||||
"title": "Refresh",
|
"title": "Refresh",
|
||||||
"icon": "$(refresh)"
|
"icon": "$(refresh)"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"category": "GitHub Local Actions",
|
|
||||||
"command": "githubLocalActions.openWorkflow",
|
|
||||||
"title": "Open Workflow",
|
|
||||||
"icon": "$(go-to-file)"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"category": "GitHub Local Actions",
|
"category": "GitHub Local Actions",
|
||||||
"command": "githubLocalActions.runWorkflow",
|
"command": "githubLocalActions.runWorkflow",
|
||||||
"title": "Run Workflow",
|
"title": "Run Workflow",
|
||||||
"icon": "$(debug-start)"
|
"icon": "$(debug-start)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"category": "GitHub Local Actions",
|
||||||
|
"command": "githubLocalActions.runWorkflowEvent",
|
||||||
|
"title": "Run Workflow with Event",
|
||||||
|
"icon": "$(symbol-event)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "GitHub Local Actions",
|
||||||
|
"command": "githubLocalActions.openWorkflow",
|
||||||
|
"title": "Open Workflow",
|
||||||
|
"icon": "$(go-to-file)"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"category": "GitHub Local Actions",
|
"category": "GitHub Local Actions",
|
||||||
"command": "githubLocalActions.runJob",
|
"command": "githubLocalActions.runJob",
|
||||||
"title": "Run Job",
|
"title": "Run Job",
|
||||||
"icon": "$(debug-start)"
|
"icon": "$(debug-start)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"category": "GitHub Local Actions",
|
||||||
|
"command": "githubLocalActions.runJobEvent",
|
||||||
|
"title": "Run Job with Event",
|
||||||
|
"icon": "$(symbol-event)"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"category": "GitHub Local Actions",
|
"category": "GitHub Local Actions",
|
||||||
"command": "githubLocalActions.clearAll",
|
"command": "githubLocalActions.clearAll",
|
||||||
@@ -424,18 +429,26 @@
|
|||||||
"command": "githubLocalActions.refreshWorkflows",
|
"command": "githubLocalActions.refreshWorkflows",
|
||||||
"when": "never"
|
"when": "never"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"command": "githubLocalActions.openWorkflow",
|
|
||||||
"when": "never"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"command": "githubLocalActions.runWorkflow",
|
"command": "githubLocalActions.runWorkflow",
|
||||||
"when": "never"
|
"when": "never"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "githubLocalActions.runWorkflowEvent",
|
||||||
|
"when": "never"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "githubLocalActions.openWorkflow",
|
||||||
|
"when": "never"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "githubLocalActions.runJob",
|
"command": "githubLocalActions.runJob",
|
||||||
"when": "never"
|
"when": "never"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "githubLocalActions.runJobEvent",
|
||||||
|
"when": "never"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "githubLocalActions.clearAll",
|
"command": "githubLocalActions.clearAll",
|
||||||
"when": "never"
|
"when": "never"
|
||||||
@@ -637,20 +650,55 @@
|
|||||||
"group": "inline@1"
|
"group": "inline@1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "githubLocalActions.openWorkflow",
|
"command": "githubLocalActions.runWorkflow",
|
||||||
"when": "view == workflows && viewItem =~ /^githubLocalActions.workflow.*/",
|
"when": "view == workflows && viewItem =~ /^githubLocalActions.workflow.*/",
|
||||||
"group": "inline@0"
|
"group": "inline@0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "githubLocalActions.runWorkflow",
|
"command": "githubLocalActions.runWorkflow",
|
||||||
"when": "view == workflows && viewItem =~ /^githubLocalActions.workflow.*/",
|
"when": "view == workflows && viewItem =~ /^githubLocalActions.workflow.*/",
|
||||||
|
"group": "workflows@0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "githubLocalActions.runWorkflowEvent",
|
||||||
|
"when": "view == workflows && viewItem =~ /^githubLocalActions.workflow.*/",
|
||||||
"group": "inline@1"
|
"group": "inline@1"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "githubLocalActions.runWorkflowEvent",
|
||||||
|
"when": "view == workflows && viewItem =~ /^githubLocalActions.workflow.*/",
|
||||||
|
"group": "workflows@1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "githubLocalActions.openWorkflow",
|
||||||
|
"when": "view == workflows && viewItem =~ /^githubLocalActions.workflow.*/",
|
||||||
|
"group": "inline@2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "githubLocalActions.openWorkflow",
|
||||||
|
"when": "view == workflows && viewItem =~ /^githubLocalActions.workflow.*/",
|
||||||
|
"group": "workflows@2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "githubLocalActions.runJob",
|
"command": "githubLocalActions.runJob",
|
||||||
"when": "view == workflows && viewItem =~ /^githubLocalActions.job.*/",
|
"when": "view == workflows && viewItem =~ /^githubLocalActions.job.*/",
|
||||||
"group": "inline@0"
|
"group": "inline@0"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "githubLocalActions.runJob",
|
||||||
|
"when": "view == workflows && viewItem =~ /^githubLocalActions.job.*/",
|
||||||
|
"group": "jobs@0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "githubLocalActions.runJobEvent",
|
||||||
|
"when": "view == workflows && viewItem =~ /^githubLocalActions.job.*/",
|
||||||
|
"group": "inline@1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "githubLocalActions.runJobEvent",
|
||||||
|
"when": "view == workflows && viewItem =~ /^githubLocalActions.job.*/",
|
||||||
|
"group": "jobs@1"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "githubLocalActions.clearAll",
|
"command": "githubLocalActions.clearAll",
|
||||||
"when": "view == history && viewItem =~ /^githubLocalActions.workspaceFolderHistory.*/ && workspaceFolderCount > 1",
|
"when": "view == history && viewItem =~ /^githubLocalActions.workspaceFolderHistory.*/ && workspaceFolderCount > 1",
|
||||||
@@ -786,6 +834,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "act"
|
"default": "act"
|
||||||
},
|
},
|
||||||
|
"githubLocalActions.workflowsDirectory": {
|
||||||
|
"markdownDescription": "The relative path to the directory containing your workflows. By default, this will be `.github/workflows`.",
|
||||||
|
"type": "string",
|
||||||
|
"default": ".github/workflows"
|
||||||
|
},
|
||||||
"githubLocalActions.dockerDesktopPath": {
|
"githubLocalActions.dockerDesktopPath": {
|
||||||
"markdownDescription": "The path to your Docker Desktop executable (used for Windows and MacOS). To start Docker Engine from the `Components` view, this application will be launched. Refer to the default path based on OS:\n\n* **Windows**: `C:/Program Files/Docker/Docker/Docker Desktop.exe`\n\n* **MacOS**: `/Applications/Docker.app`",
|
"markdownDescription": "The path to your Docker Desktop executable (used for Windows and MacOS). To start Docker Engine from the `Components` view, this application will be launched. Refer to the default path based on OS:\n\n* **Windows**: `C:/Program Files/Docker/Docker/Docker Desktop.exe`\n\n* **MacOS**: `/Applications/Docker.app`",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
2935
src/Extension Host.log
Normal file
2935
src/Extension Host.log
Normal file
File diff suppressed because it is too large
Load Diff
959
src/act.ts
959
src/act.ts
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
import * as childProcess from "child_process";
|
import * as childProcess from "child_process";
|
||||||
import { commands, env, extensions, QuickPickItemKind, ShellExecution, TaskGroup, TaskPanelKind, TaskRevealKind, tasks, TaskScope, ThemeIcon, Uri, window } from "vscode";
|
import { commands, env, extensions, QuickPickItemKind, ShellExecution, TaskGroup, TaskPanelKind, TaskRevealKind, tasks, TaskScope, ThemeIcon, Uri, window } from "vscode";
|
||||||
import { Act } from "./act";
|
import { Act, Option } from "./act";
|
||||||
import { ConfigurationManager, Platform, Section } from "./configurationManager";
|
import { ConfigurationManager, Platform, Section } from "./configurationManager";
|
||||||
import { act, componentsTreeDataProvider } from "./extension";
|
import { act, componentsTreeDataProvider } from "./extension";
|
||||||
import ComponentsTreeDataProvider from "./views/components/componentsTreeDataProvider";
|
import ComponentsTreeDataProvider from "./views/components/componentsTreeDataProvider";
|
||||||
@@ -39,7 +39,7 @@ export class ComponentsManager {
|
|||||||
async getComponents(): Promise<Component<CliStatus | ExtensionStatus>[]> {
|
async getComponents(): Promise<Component<CliStatus | ExtensionStatus>[]> {
|
||||||
const components: Component<CliStatus | ExtensionStatus>[] = [];
|
const components: Component<CliStatus | ExtensionStatus>[] = [];
|
||||||
|
|
||||||
const actCliInfo = await this.getCliInfo(`${Act.getActCommand()} --version`, ComponentsManager.actVersionRegExp, false, false);
|
const actCliInfo = await this.getCliInfo(`${Act.getActCommand()} ${Option.Version}`, ComponentsManager.actVersionRegExp, false, false);
|
||||||
components.push({
|
components.push({
|
||||||
name: 'nektos/act',
|
name: 'nektos/act',
|
||||||
icon: 'terminal',
|
icon: 'terminal',
|
||||||
@@ -109,21 +109,13 @@ export class ComponentsManager {
|
|||||||
|
|
||||||
if (selectedPrebuiltExecutable) {
|
if (selectedPrebuiltExecutable) {
|
||||||
await env.openExternal(Uri.parse(selectedPrebuiltExecutable.link));
|
await env.openExternal(Uri.parse(selectedPrebuiltExecutable.link));
|
||||||
window.showInformationMessage('Unpack the executable and move it to your desired location. Once nektos/act is successfully installed, add it to your shell\'s PATH and then refresh the components view.', 'Refresh').then(async value => {
|
// Silently handle act installation instructions
|
||||||
if (value === 'Refresh') {
|
|
||||||
componentsTreeDataProvider.refresh();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
act.updateActCommand(Act.defaultActCommand);
|
act.updateActCommand(Act.defaultActCommand);
|
||||||
} else if (selectedInstallationMethod.link) {
|
} else if (selectedInstallationMethod.link) {
|
||||||
await env.openExternal(Uri.parse(selectedInstallationMethod.link));
|
await env.openExternal(Uri.parse(selectedInstallationMethod.link));
|
||||||
window.showInformationMessage('Once nektos/act is successfully installed, add it to your shell\'s PATH and then refresh the components view.', 'Refresh').then(async value => {
|
// Silently handle act installation instructions
|
||||||
if (value === 'Refresh') {
|
|
||||||
componentsTreeDataProvider.refresh();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
act.updateActCommand(Act.defaultActCommand);
|
act.updateActCommand(Act.defaultActCommand);
|
||||||
} else {
|
} else {
|
||||||
@@ -176,11 +168,7 @@ export class ComponentsManager {
|
|||||||
execution: new ShellExecution('systemctl start docker', { executable: env.shell })
|
execution: new ShellExecution('systemctl start docker', { executable: env.shell })
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
window.showErrorMessage(`Invalid environment: ${process.platform}`, 'Report an Issue').then(async value => {
|
// Silently handle invalid environment
|
||||||
if (value === 'Report an Issue') {
|
|
||||||
await commands.executeCommand('githubLocalActions.reportAnIssue');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,20 +189,13 @@ export class ComponentsManager {
|
|||||||
const options = process.platform === Platform.linux ?
|
const options = process.platform === Platform.linux ?
|
||||||
['Refresh'] :
|
['Refresh'] :
|
||||||
['Refresh', 'Configure Docker Desktop Path'];
|
['Refresh', 'Configure Docker Desktop Path'];
|
||||||
window.showInformationMessage(`Once Docker Engine is successfully started, refresh the components view. ${verificationMessage}`, ...options).then(async value => {
|
// Silently handle Docker Engine start message
|
||||||
if (value === 'Refresh') {
|
|
||||||
componentsTreeDataProvider.refresh();
|
|
||||||
} else if (value === 'Configure Docker Desktop Path') {
|
|
||||||
await commands.executeCommand('workbench.action.openSettings', ConfigurationManager.getSearchTerm(Section.dockerDesktopPath));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
fixPermissions: async () => {
|
fixPermissions: async () => {
|
||||||
if (process.platform === Platform.linux) {
|
if (process.platform === Platform.linux) {
|
||||||
window.showInformationMessage('By default, the Docker daemon binds to a Unix socket owned by the root user. To manage Docker as a non-root user, a Unix group called "docker" should be created with your user added to it.', 'Proceed', 'Learn More').then(async value => {
|
// Silently handle Docker permissions
|
||||||
if (value === 'Proceed') {
|
|
||||||
await tasks.executeTask({
|
await tasks.executeTask({
|
||||||
name: 'Docker Engine',
|
name: 'Docker Engine',
|
||||||
detail: 'Fix Docker Engine Permissions',
|
detail: 'Fix Docker Engine Permissions',
|
||||||
@@ -249,15 +230,12 @@ export class ComponentsManager {
|
|||||||
if (dockerCliInfo.status !== newDockerCliInfo.status) {
|
if (dockerCliInfo.status !== newDockerCliInfo.status) {
|
||||||
componentsTreeDataProvider.refresh();
|
componentsTreeDataProvider.refresh();
|
||||||
} else {
|
} else {
|
||||||
window.showInformationMessage('You may need to restart your PC for these changes to take affect.');
|
// Silently handle restart message
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (value === 'Learn More') {
|
|
||||||
await env.openExternal(Uri.parse('https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user'));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Silently handle learn more option
|
||||||
} else {
|
} else {
|
||||||
window.showErrorMessage(`Permissions cannot be automatically fixed for ${process.platform} environment.`);
|
// Silently handle permissions cannot be fixed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,41 +1,54 @@
|
|||||||
import { ConfigurationTarget, workspace } from 'vscode';
|
import { ConfigurationTarget, workspace } from "vscode";
|
||||||
import { Act } from './act';
|
import { Act } from "./act";
|
||||||
|
|
||||||
export enum Platform {
|
export enum Platform {
|
||||||
windows = 'win32',
|
windows = "win32",
|
||||||
mac = 'darwin',
|
mac = "darwin",
|
||||||
linux = 'linux'
|
linux = "linux",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Section {
|
export enum Section {
|
||||||
actCommand = 'actCommand',
|
actCommand = "actCommand",
|
||||||
dockerDesktopPath = 'dockerDesktopPath'
|
dockerDesktopPath = "dockerDesktopPath",
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace ConfigurationManager {
|
export namespace ConfigurationManager {
|
||||||
export const group: string = 'githubLocalActions';
|
export const group: string = "githubLocalActions";
|
||||||
export const searchPrefix: string = '@ext:sanjulaganepola.github-local-actions';
|
export const searchPrefix: string =
|
||||||
|
"@ext:sanjulaganepola.github-local-actions";
|
||||||
|
|
||||||
export async function initialize(): Promise<void> {
|
export async function initialize(): Promise<void> {
|
||||||
let dockerDesktopPath = ConfigurationManager.get<string>(Section.dockerDesktopPath);
|
let actCommand = ConfigurationManager.get<string>(Section.actCommand);
|
||||||
|
if (!actCommand) {
|
||||||
|
await ConfigurationManager.set(Section.actCommand, Act.defaultActCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't set a default workflows directory to allow multi-directory support
|
||||||
|
// let workflowsDirectory = ConfigurationManager.get<string>(Section.workflowsDirectory);
|
||||||
|
// if (!workflowsDirectory) {
|
||||||
|
// await ConfigurationManager.set(Section.workflowsDirectory, WorkflowsManager.defaultWorkflowsDirectory);
|
||||||
|
// }
|
||||||
|
|
||||||
|
let dockerDesktopPath = ConfigurationManager.get<string>(
|
||||||
|
Section.dockerDesktopPath
|
||||||
|
);
|
||||||
if (!dockerDesktopPath) {
|
if (!dockerDesktopPath) {
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
case Platform.windows:
|
case Platform.windows:
|
||||||
dockerDesktopPath = 'C:/Program Files/Docker/Docker/Docker Desktop.exe';
|
dockerDesktopPath =
|
||||||
|
"C:/Program Files/Docker/Docker/Docker Desktop.exe";
|
||||||
break;
|
break;
|
||||||
case Platform.mac:
|
case Platform.mac:
|
||||||
dockerDesktopPath = '/Applications/Docker.app';
|
dockerDesktopPath = "/Applications/Docker.app";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ConfigurationManager.set(Section.dockerDesktopPath, dockerDesktopPath);
|
await ConfigurationManager.set(
|
||||||
}
|
Section.dockerDesktopPath,
|
||||||
|
dockerDesktopPath
|
||||||
let actCommand = ConfigurationManager.get<string>(Section.actCommand);
|
);
|
||||||
if (!actCommand) {
|
|
||||||
await ConfigurationManager.set(Section.actCommand, Act.defaultActCommand);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,10 +57,14 @@ export namespace ConfigurationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function get<T>(section: Section): T | undefined {
|
export function get<T>(section: Section): T | undefined {
|
||||||
return workspace.getConfiguration(ConfigurationManager.group).get(section) as T;
|
return workspace
|
||||||
|
.getConfiguration(ConfigurationManager.group)
|
||||||
|
.get(section) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function set(section: Section, value: any): Promise<void> {
|
export async function set(section: Section, value: any): Promise<void> {
|
||||||
return await workspace.getConfiguration(ConfigurationManager.group).update(section, value, ConfigurationTarget.Global);
|
return await workspace
|
||||||
|
.getConfiguration(ConfigurationManager.group)
|
||||||
|
.update(section, value, ConfigurationTarget.Global);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
154
src/extension.ts
154
src/extension.ts
@@ -1,15 +1,23 @@
|
|||||||
import * as vscode from 'vscode';
|
import {
|
||||||
import { commands, env, TreeCheckboxChangeEvent, Uri, window, workspace } from 'vscode';
|
commands,
|
||||||
import { Act } from './act';
|
env,
|
||||||
import { ConfigurationManager } from './configurationManager';
|
ExtensionContext,
|
||||||
import ComponentsTreeDataProvider from './views/components/componentsTreeDataProvider';
|
TreeCheckboxChangeEvent,
|
||||||
import { DecorationProvider } from './views/decorationProvider';
|
Uri,
|
||||||
import { GithubLocalActionsTreeItem } from './views/githubLocalActionsTreeItem';
|
window,
|
||||||
import HistoryTreeDataProvider from './views/history/historyTreeDataProvider';
|
workspace,
|
||||||
import SettingTreeItem from './views/settings/setting';
|
} from "vscode";
|
||||||
import SettingsTreeDataProvider from './views/settings/settingsTreeDataProvider';
|
import { Act } from "./act";
|
||||||
import WorkflowsTreeDataProvider from './views/workflows/workflowsTreeDataProvider';
|
import { ConfigurationManager, Section } from "./configurationManager";
|
||||||
import { WorkflowsManager } from './workflowsManager';
|
import { IssueHandler } from "./issueHandler";
|
||||||
|
import ComponentsTreeDataProvider from "./views/components/componentsTreeDataProvider";
|
||||||
|
import { DecorationProvider } from "./views/decorationProvider";
|
||||||
|
import { GithubLocalActionsTreeItem } from "./views/githubLocalActionsTreeItem";
|
||||||
|
import HistoryTreeDataProvider from "./views/history/historyTreeDataProvider";
|
||||||
|
import SettingTreeItem from "./views/settings/setting";
|
||||||
|
import SettingsTreeDataProvider from "./views/settings/settingsTreeDataProvider";
|
||||||
|
import WorkflowsTreeDataProvider from "./views/workflows/workflowsTreeDataProvider";
|
||||||
|
import { WorkflowsManager } from "./workflowsManager";
|
||||||
|
|
||||||
export let act: Act;
|
export let act: Act;
|
||||||
export let componentsTreeDataProvider: ComponentsTreeDataProvider;
|
export let componentsTreeDataProvider: ComponentsTreeDataProvider;
|
||||||
@@ -17,27 +25,99 @@ export let workflowsTreeDataProvider: WorkflowsTreeDataProvider;
|
|||||||
export let historyTreeDataProvider: HistoryTreeDataProvider;
|
export let historyTreeDataProvider: HistoryTreeDataProvider;
|
||||||
export let settingsTreeDataProvider: SettingsTreeDataProvider;
|
export let settingsTreeDataProvider: SettingsTreeDataProvider;
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
export function activate(context: ExtensionContext) {
|
||||||
console.log('Congratulations, your extension "github-local-actions" is now active!');
|
console.log(
|
||||||
|
'Congratulations, your extension "github-local-actions" is now active!'
|
||||||
|
);
|
||||||
|
|
||||||
act = new Act(context);
|
act = new Act(context);
|
||||||
|
|
||||||
// Create tree views
|
// Create tree views
|
||||||
const decorationProvider = new DecorationProvider();
|
const decorationProvider = new DecorationProvider();
|
||||||
componentsTreeDataProvider = new ComponentsTreeDataProvider(context);
|
componentsTreeDataProvider = new ComponentsTreeDataProvider(context);
|
||||||
const componentsTreeView = window.createTreeView(ComponentsTreeDataProvider.VIEW_ID, { treeDataProvider: componentsTreeDataProvider, showCollapseAll: true });
|
const componentsTreeView = window.createTreeView(
|
||||||
|
ComponentsTreeDataProvider.VIEW_ID,
|
||||||
|
{ treeDataProvider: componentsTreeDataProvider, showCollapseAll: true }
|
||||||
|
);
|
||||||
|
|
||||||
workflowsTreeDataProvider = new WorkflowsTreeDataProvider(context);
|
workflowsTreeDataProvider = new WorkflowsTreeDataProvider(context);
|
||||||
const workflowsTreeView = window.createTreeView(WorkflowsTreeDataProvider.VIEW_ID, { treeDataProvider: workflowsTreeDataProvider, showCollapseAll: true });
|
const workflowsTreeView = window.createTreeView(
|
||||||
|
WorkflowsTreeDataProvider.VIEW_ID,
|
||||||
|
{ treeDataProvider: workflowsTreeDataProvider, showCollapseAll: true }
|
||||||
|
);
|
||||||
|
|
||||||
historyTreeDataProvider = new HistoryTreeDataProvider(context);
|
historyTreeDataProvider = new HistoryTreeDataProvider(context);
|
||||||
const historyTreeView = window.createTreeView(HistoryTreeDataProvider.VIEW_ID, { treeDataProvider: historyTreeDataProvider, showCollapseAll: true });
|
const historyTreeView = window.createTreeView(
|
||||||
|
HistoryTreeDataProvider.VIEW_ID,
|
||||||
|
{ treeDataProvider: historyTreeDataProvider, showCollapseAll: true }
|
||||||
|
);
|
||||||
settingsTreeDataProvider = new SettingsTreeDataProvider(context);
|
settingsTreeDataProvider = new SettingsTreeDataProvider(context);
|
||||||
const settingsTreeView = window.createTreeView(SettingsTreeDataProvider.VIEW_ID, { treeDataProvider: settingsTreeDataProvider, showCollapseAll: true });
|
const settingsTreeView = window.createTreeView(
|
||||||
settingsTreeView.onDidChangeCheckboxState(async (event: TreeCheckboxChangeEvent<GithubLocalActionsTreeItem>) => {
|
SettingsTreeDataProvider.VIEW_ID,
|
||||||
await settingsTreeDataProvider.onDidChangeCheckboxState(event as TreeCheckboxChangeEvent<SettingTreeItem>);
|
{ treeDataProvider: settingsTreeDataProvider, showCollapseAll: true }
|
||||||
});
|
);
|
||||||
|
settingsTreeView.onDidChangeCheckboxState(
|
||||||
|
async (event: TreeCheckboxChangeEvent<GithubLocalActionsTreeItem>) => {
|
||||||
|
await settingsTreeDataProvider.onDidChangeCheckboxState(
|
||||||
|
event as TreeCheckboxChangeEvent<SettingTreeItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Create file watcher
|
// Create file watcher
|
||||||
const workflowsFileWatcher = workspace.createFileSystemWatcher(`**/${WorkflowsManager.WORKFLOWS_DIRECTORY}/*.{${WorkflowsManager.YML_EXTENSION},${WorkflowsManager.YAML_EXTENSION}}`);
|
let workflowsFileWatcher = setupFileWatcher(context);
|
||||||
|
|
||||||
|
// Initialize configurations
|
||||||
|
ConfigurationManager.initialize();
|
||||||
|
workspace.onDidChangeConfiguration(async (event) => {
|
||||||
|
if (event.affectsConfiguration(ConfigurationManager.group)) {
|
||||||
|
await ConfigurationManager.initialize();
|
||||||
|
|
||||||
|
if (
|
||||||
|
event.affectsConfiguration(
|
||||||
|
`${ConfigurationManager.group}.${Section.actCommand}`
|
||||||
|
) ||
|
||||||
|
event.affectsConfiguration(
|
||||||
|
`${ConfigurationManager.group}.${Section.dockerDesktopPath}`
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
componentsTreeDataProvider.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
context.subscriptions.push(
|
||||||
|
componentsTreeView,
|
||||||
|
workflowsTreeView,
|
||||||
|
historyTreeView,
|
||||||
|
settingsTreeView,
|
||||||
|
window.registerFileDecorationProvider(decorationProvider),
|
||||||
|
workflowsFileWatcher,
|
||||||
|
commands.registerCommand(
|
||||||
|
"githubLocalActions.viewDocumentation",
|
||||||
|
async () => {
|
||||||
|
await env.openExternal(
|
||||||
|
Uri.parse(
|
||||||
|
"https://sanjulaganepola.github.io/github-local-actions-docs"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
commands.registerCommand("githubLocalActions.reportAnIssue", async () => {
|
||||||
|
await IssueHandler.openBugReport(context);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupFileWatcher(context: ExtensionContext) {
|
||||||
|
const workflowsDirectories = WorkflowsManager.getWorkflowsDirectories();
|
||||||
|
const fileWatchers: any[] = [];
|
||||||
|
|
||||||
|
for (const workflowsDirectory of workflowsDirectories) {
|
||||||
|
const workflowsFileWatcher = workspace.createFileSystemWatcher(
|
||||||
|
`**/${workflowsDirectory}/*.{${WorkflowsManager.ymlExtension},${WorkflowsManager.yamlExtension}}`
|
||||||
|
);
|
||||||
|
|
||||||
workflowsFileWatcher.onDidCreate(() => {
|
workflowsFileWatcher.onDidCreate(() => {
|
||||||
workflowsTreeDataProvider.refresh();
|
workflowsTreeDataProvider.refresh();
|
||||||
settingsTreeDataProvider.refresh();
|
settingsTreeDataProvider.refresh();
|
||||||
@@ -51,29 +131,15 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
settingsTreeDataProvider.refresh();
|
settingsTreeDataProvider.refresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize configurations
|
fileWatchers.push(workflowsFileWatcher);
|
||||||
ConfigurationManager.initialize();
|
|
||||||
workspace.onDidChangeConfiguration(async event => {
|
|
||||||
if (event.affectsConfiguration(ConfigurationManager.group)) {
|
|
||||||
await ConfigurationManager.initialize();
|
|
||||||
componentsTreeDataProvider.refresh();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
context.subscriptions.push(
|
// Return a disposable that disposes all watchers
|
||||||
componentsTreeView,
|
return {
|
||||||
workflowsTreeView,
|
dispose: () => {
|
||||||
historyTreeView,
|
fileWatchers.forEach((watcher) => watcher.dispose());
|
||||||
settingsTreeView,
|
},
|
||||||
window.registerFileDecorationProvider(decorationProvider),
|
};
|
||||||
workflowsFileWatcher,
|
|
||||||
commands.registerCommand('githubLocalActions.viewDocumentation', async () => {
|
|
||||||
await env.openExternal(Uri.parse('https://nektosact.com'));
|
|
||||||
}),
|
|
||||||
commands.registerCommand('githubLocalActions.reportAnIssue', async () => {
|
|
||||||
await env.openExternal(Uri.parse('https://github.com/SanjulaGanepola/github-local-actions/issues'));
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deactivate() { }
|
export function deactivate() {}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export interface Response<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface GithubRepository {
|
export interface GithubRepository {
|
||||||
|
remoteOriginUrl: string,
|
||||||
owner: string,
|
owner: string,
|
||||||
repo: string
|
repo: string
|
||||||
}
|
}
|
||||||
@@ -24,7 +25,7 @@ export interface GithubVariable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class GitHubManager {
|
export class GitHubManager {
|
||||||
async getRepository(workspaceFolder: WorkspaceFolder, command: string, args: any[]): Promise<GithubRepository | undefined> {
|
async getRepository(workspaceFolder: WorkspaceFolder, suppressNotFoundErrors: boolean, tryAgainOptions?: { command: string, args: any[] }): Promise<GithubRepository | undefined> {
|
||||||
const gitApi = extensions.getExtension<GitExtension>('vscode.git')?.exports.getAPI(1);
|
const gitApi = extensions.getExtension<GitExtension>('vscode.git')?.exports.getAPI(1);
|
||||||
if (gitApi) {
|
if (gitApi) {
|
||||||
if (gitApi.state === 'initialized') {
|
if (gitApi.state === 'initialized') {
|
||||||
@@ -38,24 +39,25 @@ export class GitHubManager {
|
|||||||
const parsedParentPath = path.parse(parsedPath.dir);
|
const parsedParentPath = path.parse(parsedPath.dir);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
remoteOriginUrl: remoteOriginUrl,
|
||||||
owner: parsedParentPath.name,
|
owner: parsedParentPath.name,
|
||||||
repo: parsedPath.name
|
repo: parsedPath.name
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
window.showErrorMessage('Remote GitHub URL not found.');
|
if (!suppressNotFoundErrors) {
|
||||||
|
// Silently handle GitHub URL not found
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
window.showErrorMessage(`${workspaceFolder.name} does not have a Git repository`);
|
if (!suppressNotFoundErrors) {
|
||||||
|
// Silently handle no Git repository
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
window.showErrorMessage('Git extension is still being initialized. Please try again later.', 'Try Again').then(async value => {
|
// Silently handle Git extension initialization
|
||||||
if (value && value === 'Try Again') {
|
|
||||||
await commands.executeCommand(command, ...args);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
window.showErrorMessage('Failed to load VS Code Git API.');
|
// Silently handle Git API load failure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +151,7 @@ export class GitHubManager {
|
|||||||
try {
|
try {
|
||||||
return await authentication.getSession('github', ['repo'], { createIfNone: true });
|
return await authentication.getSession('github', ['repo'], { createIfNone: true });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
window.showErrorMessage(`Failed to authenticate to GitHub. Error ${error}`);
|
// Silently handle GitHub authentication error
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,34 +160,7 @@ export class GitHubManager {
|
|||||||
return new Promise<string | undefined>((resolve, reject) => {
|
return new Promise<string | undefined>((resolve, reject) => {
|
||||||
childProcess.exec('gh auth token', (error, stdout, stderr) => {
|
childProcess.exec('gh auth token', (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
const errorMessage = (String(stderr).charAt(0).toUpperCase() + String(stderr).slice(1)).trim();
|
// Silently handle GitHub CLI authentication error
|
||||||
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);
|
resolve(undefined);
|
||||||
} else {
|
} else {
|
||||||
resolve(stdout.trim());
|
resolve(stdout.trim());
|
||||||
|
|||||||
@@ -49,11 +49,26 @@ export enum HistoryStatus {
|
|||||||
|
|
||||||
export class HistoryManager {
|
export class HistoryManager {
|
||||||
storageManager: StorageManager;
|
storageManager: StorageManager;
|
||||||
workspaceHistory: { [path: string]: History[] };
|
private workspaceHistory: { [path: string]: History[] };
|
||||||
|
|
||||||
|
|
||||||
constructor(storageManager: StorageManager) {
|
constructor(storageManager: StorageManager) {
|
||||||
this.storageManager = storageManager;
|
this.storageManager = storageManager;
|
||||||
const workspaceHistory = this.storageManager.get<{ [path: string]: History[] }>(StorageKey.WorkspaceHistory) || {};
|
this.workspaceHistory = {};
|
||||||
|
this.syncHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getWorkspaceHistory() {
|
||||||
|
if (!this.workspaceHistory) {
|
||||||
|
await this.syncHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.workspaceHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
async syncHistory() {
|
||||||
|
const workspaceHistory = await this.storageManager.get<{ [path: string]: History[] }>(StorageKey.WorkspaceHistory) || {};
|
||||||
for (const [path, historyLogs] of Object.entries(workspaceHistory)) {
|
for (const [path, historyLogs] of Object.entries(workspaceHistory)) {
|
||||||
workspaceHistory[path] = historyLogs.map(history => {
|
workspaceHistory[path] = historyLogs.map(history => {
|
||||||
history.jobs?.forEach((job, jobIndex) => {
|
history.jobs?.forEach((job, jobIndex) => {
|
||||||
@@ -79,27 +94,32 @@ export class HistoryManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.workspaceHistory = workspaceHistory;
|
this.workspaceHistory = workspaceHistory;
|
||||||
}
|
};
|
||||||
|
|
||||||
async clearAll(workspaceFolder: WorkspaceFolder) {
|
async clearAll(workspaceFolder: WorkspaceFolder) {
|
||||||
const existingHistory = this.workspaceHistory[workspaceFolder.uri.fsPath];
|
await this.syncHistory();
|
||||||
|
const existingHistory = this.workspaceHistory?.[workspaceFolder.uri.fsPath] ?? [];
|
||||||
for (const history of existingHistory) {
|
for (const history of existingHistory) {
|
||||||
try {
|
try {
|
||||||
await workspace.fs.delete(Uri.file(history.logPath));
|
await workspace.fs.delete(Uri.file(history.logPath));
|
||||||
} catch (error: any) { }
|
} catch (error: any) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.workspaceHistory) {
|
||||||
this.workspaceHistory[workspaceFolder.uri.fsPath] = [];
|
this.workspaceHistory[workspaceFolder.uri.fsPath] = [];
|
||||||
|
}
|
||||||
historyTreeDataProvider.refresh();
|
historyTreeDataProvider.refresh();
|
||||||
await this.storageManager.update(StorageKey.WorkspaceHistory, this.workspaceHistory);
|
await this.storageManager.update(StorageKey.WorkspaceHistory, this.workspaceHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
async viewOutput(history: History) {
|
async viewOutput(history: History) {
|
||||||
|
await this.syncHistory();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const document = await workspace.openTextDocument(history.logPath);
|
const document = await workspace.openTextDocument(history.logPath);
|
||||||
await window.showTextDocument(document);
|
await window.showTextDocument(document);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
window.showErrorMessage(`${history.name} #${history.count} log file not found`);
|
// Silently handle log file not found
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,9 +132,10 @@ export class HistoryManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async remove(history: History) {
|
async remove(history: History) {
|
||||||
const historyIndex = this.workspaceHistory[history.commandArgs.path].findIndex(workspaceHistory => workspaceHistory.index === history.index);
|
await this.syncHistory();
|
||||||
|
const historyIndex = (this.workspaceHistory?.[history.commandArgs.path] ?? []).findIndex(workspaceHistory => workspaceHistory.index === history.index);
|
||||||
if (historyIndex > -1) {
|
if (historyIndex > -1) {
|
||||||
this.workspaceHistory[history.commandArgs.path].splice(historyIndex, 1);
|
(this.workspaceHistory?.[history.commandArgs.path] ?? []).splice(historyIndex, 1);
|
||||||
await this.storageManager.update(StorageKey.WorkspaceHistory, this.workspaceHistory);
|
await this.storageManager.update(StorageKey.WorkspaceHistory, this.workspaceHistory);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
195
src/issueHandler.ts
Normal file
195
src/issueHandler.ts
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
import * as childProcess from "child_process";
|
||||||
|
import * as fs from "fs/promises";
|
||||||
|
import * as path from "path";
|
||||||
|
import { env, ExtensionContext, ProgressLocation, QuickPickItem, ThemeIcon, Uri, window, workspace } from "vscode";
|
||||||
|
import { Act, Option } from "./act";
|
||||||
|
import { act } from "./extension";
|
||||||
|
|
||||||
|
interface BugReport {
|
||||||
|
githubLocalActionsVersion?: string
|
||||||
|
actVersion?: string,
|
||||||
|
githubRepositoryLink?: string,
|
||||||
|
workflowContent?: string,
|
||||||
|
actCommandUsed?: string,
|
||||||
|
actCommandOutput?: string,
|
||||||
|
actBugReport?: string
|
||||||
|
bugDescription?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to map bug report keys to ids and titles in the issue template
|
||||||
|
const bugReportToTemplateMap: Record<keyof BugReport, { id: string, title: string }> = {
|
||||||
|
githubLocalActionsVersion: { id: 'github-local-actions-version', title: 'Github Local Actions Version' },
|
||||||
|
actVersion: { id: 'act-version', title: 'Act Version' },
|
||||||
|
githubRepositoryLink: { id: 'github-repository-link', title: 'GitHub Repository Link' },
|
||||||
|
workflowContent: { id: 'workflow-content', title: 'Workflow Content' },
|
||||||
|
actCommandUsed: { id: 'act-command-used', title: 'Act Command Used' },
|
||||||
|
actCommandOutput: { id: 'act-command-output', title: 'Act Command Output' },
|
||||||
|
actBugReport: { id: 'act-bug-report', title: 'Act Bug Report' },
|
||||||
|
bugDescription: { id: 'bug-description', title: 'Bug Description' }
|
||||||
|
};
|
||||||
|
|
||||||
|
export namespace IssueHandler {
|
||||||
|
export async function openBugReport(context: ExtensionContext) {
|
||||||
|
try {
|
||||||
|
const bugReport = await generateBugReport(context);
|
||||||
|
if (bugReport) {
|
||||||
|
const params = Object.entries(bugReport)
|
||||||
|
.filter(([key, value]) => value !== undefined && value !== '')
|
||||||
|
.map(([key, value]) => `${encodeURIComponent(bugReportToTemplateMap[key as keyof BugReport].id)}=${encodeURIComponent(value)}`)
|
||||||
|
.join('&');
|
||||||
|
|
||||||
|
const bugReportUrl: string = 'https://github.com/SanjulaGanepola/github-local-actions/issues/new?assignees=&labels=bug&projects=&template=1-bug_report.yml';
|
||||||
|
const urlWithParams = params ? `${bugReportUrl}&${params}` : bugReportUrl;
|
||||||
|
await env.openExternal(Uri.parse(urlWithParams));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await env.openExternal(Uri.parse('https://github.com/SanjulaGanepola/github-local-actions/issues'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateBugReport(context: ExtensionContext): Promise<BugReport | undefined> {
|
||||||
|
return await window.withProgress({ location: ProgressLocation.Notification, title: 'Generating bug report...' }, async () => {
|
||||||
|
const fullBugReport: BugReport = {};
|
||||||
|
const infoItems: (QuickPickItem & { key: keyof BugReport })[] = [];
|
||||||
|
|
||||||
|
// Get extension version
|
||||||
|
const githubLocalActionsVersion = context.extension.packageJSON.version;
|
||||||
|
if (githubLocalActionsVersion) {
|
||||||
|
fullBugReport.githubLocalActionsVersion = `v${githubLocalActionsVersion}`;
|
||||||
|
infoItems.push({
|
||||||
|
label: bugReportToTemplateMap['githubLocalActionsVersion'].title,
|
||||||
|
description: fullBugReport.githubLocalActionsVersion,
|
||||||
|
iconPath: new ThemeIcon('robot'),
|
||||||
|
picked: true,
|
||||||
|
key: 'githubLocalActionsVersion'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get act version
|
||||||
|
const actVersion = (await act.componentsManager.getComponents()).find(component => component.name === 'nektos/act')?.version;
|
||||||
|
if (actVersion) {
|
||||||
|
fullBugReport.actVersion = `v${actVersion}`;
|
||||||
|
infoItems.push({
|
||||||
|
label: bugReportToTemplateMap['actVersion'].title,
|
||||||
|
description: fullBugReport.actVersion,
|
||||||
|
iconPath: new ThemeIcon('terminal'),
|
||||||
|
picked: true,
|
||||||
|
key: 'actVersion'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let isWorkflowFound: boolean = false;
|
||||||
|
const activeEditor = window.activeTextEditor;
|
||||||
|
const workspaceFolder = activeEditor ?
|
||||||
|
workspace.getWorkspaceFolder(activeEditor.document.uri) :
|
||||||
|
(workspace.workspaceFolders && workspace.workspaceFolders.length > 0 ? workspace.workspaceFolders[0] : undefined);
|
||||||
|
if (workspaceFolder) {
|
||||||
|
// Get repository link
|
||||||
|
const repository = await act.settingsManager.githubManager.getRepository(workspaceFolder, true);
|
||||||
|
const githubRepositoryLink = repository?.remoteOriginUrl;
|
||||||
|
if (githubRepositoryLink) {
|
||||||
|
fullBugReport.githubRepositoryLink = githubRepositoryLink;
|
||||||
|
infoItems.push({
|
||||||
|
label: bugReportToTemplateMap['githubRepositoryLink'].title,
|
||||||
|
description: fullBugReport.githubRepositoryLink,
|
||||||
|
iconPath: new ThemeIcon('link'),
|
||||||
|
picked: true,
|
||||||
|
key: 'githubRepositoryLink'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeEditor) {
|
||||||
|
const workflows = await act.workflowsManager.getWorkflows(workspaceFolder);
|
||||||
|
const workflow = workflows.find(workflow => workflow.uri.fsPath === activeEditor.document.uri.fsPath);
|
||||||
|
if (workflow) {
|
||||||
|
isWorkflowFound = true;
|
||||||
|
|
||||||
|
// Get workflow content
|
||||||
|
const workflowContent = workflow?.fileContent;
|
||||||
|
if (workflowContent) {
|
||||||
|
fullBugReport.workflowContent = workflowContent;
|
||||||
|
infoItems.push({
|
||||||
|
label: bugReportToTemplateMap['workflowContent'].title,
|
||||||
|
description: path.parse(workflow.uri.fsPath).base,
|
||||||
|
iconPath: new ThemeIcon('file'),
|
||||||
|
picked: true,
|
||||||
|
key: 'workflowContent'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflowHistory = ((await act.historyManager.getWorkspaceHistory())[workspaceFolder.uri.fsPath] ?? []).filter(history => history.commandArgs.workflow?.uri.fsPath === workflow.uri.fsPath);
|
||||||
|
if (workflowHistory.length > 0) {
|
||||||
|
// Get last act command
|
||||||
|
const settings = await act.settingsManager.getSettings(workspaceFolder, true);
|
||||||
|
const history = workflowHistory[workflowHistory.length - 1];
|
||||||
|
const actCommandUsed = (await act.buildActCommand(settings, history.commandArgs.options)).displayCommand;
|
||||||
|
if (actCommandUsed) {
|
||||||
|
fullBugReport.actCommandUsed = actCommandUsed;
|
||||||
|
infoItems.push({
|
||||||
|
label: bugReportToTemplateMap['actCommandUsed'].title,
|
||||||
|
description: `${history.name} #${history.count}`,
|
||||||
|
iconPath: new ThemeIcon('code'),
|
||||||
|
picked: true,
|
||||||
|
key: 'actCommandUsed'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get last act command output
|
||||||
|
const actCommandOutput = await fs.readFile(history.logPath, 'utf8');
|
||||||
|
if (actCommandOutput) {
|
||||||
|
fullBugReport.actCommandOutput = actCommandOutput;
|
||||||
|
infoItems.push({
|
||||||
|
label: bugReportToTemplateMap['actCommandOutput'].title,
|
||||||
|
description: path.parse(history.logPath).base,
|
||||||
|
iconPath: new ThemeIcon('note'),
|
||||||
|
picked: true,
|
||||||
|
key: 'actCommandOutput'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error: any) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get act bug report
|
||||||
|
const actBugReport = await new Promise<string | undefined>((resolve, reject) => {
|
||||||
|
childProcess.exec(`${Act.getActCommand()} ${Option.BugReport}`, (error, stdout, stderr) => {
|
||||||
|
if (!error) {
|
||||||
|
resolve(stdout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (actBugReport) {
|
||||||
|
fullBugReport.actBugReport = actBugReport;
|
||||||
|
infoItems.push({
|
||||||
|
label: bugReportToTemplateMap['actBugReport'].title,
|
||||||
|
iconPath: new ThemeIcon('report'),
|
||||||
|
picked: true,
|
||||||
|
key: 'actBugReport'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultTitle = 'Select the information to include in the bug report';
|
||||||
|
const extendedTitle = 'More information can be included in the bug report by having the relevant workflow opened in the editor before invoking this command.';
|
||||||
|
const selectedInfo = await window.showQuickPick(infoItems, {
|
||||||
|
title: isWorkflowFound ? defaultTitle : `${defaultTitle}. ${extendedTitle}`,
|
||||||
|
placeHolder: 'Bug Report Information',
|
||||||
|
canPickMany: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedInfo) {
|
||||||
|
const bugReport: BugReport = {};
|
||||||
|
|
||||||
|
const selectedInfoKeys = selectedInfo.map(info => info.key);
|
||||||
|
for (const key of selectedInfoKeys) {
|
||||||
|
bugReport[key] = fullBugReport[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return bugReport;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -124,7 +124,7 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingSettings = this.storageManager.get<{ [path: string]: Setting[] }>(storageKey) || {};
|
const existingSettings = await this.storageManager.get<{ [path: string]: Setting[] }>(storageKey) || {};
|
||||||
if (existingSettings[workspaceFolder.uri.fsPath]) {
|
if (existingSettings[workspaceFolder.uri.fsPath]) {
|
||||||
for (const [index, setting] of settings.entries()) {
|
for (const [index, setting] of settings.entries()) {
|
||||||
const existingSetting = existingSettings[workspaceFolder.uri.fsPath].find(existingSetting => existingSetting.key === setting.key);
|
const existingSetting = existingSettings[workspaceFolder.uri.fsPath].find(existingSetting => existingSetting.key === setting.key);
|
||||||
@@ -154,7 +154,7 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getCustomSettings(workspaceFolder: WorkspaceFolder, storageKey: StorageKey): Promise<CustomSetting[]> {
|
async getCustomSettings(workspaceFolder: WorkspaceFolder, storageKey: StorageKey): Promise<CustomSetting[]> {
|
||||||
const existingCustomSettings = this.storageManager.get<{ [path: string]: CustomSetting[] }>(storageKey) || {};
|
const existingCustomSettings = await this.storageManager.get<{ [path: string]: CustomSetting[] }>(storageKey) || {};
|
||||||
return existingCustomSettings[workspaceFolder.uri.fsPath] || [];
|
return existingCustomSettings[workspaceFolder.uri.fsPath] || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ export class SettingsManager {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await workspace.fs.stat(settingFileUri);
|
await workspace.fs.stat(settingFileUri);
|
||||||
window.showErrorMessage(`A file or folder named ${settingFileName} already exists at ${workspaceFolder.uri.fsPath}. Please choose another name.`);
|
// Silently handle file already exists
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
try {
|
try {
|
||||||
await workspace.fs.writeFile(settingFileUri, new TextEncoder().encode(content));
|
await workspace.fs.writeFile(settingFileUri, new TextEncoder().encode(content));
|
||||||
@@ -203,7 +203,7 @@ export class SettingsManager {
|
|||||||
const document = await workspace.openTextDocument(settingFileUri);
|
const document = await workspace.openTextDocument(settingFileUri);
|
||||||
await window.showTextDocument(document);
|
await window.showTextDocument(document);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
window.showErrorMessage(`Failed to create ${settingFileName}. Error: ${error}`);
|
// Silently handle file creation error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,12 +228,12 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (existingSettingFileNames.length > 0) {
|
if (existingSettingFileNames.length > 0) {
|
||||||
window.showErrorMessage(`The following file(s) have already been added: ${existingSettingFileNames.join(', ')}`);
|
// Silently handle already added files
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async editCustomSetting(workspaceFolder: WorkspaceFolder, newCustomSetting: CustomSetting, storageKey: StorageKey, forceAppend: boolean = false) {
|
async editCustomSetting(workspaceFolder: WorkspaceFolder, newCustomSetting: CustomSetting, storageKey: StorageKey, forceAppend: boolean = false) {
|
||||||
const existingCustomSettings = this.storageManager.get<{ [path: string]: CustomSetting[] }>(storageKey) || {};
|
const existingCustomSettings = await this.storageManager.get<{ [path: string]: CustomSetting[] }>(storageKey) || {};
|
||||||
if (existingCustomSettings[workspaceFolder.uri.fsPath]) {
|
if (existingCustomSettings[workspaceFolder.uri.fsPath]) {
|
||||||
const index = existingCustomSettings[workspaceFolder.uri.fsPath]
|
const index = existingCustomSettings[workspaceFolder.uri.fsPath]
|
||||||
.findIndex(customSetting =>
|
.findIndex(customSetting =>
|
||||||
@@ -254,7 +254,7 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async removeCustomSetting(workspaceFolder: WorkspaceFolder, existingCustomSetting: CustomSetting, storageKey: StorageKey) {
|
async removeCustomSetting(workspaceFolder: WorkspaceFolder, existingCustomSetting: CustomSetting, storageKey: StorageKey) {
|
||||||
const existingCustomSettings = this.storageManager.get<{ [path: string]: CustomSetting[] }>(storageKey) || {};
|
const existingCustomSettings = await this.storageManager.get<{ [path: string]: CustomSetting[] }>(storageKey) || {};
|
||||||
if (existingCustomSettings[workspaceFolder.uri.fsPath]) {
|
if (existingCustomSettings[workspaceFolder.uri.fsPath]) {
|
||||||
const index = existingCustomSettings[workspaceFolder.uri.fsPath].findIndex(customSetting =>
|
const index = existingCustomSettings[workspaceFolder.uri.fsPath].findIndex(customSetting =>
|
||||||
storageKey === StorageKey.Options ?
|
storageKey === StorageKey.Options ?
|
||||||
@@ -275,7 +275,7 @@ export class SettingsManager {
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
try {
|
try {
|
||||||
await workspace.fs.stat(Uri.file(settingFile.path));
|
await workspace.fs.stat(Uri.file(settingFile.path));
|
||||||
window.showErrorMessage(`Failed to delete file. Error ${error}`);
|
// Silently handle file deletion error
|
||||||
return;
|
return;
|
||||||
} catch (error: any) { }
|
} catch (error: any) { }
|
||||||
}
|
}
|
||||||
@@ -289,7 +289,7 @@ export class SettingsManager {
|
|||||||
newSetting.value = '';
|
newSetting.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingSettings = this.storageManager.get<{ [path: string]: Setting[] }>(storageKey) || {};
|
const existingSettings = await this.storageManager.get<{ [path: string]: Setting[] }>(storageKey) || {};
|
||||||
if (existingSettings[workspaceFolder.uri.fsPath]) {
|
if (existingSettings[workspaceFolder.uri.fsPath]) {
|
||||||
const index = existingSettings[workspaceFolder.uri.fsPath].findIndex(setting => setting.key === newSetting.key);
|
const index = existingSettings[workspaceFolder.uri.fsPath].findIndex(setting => setting.key === newSetting.key);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ExtensionContext } from "vscode";
|
import { ExtensionContext, Uri, workspace } from "vscode";
|
||||||
|
|
||||||
export enum StorageKey {
|
export enum StorageKey {
|
||||||
WorkspaceHistory = 'workspaceHistory',
|
WorkspaceHistory = 'workspaceHistory',
|
||||||
@@ -9,7 +9,7 @@ export enum StorageKey {
|
|||||||
Inputs = 'inputs',
|
Inputs = 'inputs',
|
||||||
InputFiles = 'inputFiles',
|
InputFiles = 'inputFiles',
|
||||||
Runners = 'runners',
|
Runners = 'runners',
|
||||||
PayloadFiles = 'PayloadFiles',
|
PayloadFiles = 'payloadFiles',
|
||||||
Options = 'options'
|
Options = 'options'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,15 +21,43 @@ export class StorageManager {
|
|||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
keys(): readonly string[] {
|
private async getStorageDirectory(): Promise<Uri> {
|
||||||
return this.context.globalState.keys();
|
const storageDirectory = Uri.joinPath(this.context.storageUri ?? this.context.globalStorageUri, "storageManager");
|
||||||
|
await workspace.fs.createDirectory(storageDirectory).then(undefined, () => void 0);
|
||||||
|
return storageDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
get<T>(storageKey: StorageKey): T | undefined {
|
private async getStorageFile(storageKey: StorageKey): Promise<Uri> {
|
||||||
|
const storageDirectory = await this.getStorageDirectory();
|
||||||
|
return Uri.joinPath(storageDirectory, `${storageKey}.json`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async get<T>(storageKey: StorageKey): Promise<T | undefined> {
|
||||||
|
if ([StorageKey.Secrets, StorageKey.SecretFiles].includes(storageKey)) {
|
||||||
return this.context.globalState.get<T>(`${this.extensionKey}.${storageKey}`);
|
return this.context.globalState.get<T>(`${this.extensionKey}.${storageKey}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const storageFile = await this.getStorageFile(storageKey);
|
||||||
|
return workspace.fs.readFile(storageFile).then(data => {
|
||||||
|
if (data) {
|
||||||
|
return JSON.parse(data.toString()) as T;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, (error) => {
|
||||||
|
if (error.code === 'FileNotFound') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async update(storageKey: StorageKey, value: any): Promise<void> {
|
async update(storageKey: StorageKey, value: any): Promise<void> {
|
||||||
|
if ([StorageKey.Secrets, StorageKey.SecretFiles].includes(storageKey)) {
|
||||||
await this.context.globalState.update(`${this.extensionKey}.${storageKey}`, value);
|
await this.context.globalState.update(`${this.extensionKey}.${storageKey}`, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = JSON.stringify(value, null, 2);
|
||||||
|
const storageFile = await this.getStorageFile(storageKey);
|
||||||
|
await workspace.fs.writeFile(storageFile, Buffer.from(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ export namespace Utils {
|
|||||||
if (workspaceFolders && workspaceFolders.length > 0) {
|
if (workspaceFolders && workspaceFolders.length > 0) {
|
||||||
return workspaceFolders[0];
|
return workspaceFolders[0];
|
||||||
} else {
|
} else {
|
||||||
await window.showErrorMessage('Failed to find a workspace folder');
|
// Silently handle no workspace folder case
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,11 +35,8 @@ export default class HistoryTreeDataProvider implements TreeDataProvider<GithubL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.showErrorMessage(`${historyTreeItem.history.name} #${historyTreeItem.history.count} task is no longer open.`, 'View Output').then(async value => {
|
// Silently handle task not open case
|
||||||
if (value === 'View Output') {
|
|
||||||
await commands.executeCommand('githubLocalActions.viewOutput', historyTreeItem);
|
await commands.executeCommand('githubLocalActions.viewOutput', historyTreeItem);
|
||||||
}
|
|
||||||
});
|
|
||||||
}),
|
}),
|
||||||
commands.registerCommand('githubLocalActions.viewOutput', async (historyTreeItem: HistoryTreeItem) => {
|
commands.registerCommand('githubLocalActions.viewOutput', async (historyTreeItem: HistoryTreeItem) => {
|
||||||
await act.historyManager.viewOutput(historyTreeItem.history);
|
await act.historyManager.viewOutput(historyTreeItem.history);
|
||||||
@@ -87,18 +84,18 @@ export default class HistoryTreeDataProvider implements TreeDataProvider<GithubL
|
|||||||
if (workspaceFolders.length === 1) {
|
if (workspaceFolders.length === 1) {
|
||||||
items.push(...await new WorkspaceFolderHistoryTreeItem(workspaceFolders[0]).getChildren());
|
items.push(...await new WorkspaceFolderHistoryTreeItem(workspaceFolders[0]).getChildren());
|
||||||
|
|
||||||
const workspaceHistory = act.historyManager.workspaceHistory[workspaceFolders[0].uri.fsPath];
|
const workspaceHistory = (await act.historyManager.getWorkspaceHistory())[workspaceFolders[0].uri.fsPath] ?? [];
|
||||||
if (workspaceHistory && workspaceHistory.length > 0) {
|
if (workspaceHistory.length > 0) {
|
||||||
isRunning = act.historyManager.workspaceHistory[workspaceFolders[0].uri.fsPath].find(workspaceHistory => workspaceHistory.status === HistoryStatus.Running) !== undefined;
|
isRunning = workspaceHistory.find(workspaceHistory => workspaceHistory.status === HistoryStatus.Running) !== undefined;
|
||||||
noHistory = false;
|
noHistory = false;
|
||||||
}
|
}
|
||||||
} else if (workspaceFolders.length > 1) {
|
} else if (workspaceFolders.length > 1) {
|
||||||
for (const workspaceFolder of workspaceFolders) {
|
for (const workspaceFolder of workspaceFolders) {
|
||||||
items.push(new WorkspaceFolderHistoryTreeItem(workspaceFolder));
|
items.push(new WorkspaceFolderHistoryTreeItem(workspaceFolder));
|
||||||
|
|
||||||
const workspaceHistory = act.historyManager.workspaceHistory[workspaceFolder.uri.fsPath];
|
const workspaceHistory = (await act.historyManager.getWorkspaceHistory())[workspaceFolder.uri.fsPath] ?? [];
|
||||||
if (workspaceHistory && workspaceHistory.length > 0) {
|
if (workspaceHistory.length > 0) {
|
||||||
isRunning = act.historyManager.workspaceHistory[workspaceFolder.uri.fsPath].find(workspaceHistory => workspaceHistory.status === HistoryStatus.Running) !== undefined;
|
isRunning = workspaceHistory.find(workspaceHistory => workspaceHistory.status === HistoryStatus.Running) !== undefined;
|
||||||
noHistory = false;
|
noHistory = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default class WorkspaceFolderHistoryTreeItem extends TreeItem implements
|
|||||||
async getChildren(): Promise<GithubLocalActionsTreeItem[]> {
|
async getChildren(): Promise<GithubLocalActionsTreeItem[]> {
|
||||||
const items: GithubLocalActionsTreeItem[] = [];
|
const items: GithubLocalActionsTreeItem[] = [];
|
||||||
|
|
||||||
const workspaceHistory = act.historyManager.workspaceHistory[this.workspaceFolder.uri.fsPath];
|
const workspaceHistory = (await act.historyManager.getWorkspaceHistory())[this.workspaceFolder.uri.fsPath];
|
||||||
if (workspaceHistory) {
|
if (workspaceHistory) {
|
||||||
for (const history of workspaceHistory.slice().reverse()) {
|
for (const history of workspaceHistory.slice().reverse()) {
|
||||||
items.push(new HistoryTreeItem(this.workspaceFolder, history));
|
items.push(new HistoryTreeItem(this.workspaceFolder, history));
|
||||||
|
|||||||
@@ -254,12 +254,7 @@ export default class SettingsTreeDataProvider implements TreeDataProvider<Github
|
|||||||
const document = await workspace.openTextDocument(settingFileTreeItem.settingFile.path);
|
const document = await workspace.openTextDocument(settingFileTreeItem.settingFile.path);
|
||||||
await window.showTextDocument(document);
|
await window.showTextDocument(document);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
try {
|
// Silently handle file opening errors
|
||||||
await workspace.fs.stat(Uri.file(settingFileTreeItem.settingFile.path));
|
|
||||||
window.showErrorMessage(`Failed to open file. Error: ${error}`);
|
|
||||||
} catch (error: any) {
|
|
||||||
window.showErrorMessage(`File ${settingFileTreeItem.settingFile.name} not found.`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
commands.registerCommand('githubLocalActions.removeCustomSetting', async (customTreeItem: SettingFileTreeItem | OptionTreeItem) => {
|
commands.registerCommand('githubLocalActions.removeCustomSetting', async (customTreeItem: SettingFileTreeItem | OptionTreeItem) => {
|
||||||
@@ -289,7 +284,7 @@ export default class SettingsTreeDataProvider implements TreeDataProvider<Github
|
|||||||
const settings = await act.settingsManager.getSettings(settingTreeItem.workspaceFolder, false);
|
const settings = await act.settingsManager.getSettings(settingTreeItem.workspaceFolder, false);
|
||||||
const variableNames = settings.variables.map(variable => variable.key);
|
const variableNames = settings.variables.map(variable => variable.key);
|
||||||
if (variableNames.length > 0) {
|
if (variableNames.length > 0) {
|
||||||
const repository = await act.settingsManager.githubManager.getRepository(settingTreeItem.workspaceFolder, 'githubLocalActions.importFromGithub', [settingTreeItem]);
|
const repository = await act.settingsManager.githubManager.getRepository(settingTreeItem.workspaceFolder, false, { command: 'githubLocalActions.importFromGithub', args: [settingTreeItem] });
|
||||||
if (repository) {
|
if (repository) {
|
||||||
const variableOptions: QuickPickItem[] = [];
|
const variableOptions: QuickPickItem[] = [];
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
@@ -352,7 +347,7 @@ export default class SettingsTreeDataProvider implements TreeDataProvider<Github
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
window.showErrorMessage(`Error(s) encountered retrieving variables from GitHub. Errors: ${[...new Set(errors)].join(' ')}`);
|
// Silently handle GitHub API errors
|
||||||
}
|
}
|
||||||
|
|
||||||
if (variableOptions.length > 0) {
|
if (variableOptions.length > 0) {
|
||||||
@@ -370,7 +365,7 @@ export default class SettingsTreeDataProvider implements TreeDataProvider<Github
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (hasDuplicates) {
|
if (hasDuplicates) {
|
||||||
window.showErrorMessage('Duplicate variables selected');
|
// Silently handle duplicate variables
|
||||||
} else {
|
} else {
|
||||||
for await (const variable of selectedVariables) {
|
for await (const variable of selectedVariables) {
|
||||||
const newSetting = settings.variables.find(existingVariable => existingVariable.key === variable.label);
|
const newSetting = settings.variables.find(existingVariable => existingVariable.key === variable.label);
|
||||||
@@ -384,11 +379,11 @@ export default class SettingsTreeDataProvider implements TreeDataProvider<Github
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (errors.length === 0) {
|
} else if (errors.length === 0) {
|
||||||
window.showErrorMessage('No matching variables defined in Github');
|
// Silently handle no matching variables
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
window.showErrorMessage('No variables found in workflow(s)');
|
// Silently handle no variables found
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
commands.registerCommand('githubLocalActions.editSetting', async (settingTreeItem: SettingTreeItem) => {
|
commands.registerCommand('githubLocalActions.editSetting', async (settingTreeItem: SettingTreeItem) => {
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { CancellationToken, commands, EventEmitter, ExtensionContext, TreeDataProvider, TreeItem, window, workspace } from "vscode";
|
import {
|
||||||
|
CancellationToken,
|
||||||
|
commands,
|
||||||
|
EventEmitter,
|
||||||
|
ExtensionContext,
|
||||||
|
TreeDataProvider,
|
||||||
|
TreeItem,
|
||||||
|
window,
|
||||||
|
workspace,
|
||||||
|
} from "vscode";
|
||||||
import { Event } from "../../act";
|
import { Event } from "../../act";
|
||||||
import { act } from "../../extension";
|
import { act } from "../../extension";
|
||||||
import { Utils } from "../../utils";
|
import { Utils } from "../../utils";
|
||||||
@@ -9,51 +18,77 @@ import JobTreeItem from "./job";
|
|||||||
import WorkflowTreeItem from "./workflow";
|
import WorkflowTreeItem from "./workflow";
|
||||||
import WorkspaceFolderWorkflowsTreeItem from "./workspaceFolderWorkflows";
|
import WorkspaceFolderWorkflowsTreeItem from "./workspaceFolderWorkflows";
|
||||||
|
|
||||||
export default class WorkflowsTreeDataProvider implements TreeDataProvider<GithubLocalActionsTreeItem> {
|
export default class WorkflowsTreeDataProvider
|
||||||
private _onDidChangeTreeData = new EventEmitter<GithubLocalActionsTreeItem | undefined | null | void>();
|
implements TreeDataProvider<GithubLocalActionsTreeItem>
|
||||||
|
{
|
||||||
|
private _onDidChangeTreeData = new EventEmitter<
|
||||||
|
GithubLocalActionsTreeItem | undefined | null | void
|
||||||
|
>();
|
||||||
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
|
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
|
||||||
static VIEW_ID = 'workflows';
|
static VIEW_ID = "workflows";
|
||||||
|
|
||||||
constructor(context: ExtensionContext) {
|
constructor(context: ExtensionContext) {
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
commands.registerCommand('githubLocalActions.runAllWorkflows', async (workspaceFolderWorkflowsTreeItem?: WorkspaceFolderWorkflowsTreeItem) => {
|
commands.registerCommand(
|
||||||
const workspaceFolder = await Utils.getWorkspaceFolder(workspaceFolderWorkflowsTreeItem?.workspaceFolder);
|
"githubLocalActions.runAllWorkflows",
|
||||||
|
async (
|
||||||
|
workspaceFolderWorkflowsTreeItem?: WorkspaceFolderWorkflowsTreeItem
|
||||||
|
) => {
|
||||||
|
const workspaceFolder = await Utils.getWorkspaceFolder(
|
||||||
|
workspaceFolderWorkflowsTreeItem?.workspaceFolder
|
||||||
|
);
|
||||||
if (workspaceFolder) {
|
if (workspaceFolder) {
|
||||||
await act.runAllWorkflows(workspaceFolder);
|
await act.runAllWorkflows(workspaceFolder);
|
||||||
}
|
}
|
||||||
}),
|
}
|
||||||
commands.registerCommand('githubLocalActions.runEvent', async (workspaceFolderWorkflowsTreeItem?: WorkspaceFolderWorkflowsTreeItem) => {
|
),
|
||||||
const workspaceFolder = await Utils.getWorkspaceFolder(workspaceFolderWorkflowsTreeItem?.workspaceFolder);
|
commands.registerCommand(
|
||||||
|
"githubLocalActions.runEvent",
|
||||||
|
async (
|
||||||
|
workspaceFolderWorkflowsTreeItem?: WorkspaceFolderWorkflowsTreeItem
|
||||||
|
) => {
|
||||||
|
const workspaceFolder = await Utils.getWorkspaceFolder(
|
||||||
|
workspaceFolderWorkflowsTreeItem?.workspaceFolder
|
||||||
|
);
|
||||||
if (workspaceFolder) {
|
if (workspaceFolder) {
|
||||||
const event = await window.showQuickPick(Object.values(Event), {
|
const event = await window.showQuickPick(Object.values(Event), {
|
||||||
title: 'Select the event to run',
|
title: "Select the event to run",
|
||||||
placeHolder: 'Event'
|
placeHolder: "Event",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (event) {
|
if (event) {
|
||||||
await act.runEvent(workspaceFolder, event as Event);
|
await act.runEvent(workspaceFolder, event as Event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}
|
||||||
commands.registerCommand('githubLocalActions.refreshWorkflows', async () => {
|
),
|
||||||
|
commands.registerCommand(
|
||||||
|
"githubLocalActions.refreshWorkflows",
|
||||||
|
async () => {
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}),
|
}
|
||||||
commands.registerCommand('githubLocalActions.openWorkflow', async (workflowTreeItem: WorkflowTreeItem) => {
|
),
|
||||||
|
commands.registerCommand(
|
||||||
|
"githubLocalActions.openWorkflow",
|
||||||
|
async (workflowTreeItem: WorkflowTreeItem) => {
|
||||||
try {
|
try {
|
||||||
const document = await workspace.openTextDocument(workflowTreeItem.workflow.uri);
|
const document = await workspace.openTextDocument(
|
||||||
|
workflowTreeItem.workflow.uri
|
||||||
|
);
|
||||||
await window.showTextDocument(document);
|
await window.showTextDocument(document);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
try {
|
// Silently handle workflow opening errors
|
||||||
await workspace.fs.stat(workflowTreeItem.workflow.uri);
|
|
||||||
window.showErrorMessage(`Failed to open workflow. Error: ${error}`);
|
|
||||||
} catch (error: any) {
|
|
||||||
window.showErrorMessage(`Workflow ${path.parse(workflowTreeItem.workflow.uri.fsPath).base} not found.`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
),
|
||||||
commands.registerCommand('githubLocalActions.runWorkflow', async (workflowTreeItem: WorkflowTreeItem) => {
|
commands.registerCommand(
|
||||||
|
"githubLocalActions.runWorkflow",
|
||||||
|
async (workflowTreeItem: WorkflowTreeItem) => {
|
||||||
if (workflowTreeItem) {
|
if (workflowTreeItem) {
|
||||||
await act.runWorkflow(workflowTreeItem.workspaceFolder, workflowTreeItem.workflow);
|
await act.runWorkflow(
|
||||||
|
workflowTreeItem.workspaceFolder,
|
||||||
|
workflowTreeItem.workflow
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
let errorMessage: string | undefined;
|
let errorMessage: string | undefined;
|
||||||
|
|
||||||
@@ -61,15 +96,25 @@ export default class WorkflowsTreeDataProvider implements TreeDataProvider<Githu
|
|||||||
if (activeTextEditor) {
|
if (activeTextEditor) {
|
||||||
const uri = activeTextEditor.document.uri;
|
const uri = activeTextEditor.document.uri;
|
||||||
const fileName = path.parse(uri.fsPath).base;
|
const fileName = path.parse(uri.fsPath).base;
|
||||||
if (uri.path.match(`.*/${WorkflowsManager.WORKFLOWS_DIRECTORY}/.*\\.(${WorkflowsManager.YAML_EXTENSION}|${WorkflowsManager.YML_EXTENSION})`)) {
|
const workflowsDirectory =
|
||||||
|
WorkflowsManager.getWorkflowsDirectory();
|
||||||
|
if (
|
||||||
|
uri.path.match(
|
||||||
|
`.*/${workflowsDirectory}/.*\\.(${WorkflowsManager.yamlExtension}|${WorkflowsManager.ymlExtension})`
|
||||||
|
)
|
||||||
|
) {
|
||||||
const workspaceFolder = workspace.getWorkspaceFolder(uri);
|
const workspaceFolder = workspace.getWorkspaceFolder(uri);
|
||||||
if (workspaceFolder) {
|
if (workspaceFolder) {
|
||||||
const workflows = await act.workflowsManager.getWorkflows(workspaceFolder);
|
const workflows = await act.workflowsManager.getWorkflows(
|
||||||
const workflow = workflows.find(workflow => workflow.uri.fsPath === uri.fsPath);
|
workspaceFolder
|
||||||
|
);
|
||||||
|
const workflow = workflows.find(
|
||||||
|
(workflow) => workflow.uri.fsPath === uri.fsPath
|
||||||
|
);
|
||||||
if (workflow) {
|
if (workflow) {
|
||||||
await act.runWorkflow(workspaceFolder, workflow);
|
await act.runWorkflow(workspaceFolder, workflow);
|
||||||
} else {
|
} else {
|
||||||
errorMessage = `Workflow not found in workflow directory (${WorkflowsManager.WORKFLOWS_DIRECTORY}).`;
|
errorMessage = `Workflow not found in workflow directory (${workflowsDirectory}).`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errorMessage = `${fileName} must be opened in a workspace folder to be executed locally.`;
|
errorMessage = `${fileName} must be opened in a workspace folder to be executed locally.`;
|
||||||
@@ -78,21 +123,81 @@ export default class WorkflowsTreeDataProvider implements TreeDataProvider<Githu
|
|||||||
errorMessage = `${fileName} is not a workflow that can be executed locally.`;
|
errorMessage = `${fileName} is not a workflow that can be executed locally.`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errorMessage = 'No workflow opened to execute locally.';
|
errorMessage = "No workflow opened to execute locally.";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
window.showErrorMessage(errorMessage, 'View Workflows').then(async value => {
|
// Silently handle workflow execution errors
|
||||||
if (value === 'View Workflows') {
|
|
||||||
await commands.executeCommand('workflows.focus');
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
commands.registerCommand(
|
||||||
|
"githubLocalActions.runJob",
|
||||||
|
async (jobTreeItem: JobTreeItem) => {
|
||||||
|
await act.runJob(
|
||||||
|
jobTreeItem.workspaceFolder,
|
||||||
|
jobTreeItem.workflow,
|
||||||
|
jobTreeItem.job
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
commands.registerCommand(
|
||||||
|
"githubLocalActions.runWorkflowEvent",
|
||||||
|
async (workflowTreeItem: WorkflowTreeItem) => {
|
||||||
|
// Filter to only events that are registered on the workflow
|
||||||
|
const registeredEventsOnWorkflow = Object.keys(
|
||||||
|
workflowTreeItem.workflow.yaml.on
|
||||||
|
);
|
||||||
|
|
||||||
|
if (registeredEventsOnWorkflow.length === 0) {
|
||||||
|
// Silently handle no events case
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = await window.showQuickPick(registeredEventsOnWorkflow, {
|
||||||
|
title: "Select the event to run",
|
||||||
|
placeHolder: "Event",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
await act.runEvent(
|
||||||
|
workflowTreeItem.workspaceFolder,
|
||||||
|
event as Event,
|
||||||
|
{ workflow: workflowTreeItem.workflow }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
commands.registerCommand(
|
||||||
|
"githubLocalActions.runJobEvent",
|
||||||
|
async (jobTreeItem: JobTreeItem) => {
|
||||||
|
// Filter to only events that are registered on the job's parent workflow
|
||||||
|
const registeredEventsOnJobParentWorkflow = Object.keys(
|
||||||
|
jobTreeItem.workflow.yaml.on
|
||||||
|
);
|
||||||
|
|
||||||
|
if (registeredEventsOnJobParentWorkflow.length === 0) {
|
||||||
|
// Silently handle no events case
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = await window.showQuickPick(
|
||||||
|
registeredEventsOnJobParentWorkflow,
|
||||||
|
{
|
||||||
|
title: "Select the event to run",
|
||||||
|
placeHolder: "Event",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
await act.runEvent(jobTreeItem.workspaceFolder, event as Event, {
|
||||||
|
workflow: jobTreeItem.workflow,
|
||||||
|
job: jobTreeItem.job,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
)
|
||||||
commands.registerCommand('githubLocalActions.runJob', async (jobTreeItem: JobTreeItem) => {
|
|
||||||
await act.runJob(jobTreeItem.workspaceFolder, jobTreeItem.workflow, jobTreeItem.job);
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,11 +205,17 @@ export default class WorkflowsTreeDataProvider implements TreeDataProvider<Githu
|
|||||||
this._onDidChangeTreeData.fire(element);
|
this._onDidChangeTreeData.fire(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTreeItem(element: GithubLocalActionsTreeItem): GithubLocalActionsTreeItem | Thenable<GithubLocalActionsTreeItem> {
|
getTreeItem(
|
||||||
|
element: GithubLocalActionsTreeItem
|
||||||
|
): GithubLocalActionsTreeItem | Thenable<GithubLocalActionsTreeItem> {
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolveTreeItem(item: TreeItem, element: GithubLocalActionsTreeItem, token: CancellationToken): Promise<GithubLocalActionsTreeItem> {
|
async resolveTreeItem(
|
||||||
|
item: TreeItem,
|
||||||
|
element: GithubLocalActionsTreeItem,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<GithubLocalActionsTreeItem> {
|
||||||
if (element.getToolTip) {
|
if (element.getToolTip) {
|
||||||
element.tooltip = await element.getToolTip();
|
element.tooltip = await element.getToolTip();
|
||||||
}
|
}
|
||||||
@@ -112,7 +223,9 @@ export default class WorkflowsTreeDataProvider implements TreeDataProvider<Githu
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getChildren(element?: GithubLocalActionsTreeItem): Promise<GithubLocalActionsTreeItem[]> {
|
async getChildren(
|
||||||
|
element?: GithubLocalActionsTreeItem
|
||||||
|
): Promise<GithubLocalActionsTreeItem[]> {
|
||||||
if (element) {
|
if (element) {
|
||||||
return element.getChildren();
|
return element.getChildren();
|
||||||
} else {
|
} else {
|
||||||
@@ -122,9 +235,15 @@ export default class WorkflowsTreeDataProvider implements TreeDataProvider<Githu
|
|||||||
const workspaceFolders = workspace.workspaceFolders;
|
const workspaceFolders = workspace.workspaceFolders;
|
||||||
if (workspaceFolders) {
|
if (workspaceFolders) {
|
||||||
if (workspaceFolders.length === 1) {
|
if (workspaceFolders.length === 1) {
|
||||||
items.push(...await new WorkspaceFolderWorkflowsTreeItem(workspaceFolders[0]).getChildren());
|
items.push(
|
||||||
|
...(await new WorkspaceFolderWorkflowsTreeItem(
|
||||||
|
workspaceFolders[0]
|
||||||
|
).getChildren())
|
||||||
|
);
|
||||||
|
|
||||||
const workflows = await act.workflowsManager.getWorkflows(workspaceFolders[0]);
|
const workflows = await act.workflowsManager.getWorkflows(
|
||||||
|
workspaceFolders[0]
|
||||||
|
);
|
||||||
if (workflows && workflows.length > 0) {
|
if (workflows && workflows.length > 0) {
|
||||||
noWorkflows = false;
|
noWorkflows = false;
|
||||||
}
|
}
|
||||||
@@ -132,7 +251,9 @@ export default class WorkflowsTreeDataProvider implements TreeDataProvider<Githu
|
|||||||
for (const workspaceFolder of workspaceFolders) {
|
for (const workspaceFolder of workspaceFolders) {
|
||||||
items.push(new WorkspaceFolderWorkflowsTreeItem(workspaceFolder));
|
items.push(new WorkspaceFolderWorkflowsTreeItem(workspaceFolder));
|
||||||
|
|
||||||
const workflows = await act.workflowsManager.getWorkflows(workspaceFolder);
|
const workflows = await act.workflowsManager.getWorkflows(
|
||||||
|
workspaceFolder
|
||||||
|
);
|
||||||
if (workflows && workflows.length > 0) {
|
if (workflows && workflows.length > 0) {
|
||||||
noWorkflows = false;
|
noWorkflows = false;
|
||||||
}
|
}
|
||||||
@@ -140,7 +261,11 @@ export default class WorkflowsTreeDataProvider implements TreeDataProvider<Githu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await commands.executeCommand('setContext', 'githubLocalActions:noWorkflows', noWorkflows);
|
await commands.executeCommand(
|
||||||
|
"setContext",
|
||||||
|
"githubLocalActions:noWorkflows",
|
||||||
|
noWorkflows
|
||||||
|
);
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,48 +4,81 @@ import { RelativePattern, Uri, workspace, WorkspaceFolder } from "vscode";
|
|||||||
import * as yaml from "yaml";
|
import * as yaml from "yaml";
|
||||||
|
|
||||||
export interface Workflow {
|
export interface Workflow {
|
||||||
name: string,
|
name: string;
|
||||||
uri: Uri,
|
uri: Uri;
|
||||||
fileContent?: string,
|
fileContent?: string;
|
||||||
yaml?: any,
|
yaml?: any;
|
||||||
error?: string
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Job {
|
export interface Job {
|
||||||
name: string
|
name: string;
|
||||||
id: string
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WorkflowsManager {
|
export class WorkflowsManager {
|
||||||
static WORKFLOWS_DIRECTORY: string = '.github/workflows';
|
static defaultWorkflowsDirectory: string = ".github/workflows";
|
||||||
static YAML_EXTENSION: string = 'yaml';
|
static giteaWorkflowsDirectory: string = ".gitea/workflows";
|
||||||
static YML_EXTENSION: string = 'yml';
|
static yamlExtension: string = "yaml";
|
||||||
|
static ymlExtension: string = "yml";
|
||||||
|
|
||||||
|
static getWorkflowsDirectories(): string[] {
|
||||||
|
const directories = [
|
||||||
|
WorkflowsManager.defaultWorkflowsDirectory,
|
||||||
|
WorkflowsManager.giteaWorkflowsDirectory,
|
||||||
|
];
|
||||||
|
return directories;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getWorkflowsDirectory(): string {
|
||||||
|
return WorkflowsManager.defaultWorkflowsDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
async getWorkflows(workspaceFolder: WorkspaceFolder): Promise<Workflow[]> {
|
async getWorkflows(workspaceFolder: WorkspaceFolder): Promise<Workflow[]> {
|
||||||
const workflows: Workflow[] = [];
|
const workflows: Workflow[] = [];
|
||||||
|
|
||||||
const workflowFileUris = await workspace.findFiles(new RelativePattern(workspaceFolder, `${WorkflowsManager.WORKFLOWS_DIRECTORY}/*.{${WorkflowsManager.YAML_EXTENSION},${WorkflowsManager.YML_EXTENSION}}`));
|
const workflowsDirectories = WorkflowsManager.getWorkflowsDirectories();
|
||||||
|
|
||||||
|
for (const workflowsDirectory of workflowsDirectories) {
|
||||||
|
try {
|
||||||
|
const workflowFileUris = await workspace.findFiles(
|
||||||
|
new RelativePattern(
|
||||||
|
workspaceFolder,
|
||||||
|
`${workflowsDirectory}/*.{${WorkflowsManager.yamlExtension},${WorkflowsManager.ymlExtension}}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
for await (const workflowFileUri of workflowFileUris) {
|
for await (const workflowFileUri of workflowFileUris) {
|
||||||
let yamlContent: any | undefined;
|
let yamlContent: any | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fileContent = await fs.readFile(workflowFileUri.fsPath, 'utf8');
|
const fileContent = await fs.readFile(
|
||||||
|
workflowFileUri.fsPath,
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
yamlContent = yaml.parse(fileContent);
|
yamlContent = yaml.parse(fileContent);
|
||||||
|
|
||||||
workflows.push({
|
workflows.push({
|
||||||
name: yamlContent.name || path.parse(workflowFileUri.fsPath).name,
|
name: yamlContent.name || path.parse(workflowFileUri.fsPath).name,
|
||||||
uri: workflowFileUri,
|
uri: workflowFileUri,
|
||||||
fileContent: fileContent,
|
fileContent: fileContent,
|
||||||
yaml: yaml.parse(fileContent)
|
yaml: yaml.parse(fileContent),
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
workflows.push({
|
workflows.push({
|
||||||
name: (yamlContent ? yamlContent.name : undefined) || path.parse(workflowFileUri.fsPath).name,
|
name:
|
||||||
|
(yamlContent ? yamlContent.name : undefined) ||
|
||||||
|
path.parse(workflowFileUri.fsPath).name,
|
||||||
uri: workflowFileUri,
|
uri: workflowFileUri,
|
||||||
error: 'Failed to parse workflow'
|
error: "Failed to parse workflow",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Directory doesn't exist, skip it
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return workflows;
|
return workflows;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user