-
-
Notifications
You must be signed in to change notification settings - Fork 174
feat(release): draft per-channel announcement workflow #725
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
29054a3
feat(release): draft per-channel announcement workflow (#719)
kojiromike f501e80
fix(release): address copilot review on draft-announcements
kojiromike 6177a1a
fix(release): validate dispatch payload fields are non-null
kojiromike e6ef7cd
refactor(release): move dispatch payload validation to PHP
kojiromike d81e2e1
fix(release): drop redundant GitHub-sponsors mention; expand forum-UR…
kojiromike b840728
refactor(release): collapse remaining workflow shell into PHP + Taskfile
kojiromike b6ef4ff
fix(release): explicit contents:read; use placeholder constant in tests
kojiromike 7c9da8b
fix(release): route CLI errors to stderr so GITHUB_OUTPUT stays clean
kojiromike File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| name: Release Announcements (drafts) | ||
|
|
||
| # Drafts-only first phase of openemr/openemr-devops#711 (tracked in #719). | ||
| # On openemr-tag dispatch (or workflow_dispatch), renders a per-channel | ||
| # announcement for forum/chat/X/Facebook/LinkedIn/mailing-list and surfaces | ||
| # them as a workflow artifact + step summary. Maintainer copy/pastes onto | ||
| # each channel; mailing list is sent separately via openemr-registration's | ||
| # oe-sender.js, which takes the rendered mail.html + mail.subject.txt files | ||
| # this workflow produces. No posting, no SMTP, no API integration. | ||
|
|
||
| on: | ||
| repository_dispatch: | ||
| types: [openemr-tag] | ||
| workflow_dispatch: | ||
| inputs: | ||
| version: | ||
| description: 'Release version (e.g. 8.1.0)' | ||
| required: true | ||
| type: string | ||
| tag: | ||
| description: 'Annotated release tag (e.g. v8_1_0)' | ||
| required: true | ||
| type: string | ||
| branch: | ||
| description: 'Release branch (e.g. rel-810)' | ||
| required: true | ||
| type: string | ||
| forum_url: | ||
| description: 'Per-release Discourse thread URL (optional; placeholder used if blank)' | ||
| required: false | ||
| type: string | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: release-announcements-${{ github.event.client_payload.data.tag || inputs.tag }} | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| draft: | ||
| name: Render announcement drafts | ||
| runs-on: ubuntu-24.04 | ||
| env: | ||
| OUTPUT_DIR: ${{ github.workspace }}/out/announcements | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v6 | ||
|
|
||
| - name: Setup PHP | ||
| uses: shivammathur/setup-php@v2 | ||
| with: | ||
| php-version: '8.5' | ||
|
|
||
| - name: Install Task | ||
| uses: arduino/setup-task@v2 | ||
|
|
||
| - name: Install release-tools dependencies | ||
| working-directory: tools/release | ||
| run: composer install --no-interaction --no-progress --prefer-dist | ||
|
|
||
| - name: Derive release inputs | ||
| id: inputs | ||
| working-directory: tools/release | ||
| env: | ||
| # repository_dispatch path: PAYLOAD_FILE='-' on stdin, all other vars empty. | ||
| # workflow_dispatch path: PAYLOAD_FILE empty, VERSION/TAG/BRANCH/FORUM_URL set. | ||
| # The PHP CLI rejects mixing the two and validates each field against | ||
| # the canonical dispatch.schema.json patterns; missing/null/malformed | ||
| # envelopes abort here instead of producing artifacts that reference "null". | ||
| PAYLOAD_FILE: ${{ github.event_name == 'repository_dispatch' && '-' || '' }} | ||
| VERSION: ${{ inputs.version }} | ||
| TAG: ${{ inputs.tag }} | ||
| BRANCH: ${{ inputs.branch }} | ||
| FORUM_URL: ${{ inputs.forum_url }} | ||
| CLIENT_PAYLOAD: ${{ toJSON(github.event.client_payload) }} | ||
| run: | | ||
| printf '%s' "${CLIENT_PAYLOAD}" \ | ||
| | task release:derive-announcement-inputs \ | ||
| >> "${GITHUB_OUTPUT}" | ||
|
|
||
| - name: Render announcements | ||
| working-directory: tools/release | ||
| env: | ||
| VERSION: ${{ steps.inputs.outputs.version }} | ||
| TAG: ${{ steps.inputs.outputs.tag }} | ||
| BRANCH: ${{ steps.inputs.outputs.branch }} | ||
| FORUM_URL: ${{ steps.inputs.outputs.forum_url }} | ||
| run: task release:render-announcements | ||
|
|
||
| - name: Write step summary | ||
| working-directory: tools/release | ||
| env: | ||
| VERSION: ${{ steps.inputs.outputs.version }} | ||
| TAG: ${{ steps.inputs.outputs.tag }} | ||
| FORUM_URL: ${{ steps.inputs.outputs.forum_url }} | ||
| run: OUTPUT="${GITHUB_STEP_SUMMARY}" task release:render-announcement-step-summary | ||
|
|
||
| - name: Upload announcement drafts | ||
| uses: actions/upload-artifact@v7 | ||
| with: | ||
| name: release-announcements | ||
| path: ${{ env.OUTPUT_DIR }}/ | ||
| if-no-files-found: error | ||
|
kojiromike marked this conversation as resolved.
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| #!/usr/bin/env php | ||
| <?php | ||
|
|
||
| /** | ||
| * Emit the `version=` / `tag=` / `branch=` / `forum_url=` lines the | ||
| * release-announcements workflow appends to $GITHUB_OUTPUT, regardless | ||
| * of whether the trigger was an `openemr-tag` repository_dispatch | ||
| * (--payload-file) or a manual workflow_dispatch (--release-version / | ||
| * --release-tag / --release-branch / --forum-url). | ||
| * | ||
| * Validation lives in AnnouncementDispatchPayload (mirrors the canonical | ||
| * dispatch.schema.json patterns); a missing or malformed field aborts | ||
| * the step instead of producing artifacts that reference "null". | ||
| * | ||
| * @package openemr-devops | ||
| * @link https://www.open-emr.org | ||
| * @author Michael A. Smith <michael@opencoreemr.com> | ||
| * @copyright Copyright (c) 2026 OpenCoreEMR Inc. | ||
| * @license https://github.com/openemr/openemr-devops/blob/master/LICENSE GNU General Public License 3 | ||
| */ | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| require dirname(__DIR__) . '/vendor/autoload.php'; | ||
|
|
||
| use OpenEMR\Release\AnnouncementDispatchPayload; | ||
| use Symfony\Component\Console\Input\InputInterface; | ||
| use Symfony\Component\Console\Input\InputOption; | ||
| use Symfony\Component\Console\Output\ConsoleOutputInterface; | ||
| use Symfony\Component\Console\Output\OutputInterface; | ||
| use Symfony\Component\Console\SingleCommandApplication; | ||
|
|
||
| (new SingleCommandApplication()) | ||
| ->setName('derive-announcement-inputs') | ||
| ->setDescription('Emit version/tag/branch/forum_url lines for the announcements workflow') | ||
| ->addOption( | ||
| 'payload-file', | ||
| null, | ||
| InputOption::VALUE_REQUIRED, | ||
| "Path to openemr-tag JSON envelope (use '-' for stdin). Mutually exclusive with --release-* flags.", | ||
| ) | ||
| ->addOption('release-version', null, InputOption::VALUE_REQUIRED, 'Release version (e.g. 8.1.0)') | ||
| ->addOption('release-tag', null, InputOption::VALUE_REQUIRED, 'Annotated release tag (e.g. v8_1_0)') | ||
| ->addOption('release-branch', null, InputOption::VALUE_REQUIRED, 'Release branch (e.g. rel-810)') | ||
| ->addOption( | ||
| 'forum-url', | ||
| null, | ||
| InputOption::VALUE_REQUIRED, | ||
| 'Per-release Discourse thread URL; empty value falls back to the placeholder', | ||
| '', | ||
| ) | ||
| ->setCode(function (InputInterface $input, OutputInterface $output): int { | ||
| // Stdout is reserved for the GITHUB_OUTPUT key=value lines the | ||
| // workflow appends with `>>`. Errors must not pollute it. | ||
| $err = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output; | ||
| $str = static function (string $name) use ($input): string { | ||
| $value = $input->getOption($name); | ||
| return is_string($value) ? $value : ''; | ||
| }; | ||
| $payloadFile = $str('payload-file'); | ||
| $version = $str('release-version'); | ||
| $tag = $str('release-tag'); | ||
| $branch = $str('release-branch'); | ||
|
|
||
| $flagsProvided = array_filter( | ||
| [$version, $tag, $branch], | ||
| static fn (string $v): bool => $v !== '', | ||
| ); | ||
| if ($payloadFile !== '' && $flagsProvided !== []) { | ||
| $err->writeln( | ||
| '<error>--payload-file is mutually exclusive with --release-* flags</error>', | ||
| ); | ||
| return 1; | ||
| } | ||
| if ($payloadFile === '' && count($flagsProvided) !== 3) { | ||
| $err->writeln( | ||
| '<error>Provide either --payload-file or all of' | ||
| . ' --release-version/--release-tag/--release-branch</error>', | ||
| ); | ||
| return 1; | ||
| } | ||
|
|
||
| try { | ||
| $payload = $payloadFile !== '' | ||
| ? AnnouncementDispatchPayload::fromPayloadFile($payloadFile) | ||
| : new AnnouncementDispatchPayload($version, $tag, $branch); | ||
| } catch (\JsonException $e) { | ||
| $err->writeln(sprintf('<error>Payload is not valid JSON: %s</error>', $e->getMessage())); | ||
| return 1; | ||
| } catch (\RuntimeException $e) { | ||
| $err->writeln(sprintf('<error>%s</error>', $e->getMessage())); | ||
| return 1; | ||
| } | ||
|
|
||
| // Emit forum_url verbatim (possibly empty); downstream renderers | ||
| // substitute their own placeholder when the maintainer hasn't | ||
| // supplied a real URL. Keeping the placeholder string out of the | ||
| // pipeline avoids Taskfile/Go-template confusion over the literal | ||
| // braces. | ||
| $output->writeln(sprintf('version=%s', $payload->version)); | ||
| $output->writeln(sprintf('tag=%s', $payload->tag)); | ||
| $output->writeln(sprintf('branch=%s', $payload->branch)); | ||
| $output->writeln(sprintf('forum_url=%s', $str('forum-url'))); | ||
| return 0; | ||
| }) | ||
| ->run(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| #!/usr/bin/env php | ||
| <?php | ||
|
|
||
| /** | ||
| * Render the GitHub Actions step-summary markdown for the | ||
| * release-announcements workflow. | ||
| * | ||
| * Reads the per-channel files AnnouncementRenderer wrote into | ||
| * --output-dir and emits the summary markdown to stdout (or --output); | ||
| * the workflow appends it to $GITHUB_STEP_SUMMARY. | ||
| * | ||
| * @package openemr-devops | ||
| * @link https://www.open-emr.org | ||
| * @author Michael A. Smith <michael@opencoreemr.com> | ||
| * @copyright Copyright (c) 2026 OpenCoreEMR Inc. | ||
| * @license https://github.com/openemr/openemr-devops/blob/master/LICENSE GNU General Public License 3 | ||
| */ | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| require dirname(__DIR__) . '/vendor/autoload.php'; | ||
|
|
||
| use OpenEMR\Release\AnnouncementRenderer; | ||
| use OpenEMR\Release\AnnouncementStepSummaryRenderer; | ||
| use Symfony\Component\Console\Input\InputInterface; | ||
| use Symfony\Component\Console\Input\InputOption; | ||
| use Symfony\Component\Console\Output\ConsoleOutputInterface; | ||
| use Symfony\Component\Console\Output\OutputInterface; | ||
| use Symfony\Component\Console\SingleCommandApplication; | ||
|
|
||
| (new SingleCommandApplication()) | ||
| ->setName('render-announcement-step-summary') | ||
| ->setDescription('Render the GitHub Actions step-summary markdown for the announcement drafts') | ||
| ->addOption( | ||
| 'template-dir', | ||
| null, | ||
| InputOption::VALUE_REQUIRED, | ||
| 'Twig template directory (defaults to the binary\'s sibling templates/ dir)', | ||
| ) | ||
| ->addOption('output-dir', null, InputOption::VALUE_REQUIRED, 'Directory containing the per-channel rendered files') | ||
| ->addOption('release-version', null, InputOption::VALUE_REQUIRED, 'Release version (e.g. 8.1.0)') | ||
| ->addOption('release-tag', null, InputOption::VALUE_REQUIRED, 'Annotated release tag (e.g. v8_1_0)') | ||
| ->addOption( | ||
| 'forum-url', | ||
| null, | ||
| InputOption::VALUE_REQUIRED, | ||
| 'Forum URL value rendered into the summary; defaults to the placeholder', | ||
| AnnouncementRenderer::FORUM_URL_PLACEHOLDER, | ||
| ) | ||
| ->addOption('output', 'o', InputOption::VALUE_REQUIRED, 'Output file (defaults to stdout)') | ||
| ->setCode(function (InputInterface $input, OutputInterface $output): int { | ||
| $err = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output; | ||
| $templateDir = $input->getOption('template-dir'); | ||
| if (!is_string($templateDir) || $templateDir === '') { | ||
| $templateDir = dirname(__DIR__) . '/templates'; | ||
| } | ||
|
|
||
| foreach (['output-dir', 'release-version', 'release-tag'] as $required) { | ||
| $value = $input->getOption($required); | ||
| if (!is_string($value) || $value === '') { | ||
| $err->writeln(sprintf('<error>--%s is required</error>', $required)); | ||
| return 1; | ||
| } | ||
| } | ||
| /** @var string $outputDir */ | ||
| $outputDir = $input->getOption('output-dir'); | ||
| /** @var string $version */ | ||
| $version = $input->getOption('release-version'); | ||
| /** @var string $tag */ | ||
| $tag = $input->getOption('release-tag'); | ||
| $forumUrl = $input->getOption('forum-url'); | ||
| if (!is_string($forumUrl) || $forumUrl === '') { | ||
| $forumUrl = AnnouncementRenderer::FORUM_URL_PLACEHOLDER; | ||
| } | ||
|
|
||
| $rendered = (new AnnouncementStepSummaryRenderer($templateDir))->render($outputDir, $version, $tag, $forumUrl); | ||
|
|
||
| $target = $input->getOption('output'); | ||
| if (is_string($target) && $target !== '') { | ||
| file_put_contents($target, $rendered); | ||
| return 0; | ||
| } | ||
| $output->write($rendered); | ||
| return 0; | ||
| }) | ||
| ->run(); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.