Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

## [Unreleased]

### Fixes

- Step outputs written inside grouped shell redirects to `$GITHUB_OUTPUT` or `$GITEA_OUTPUT` are now recognized.

## [2026.6.20] - 2026-06-20

### Plugin Wiring
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import com.github.yunabraska.githubworkflow.i18n.GitHubWorkflowBundle;



import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -19,9 +17,11 @@
@SuppressWarnings("java:S2386")
public class WorkflowContextCatalog {

public static final Pattern PATTERN_GITHUB_OUTPUT = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*>>\\s*\"?\\$\\w*:?\\{?GITHUB_OUTPUT\\}?\"?");
public static final Pattern PATTERN_GITHUB_OUTPUT_TEE = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*\\|\\s*tee\\s+(?:-[A-Za-z]+\\s+)*.*\\$\\w*:?\\{?GITHUB_OUTPUT\\}?");
public static final Pattern PATTERN_GITHUB_ENV = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*>>\\s*\"?\\$\\w*:?\\{?GITHUB_ENV\\}?\"?");
public static final Pattern PATTERN_GITHUB_OUTPUT = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*>>\\s*\"?\\$\\w*:?\\{?(?:GITHUB|GITEA)_OUTPUT\\}?\"?");
public static final Pattern PATTERN_GITHUB_OUTPUT_TEE = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*\\|\\s*tee\\s+(?:-[A-Za-z]+\\s+)*.*\\$\\w*:?\\{?(?:GITHUB|GITEA)_OUTPUT\\}?");
public static final Pattern PATTERN_GITHUB_OUTPUT_GROUP = Pattern.compile("(?m)^\\s*\\{\\s*\\R([\\s\\S]*?)^\\s*}\\s*>>\\s*\"?\\$\\w*:?\\{?(?:GITHUB|GITEA)_OUTPUT\\}?\"?");
public static final Pattern PATTERN_SHELL_OUTPUT_ASSIGNMENT = Pattern.compile("(?m)^\\s*(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*;?\\s*$");
public static final Pattern PATTERN_GITHUB_ENV = Pattern.compile("(?:echo\\s+)?[\"']([A-Za-z_][A-Za-z0-9_-]*)=(.*?)[\"']\\s*>>\\s*\"?\\$\\w*:?\\{?(?:GITHUB|GITEA)_ENV\\}?\"?");
public static final Pattern PATTERN_GITHUB_OUTPUT_MULTILINE = Pattern.compile("(?:echo\\s+)?[\"']?([A-Za-z_][A-Za-z0-9_-]*)<<[^\"'\\r\\n]+[\"']?");
public static final Pattern PATTERN_GITHUB_ENV_MULTILINE = Pattern.compile("(?:echo\\s+)?[\"']?([A-Za-z_][A-Za-z0-9_-]*)<<[^\"'\\r\\n]+[\"']?");
public static final long CACHE_ONE_DAY = 24L * 60 * 60 * 1000;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@
import static com.github.yunabraska.githubworkflow.syntax.WorkflowContextCatalog.PATTERN_GITHUB_ENV;
import static com.github.yunabraska.githubworkflow.syntax.WorkflowContextCatalog.PATTERN_GITHUB_ENV_MULTILINE;
import static com.github.yunabraska.githubworkflow.syntax.WorkflowContextCatalog.PATTERN_GITHUB_OUTPUT;
import static com.github.yunabraska.githubworkflow.syntax.WorkflowContextCatalog.PATTERN_GITHUB_OUTPUT_GROUP;
import static com.github.yunabraska.githubworkflow.syntax.WorkflowContextCatalog.PATTERN_GITHUB_OUTPUT_MULTILINE;
import static com.github.yunabraska.githubworkflow.syntax.WorkflowContextCatalog.PATTERN_GITHUB_OUTPUT_TEE;
import static com.github.yunabraska.githubworkflow.syntax.WorkflowContextCatalog.PATTERN_SHELL_OUTPUT_ASSIGNMENT;
import static java.util.Collections.unmodifiableList;
import static java.util.Optional.ofNullable;

Expand Down Expand Up @@ -332,23 +334,31 @@ public static String goToDeclarationString() {

private static Map<String, String> toGithubOutputs(final String text) {
final Map<String, String> variables = new HashMap<>();
if (text.contains("GITHUB_OUTPUT")) {
if (text.contains("GITHUB_OUTPUT") || text.contains("GITEA_OUTPUT")) {
putMatches(variables, PATTERN_GITHUB_OUTPUT.matcher(text), false);
putMatches(variables, PATTERN_GITHUB_OUTPUT_TEE.matcher(text), false);
putGroupedOutputMatches(variables, text);
putMatches(variables, PATTERN_GITHUB_OUTPUT_MULTILINE.matcher(text), true);
}
return variables;
}

private static Map<String, String> toGithubEnvs(final String text) {
final Map<String, String> variables = new HashMap<>();
if (text.contains("GITHUB_ENV")) {
if (text.contains("GITHUB_ENV") || text.contains("GITEA_ENV")) {
putMatches(variables, PATTERN_GITHUB_ENV.matcher(text), false);
putMatches(variables, PATTERN_GITHUB_ENV_MULTILINE.matcher(text), true);
}
return variables;
}

private static void putGroupedOutputMatches(final Map<String, String> variables, final String text) {
final Matcher matcher = PATTERN_GITHUB_OUTPUT_GROUP.matcher(text);
while (matcher.find()) {
putMatches(variables, PATTERN_SHELL_OUTPUT_ASSIGNMENT.matcher(matcher.group(1)), false);
}
}

private static void putMatches(final Map<String, String> variables, final Matcher matcher, final boolean multiline) {
while (matcher.find()) {
if (matcher.groupCount() >= 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,28 @@ public void testWorkflowDispatchShowsRunLineMarker() throws Exception {
}
}

public void testWorkflowDispatchShowsOneRunLineMarker() {
configureWorkflowProjectFile("""
name: Gutter
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo ok
""");
final WorkflowRun.LineMarkerContributor.RepositoryAvailability previous =
WorkflowRun.LineMarkerContributor.useRepositoryAvailabilityForTests((project, file) -> true);
try {
assertThat(myFixture.findAllGutters().stream()
.filter(gutter -> gutter.getIcon() == AllIcons.Actions.Execute)
.count()).isEqualTo(1);
} finally {
WorkflowRun.LineMarkerContributor.useRepositoryAvailabilityForTests(previous);
}
}

public void testWorkflowDispatchDoesNotShowRunLineMarkerWithoutGitRepository() {
configureWorkflowProjectFile("""
name: Gutter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1493,6 +1493,44 @@ public void testStepsOutputCompletionSuggestsRunOutput() {
""")).contains("artifact");
}

public void testStepsOutputCompletionSuggestsGroupedRunOutput() {
assertThat(completeWorkflow("""
name: Completion
on: workflow_dispatch
jobs:
build:
runs-on: ubuntu-latest
steps:
- id: prepare
run: |
echo "output1=value1" >> "${GITHUB_OUTPUT}"
{
echo "output2=value2"
echo "output3=value3"
} >> "${GITHUB_OUTPUT}"
- run: echo "${{ steps.prepare.outputs.<caret> }}"
""")).contains("output1", "output2", "output3");
}

public void testGiteaStepsOutputCompletionSuggestsGroupedRunOutput() {
assertThat(completeGiteaWorkflow("""
name: Completion
on: workflow_dispatch
jobs:
build:
runs-on: ubuntu-latest
steps:
- id: prepare
run: |
echo "output1=value1" >> "${GITEA_OUTPUT}"
{
echo "output2=value2"
echo "output3=value3"
} >> "${GITEA_OUTPUT}"
- run: echo "${{ steps.prepare.outputs.<caret> }}"
""")).contains("output1", "output2", "output3");
}

public void testBracketStepsOutputCompletionSuggestsRunOutput() {
assertThat(completeWorkflow("""
name: Completion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,58 @@ public void testIssue79MultilineStepOutputFromGroupedEchoIsAccepted() {
""");
}

public void testIssue94StepOutputFromGroupedEchoIsAccepted() {
assertWorkflowHighlights("""
name: grouping bug
on:
workflow_dispatch:
jobs:
grouping-bug:
runs-on: ubuntu-latest
steps:
- name: Prepare outputs
id: prepare
run: |
echo "output1=value1" >> "${GITHUB_OUTPUT}"

{
echo "output2=value2"
echo "output3=value3"
} >> "${GITHUB_OUTPUT}"
- name: Print outputs
run: |
echo "Output 1: ${{ steps.prepare.outputs.output1 }}"
echo "Output 2: ${{ steps.prepare.outputs.output2 }}"
echo "Output 3: ${{ steps.prepare.outputs.output3 }}"
""");
}

public void testGiteaStepOutputFromGroupedEchoIsAccepted() {
assertGiteaWorkflowHighlights("""
name: grouping bug
on:
workflow_dispatch:
jobs:
grouping-bug:
runs-on: ubuntu-latest
steps:
- name: Prepare outputs
id: prepare
run: |
echo "output1=value1" >> "${GITEA_OUTPUT}"

{
echo "output2=value2"
echo "output3=value3"
} >> "${GITEA_OUTPUT}"
- name: Print outputs
run: |
echo "Output 1: ${{ steps.prepare.outputs.output1 }}"
echo "Output 2: ${{ steps.prepare.outputs.output2 }}"
echo "Output 3: ${{ steps.prepare.outputs.output3 }}"
""");
}

public void testIssue73StepOutputFromTeePipeIsAccepted() {
assertWorkflowHighlights("""
name: Issue 73
Expand Down