Skip to content

Create configlet-sync.yml#212

Merged
IsaacG merged 18 commits into
exercism:mainfrom
jagdish-15:add-sync-workflow
Jun 29, 2025
Merged

Create configlet-sync.yml#212
IsaacG merged 18 commits into
exercism:mainfrom
jagdish-15:add-sync-workflow

Conversation

@jagdish-15
Copy link
Copy Markdown
Member

Pull Request

See forum topic here

Add configlet-sync.yml
@github-actions

This comment was marked as resolved.

@github-actions github-actions Bot closed this Jun 24, 2025
@BethanyG BethanyG reopened this Jun 24, 2025
Comment thread .github/workflows/configlet-sync.yml Outdated
Comment thread .github/workflows/configlet-sync.yml Outdated
Comment thread .github/workflows/configlet-sync.yml Outdated
Comment on lines +55 to +66
configlet_raw_output="$(./bin/configlet sync --tests | tee .github/sync-test-output.txt)"

echo "configlet output:"
echo "$configlet_raw_output"

echo '```' > .github/sync-test-output.txt
echo "$configlet_raw_output" >> .github/sync-test-output.txt
echo '```' >> .github/sync-test-output.txt

echo "output<<EOF" >> "$GITHUB_OUTPUT"
echo "$configlet_raw_output" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason the .github directory would be used as a scratch location? That path should be used for github related configurations. Usually /tmp or a tempfile is used for this sort of thing.

Additionally, you write the contents to this file (L55) then immediately overwrite the contents a few lines later (L60).

Suggested change
configlet_raw_output="$(./bin/configlet sync --tests | tee .github/sync-test-output.txt)"
echo "configlet output:"
echo "$configlet_raw_output"
echo '```' > .github/sync-test-output.txt
echo "$configlet_raw_output" >> .github/sync-test-output.txt
echo '```' >> .github/sync-test-output.txt
echo "output<<EOF" >> "$GITHUB_OUTPUT"
echo "$configlet_raw_output" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
configlet_raw_output="$(./bin/configlet sync --tests)"
printf "configlet output:\n%s\n" "$configlet_raw_output"
printf '```\n%s\n```\n' "$configlet_raw_output" > .github/sync-test-output.txt
printf 'output<<EOF\n%s\nEOF\n' "$configlet_raw_output" > "$GITHUB_OUTPUT"

Is the output needed in two files?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, you write the contents to this file (L55) then immediately overwrite the contents a few lines later (L60).

You're absolutely right, I had meant to update line 55 so it only saved the variable, but I missed it. Thanks for catching that!

Also, really appreciate the other suggestions. They’ve definitely helped clean up and shorten the code. I agree that using a temp folder makes more sense here, so I’ll go ahead and make that change as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • /tmp and the mktemp tool, not a local directory named "temp"
  • Do you need the output in two places?
  • Does output > $GITHUB_OUTPUT make sense or should you be populating the contents of that variable?
  • Do you need to write the output to a file at all or can you use $GITHUB_OUTPUT to share content between steps? It seems like the whole purpose of $GITHUB_OUTPUT is to avoid needing temp files.

Comment thread .github/workflows/configlet-sync.yml Outdated
echo "$configlet_raw_output" >> .github/sync-test-output.txt
echo '```' >> .github/sync-test-output.txt

echo "output<<EOF" >> "$GITHUB_OUTPUT"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm understanding correctly, this variable is supposed to contain an output string, and is not meant to be used as a filename.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly, the configlet_raw_output variable was meant to capture the output of the configlet command, and now it works as intended after you caught the issue at line 55!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm referring to the $GITHUB_OUTPUT variable.

Comment thread .github/workflows/configlet-sync.yml Outdated
jagdish-15 and others added 5 commits June 24, 2025 18:50
Co-authored-by: Isaac Good <IsaacG@users.noreply.github.com>
Co-authored-by: Isaac Good <IsaacG@users.noreply.github.com>
Co-authored-by: Isaac Good <IsaacG@users.noreply.github.com>
Co-authored-by: Isaac Good <IsaacG@users.noreply.github.com>
Comment thread .github/workflows/configlet-sync.yml Outdated
configlet_raw_output="$(./bin/configlet sync --tests)"

printf "configlet output:\n%s\n" "$configlet_raw_output"
mkdir -p temp
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/tmp or the mktemp tool, not a local temp file ;)

Copy link
Copy Markdown
Member

@ErikSchierboom ErikSchierboom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lovely idea! I've left some comments

Comment thread .github/workflows/configlet-sync.yml Outdated

steps:
- name: Checkout repository
uses: actions/checkout@v4
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread .github/workflows/configlet-sync.yml Outdated
run: ./bin/configlet sync --docs --metadata --filepaths -u -y

- name: Create pull request if changes
uses: peter-evans/create-pull-request@v5
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also pin this to a SHA

Comment thread .github/workflows/configlet-sync.yml Outdated

steps:
- name: Checkout repository
uses: actions/checkout@v4
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please pin to a SHA

Comment thread .github/workflows/configlet-sync.yml Outdated

- name: Format test sync output and find existing issue
id: find_issue
if: ${{ !contains(steps.sync_test.outputs.output, 'Every exercise has up-to-date tests!') }}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of relying on the output, I would suggest using the exit code of ./bin/configlet sync --tests to determine if anything is unsynced. I've just checked and it looks like it is non-zero when there are out-of-date tests, and zero when there aren't. You might want to double check, but I would recommend using that if true.

Comment thread .github/workflows/configlet-sync.yml Outdated

- name: Create or Update issue if tests are not synced
if: ${{ !contains(steps.sync_test.outputs.output, 'Every exercise has up-to-date tests!') }}
uses: peter-evans/create-issue-from-file@v5
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please pin this to as SHA

Comment thread .github/workflows/configlet-sync.yml Outdated
Comment thread .github/workflows/configlet-sync.yml Outdated
Comment on lines +3 to +7
on:
workflow_dispatch:
schedule:
- cron: '0 0 15 * *'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest removing the schedule and dispatch and allowing the tracks to "opt-in" by setting their own schedules at the track's workflow level.

@jagdish-15
Copy link
Copy Markdown
Member Author

@IsaacG @ErikSchierboom @kahgoh

I've incorporated all the changes suggested by you! Please let me know if there's anything else that needs adjustment.

One thing to note: I had to use set +e and set -e to prevent the workflow from exiting early when configlet returns a non-zero exit code. You might want to take a quick look at that part.

Comment thread .github/workflows/configlet-sync.yml
Co-authored-by: Glenn Jackman <glenn.jackman@gmail.com>
Comment thread .github/workflows/configlet-sync.yml Outdated
id: sync_test
shell: bash {0}
run: |
configlet_raw_output="$(./bin/configlet sync --tests 2>&1)"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there useful output in STDERR?

Comment thread .github/workflows/configlet-sync.yml Outdated
run: |
configlet_raw_output="$(./bin/configlet sync --tests 2>&1)"
exit_code=$?
echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to be consistent, you can replace all the echo lines with printf ... though it doesn't especially matter either way.

token: ${{ secrets.GITHUB_TOKEN }}
title: "🚨 configlet sync --test found unsynced tests"
content-filepath: /tmp/sync-test-output.txt
issue-number: ${{ steps.find_issue.outputs.issue_number || '' }}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will steps.find_issue.outputs.issue_number ever be unset?

Comment on lines +52 to +58
shell: bash {0}
run: |
configlet_raw_output="$(./bin/configlet sync --tests)"
exit_code=$?
printf "exit_code=%d\n" "$exit_code" >> "$GITHUB_OUTPUT"
printf "output<<EOF\n%s\nEOF\n" "$configlet_raw_output" >> "$GITHUB_OUTPUT"
printf "configlet output:\n%s\n" "$configlet_raw_output"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need to turn off set -e via a custom shell entry. Bash under set -e can handle commands whose non-zero exit code is handled by the calling script and therefore not considered an unexpected failure that should lead to termination (which is what set -e is for). Just use some conditional statement such as if to use this. I believe here it would even increase readability, because instead of storing the raw exit code, you could save a boolean with a better name and use that below.

Suggested change
shell: bash {0}
run: |
configlet_raw_output="$(./bin/configlet sync --tests)"
exit_code=$?
printf "exit_code=%d\n" "$exit_code" >> "$GITHUB_OUTPUT"
printf "output<<EOF\n%s\nEOF\n" "$configlet_raw_output" >> "$GITHUB_OUTPUT"
printf "configlet output:\n%s\n" "$configlet_raw_output"
run: |
if configlet_raw_output="$(./bin/configlet sync --tests)"; then
printf "unsynced_tests_found=false\n" >> "$GITHUB_OUTPUT"
else
printf "unsynced_tests_found=true\n" >> "$GITHUB_OUTPUT"
printf "output<<EOF\n%s\nEOF\n" "$configlet_raw_output" >> "$GITHUB_OUTPUT"
fi
printf "configlet output:\n%s\n" "$configlet_raw_output"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies that the string "true" or "false" is better than 0 or 1 somehow. This is when m shell code. Shell exit codes are very well understood. This also adds more code and sets up additional outputs that may or may not be set. I'm not sure this approach is necessarily better or more readable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, you can split the handling of configlet's exit code and writing to $GITHUB_OUTPUT:

Suggested change
shell: bash {0}
run: |
configlet_raw_output="$(./bin/configlet sync --tests)"
exit_code=$?
printf "exit_code=%d\n" "$exit_code" >> "$GITHUB_OUTPUT"
printf "output<<EOF\n%s\nEOF\n" "$configlet_raw_output" >> "$GITHUB_OUTPUT"
printf "configlet output:\n%s\n" "$configlet_raw_output"
run: |
if configlet_raw_output="$(./bin/configlet sync --tests)"; then
unsynced_tests_found="false"
else
unsynced_tests_found="true"
fi
printf "unsynced_tests_found=%s\n" "$unsynced_tests_found" >> "$GITHUB_OUTPUT"
printf "output<<EOF\n%s\nEOF\n" "$configlet_raw_output" >> "$GITHUB_OUTPUT"
printf "configlet output:\n%s\n" "$configlet_raw_output"

This would add configlet's output to $GITHUB_OUTPUT even if it is not used, but for better readability would be acceptable (it's also what your current implementation does).

If you find the if ... then ... else ... fi too verbose, I think something like this instead would also count as idiomatic Bash code (the exception about set -e in conditionals holds here as well):

Suggested change
shell: bash {0}
run: |
configlet_raw_output="$(./bin/configlet sync --tests)"
exit_code=$?
printf "exit_code=%d\n" "$exit_code" >> "$GITHUB_OUTPUT"
printf "output<<EOF\n%s\nEOF\n" "$configlet_raw_output" >> "$GITHUB_OUTPUT"
printf "configlet output:\n%s\n" "$configlet_raw_output"
run: |
unsynced_tests_found="false"
configlet_raw_output="$(./bin/configlet sync --tests)" || unsynced_tests_found="true"
printf "unsynced_tests_found=%s\n" "$unsynced_tests_found" >> "$GITHUB_OUTPUT"
printf "output<<EOF\n%s\nEOF\n" "$configlet_raw_output" >> "$GITHUB_OUTPUT"
printf "configlet output:\n%s\n" "$configlet_raw_output"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using $? is also perfectly readable and idiomatic bash 🙂

Copy link
Copy Markdown

@ahans ahans Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This implies that the string "true" or "false" is better than 0 or 1 somehow.

When used as a boolean in a GH actions workflow, it is better, because boolean exists as a datatype and anything else is coerced when used in a boolean expression (this is what the if:s in the subsequent steps are), see here. Directly saying true or false avoids this coercion.

Shell exit codes are very well understood.

Sure, but besides success being zero and non-success something else, it depends on the tool. Without knowing configlet, from this snippet alone I would not know that a non-zero exit code means there are unsynced tests. I would have to read the rest of the workflow file to learn that. Using a more descriptive key than exit_code does convey more information.

This also adds more code

More code is not necessarily worse if it's easy to read. Even somebody with little Bash experience would know what the if ... then ... else ... fi does. As an alternative, I suggested the option with the || shortcut that adds less code.

and sets up additional outputs that may or may not be set.

Not true. I replaced one output (exit_code) with another (unsynced_tests_found). The statement about maybe being set or not applies just as well to exit_code.

I'm not sure this approach is necessarily better or more readable.

I guess that depends on perspective. Ultimately it was just a suggestion.

My main point was that it is not necessarily to override GH's reasonable set -e default. That you can use more descriptive variable names in the process was an added benefit, IMHO at least.

(Edit: Many typos fixed.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set -e as a reasonable default is also a matter of debate. See https://mywiki.wooledge.org/BashFAQ/105


- name: Format test sync output and find existing issue
id: find_issue
if: ${{ steps.sync_test.outputs.exit_code != 0 }}
Copy link
Copy Markdown

@ahans ahans Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you go with my suggestion, this would become:

Suggested change
if: ${{ steps.sync_test.outputs.exit_code != 0 }}
if: ${{ steps.sync_test.outputs.unsynced_tests_found }}

fi

- name: Create or Update issue if tests are not synced
if: ${{ steps.sync_test.outputs.exit_code != 0 }}
Copy link
Copy Markdown

@ahans ahans Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dito:

Suggested change
if: ${{ steps.sync_test.outputs.exit_code != 0 }}
if: ${{ steps.sync_test.outputs.unsynced_tests_found }}

Copy link
Copy Markdown
Member

@ErikSchierboom ErikSchierboom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's wait with merging for the ongoing discussion on exit codes and booleans to wrap up

@jagdish-15
Copy link
Copy Markdown
Member Author

Let me know how you'd like me to proceed with this! The exit_code implementation is already in place, and I’ve tested the boolean approach on my fork and shared a few notes above.

@IsaacG
Copy link
Copy Markdown
Member

IsaacG commented Jun 29, 2025

Two of the bash track maintainers are good with this current approach. Any objections to proceeding, @ErikSchierboom ?

@ErikSchierboom
Copy link
Copy Markdown
Member

Nope

@IsaacG IsaacG merged commit 9bdcc6f into exercism:main Jun 29, 2025
1 check passed
@jagdish-15 jagdish-15 deleted the add-sync-workflow branch June 29, 2025 18:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants