-
Notifications
You must be signed in to change notification settings - Fork 28
Change release process to use changie.dev #1117
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| changesDir: .changes | ||
| unreleasedDir: unreleased | ||
| headerPath: .changes/header.tpl.md | ||
| changelogPath: CHANGELOG.md | ||
| versionFormat: '## {{.Version}}' | ||
| kindFormat: '' | ||
| changeFormat: |- | ||
| - {{.Body}} | ||
| ({{.Kind}}) | ||
| [PR {{.Custom.PR}}](https://github.com/IntersectMBO/cardano-api/pull/{{.Custom.PR}}) | ||
|
|
||
| kinds: | ||
| - label: breaking | ||
| - label: feature | ||
| - label: compatible | ||
| - label: bugfix | ||
| - label: optimisation | ||
|
|
||
| custom: | ||
| - key: PR | ||
| type: int | ||
| label: PR Number | ||
|
|
||
| projects: | ||
| - label: cardano-api | ||
| key: cardano-api | ||
| changelog: cardano-api/CHANGELOG.md | ||
| replacements: | ||
| - path: cardano-api/cardano-api.cabal | ||
| find: '^version:.*$' | ||
| replace: 'version: {{.Version}}' | ||
| flag: m | ||
|
|
||
| - label: cardano-api-gen | ||
| key: cardano-api-gen | ||
| changelog: cardano-api-gen/CHANGELOG.md | ||
| replacements: | ||
| - path: cardano-api-gen/cardano-api-gen.cabal | ||
| find: '^version:.*$' | ||
| replace: 'version: {{.Version}}' | ||
| flag: m | ||
|
|
||
| - label: cardano-rpc | ||
| key: cardano-rpc | ||
| changelog: cardano-rpc/CHANGELOG.md | ||
| replacements: | ||
| - path: cardano-rpc/cardano-rpc.cabal | ||
| find: '^version:.*$' | ||
| replace: 'version: {{.Version}}' | ||
| flag: m | ||
|
|
||
| - label: cardano-wasm | ||
| key: cardano-wasm | ||
| changelog: cardano-wasm/CHANGELOG.md | ||
| replacements: | ||
| - path: cardano-wasm/cardano-wasm.cabal | ||
| find: '^version:.*$' | ||
| replace: 'version: {{.Version}}' | ||
| flag: m |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| name: Check Changelog Fragment | ||
|
|
||
| on: | ||
| merge_group: | ||
| pull_request: | ||
| types: [opened, edited, synchronize, ready_for_review, labeled, unlabeled] | ||
|
|
||
| jobs: | ||
| check-fragment: | ||
| name: Changelog fragment present and valid | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Skip on merge_group | ||
| if: github.event_name == 'merge_group' | ||
| run: echo "Skipping changelog check on merge_group" | ||
|
|
||
| - uses: actions/checkout@v4 | ||
| if: github.event_name != 'merge_group' | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Fetch base branch | ||
| if: github.event_name != 'merge_group' | ||
| run: git fetch origin ${{ github.event.pull_request.base.ref }} | ||
|
|
||
| - name: Check for no-changelog label | ||
| if: github.event_name != 'merge_group' | ||
| id: check-label | ||
| run: | | ||
| labels='${{ toJSON(github.event.pull_request.labels.*.name) }}' | ||
| if python3 -c " | ||
| import sys, json | ||
| labels = json.loads('$labels') | ||
| sys.exit(0 if 'no-changelog' in labels else 1) | ||
| " 2>/dev/null; then | ||
| echo "skip=true" >> "$GITHUB_OUTPUT" | ||
| echo "PR has 'no-changelog' label — skipping fragment check." | ||
| else | ||
| echo "skip=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| - name: Find changelog fragments added in this PR | ||
| if: github.event_name != 'merge_group' && steps.check-label.outputs.skip != 'true' | ||
| id: find-fragments | ||
| run: | | ||
| BASE="origin/${{ github.event.pull_request.base.ref }}" | ||
| FRAGMENTS=$(git diff --name-only --diff-filter=A "$BASE...HEAD" \ | ||
| | grep '^\.changes/unreleased/.*\.yaml$' || true) | ||
|
|
||
| if [ -z "$FRAGMENTS" ]; then | ||
| echo "found=false" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "found=true" >> "$GITHUB_OUTPUT" | ||
| # Use random EOF delimiter to safely handle arbitrary fragment paths | ||
| EOF_DELIM=$(openssl rand -hex 8) | ||
| { | ||
| echo "fragments<<${EOF_DELIM}" | ||
| echo "$FRAGMENTS" | ||
| echo "${EOF_DELIM}" | ||
| } >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| - name: Fail if no fragment found | ||
| if: >- | ||
| github.event_name != 'merge_group' | ||
| && steps.check-label.outputs.skip != 'true' | ||
| && steps.find-fragments.outputs.found == 'false' | ||
| run: | | ||
| echo "::error::No changelog fragment found in .changes/unreleased/" | ||
| echo "" | ||
| echo "Every PR that changes user-visible behaviour must include a changie fragment." | ||
| echo "To add one, run:" | ||
| echo "" | ||
| echo " changie new --project <package>" | ||
| echo "" | ||
| echo "and commit the generated file." | ||
| echo "Add the 'no-changelog' label to skip this check for maintenance / test-only PRs." | ||
| exit 1 | ||
|
|
||
| - name: Validate fragment files | ||
| if: >- | ||
| github.event_name != 'merge_group' | ||
| && steps.check-label.outputs.skip != 'true' | ||
| && steps.find-fragments.outputs.found == 'true' | ||
| env: | ||
| FRAGMENTS: ${{ steps.find-fragments.outputs.fragments }} | ||
| run: | | ||
| python3 << 'PYEOF' | ||
| import sys | ||
| import os | ||
| import yaml | ||
|
|
||
| VALID_KINDS = {'breaking', 'feature', 'compatible', 'bugfix', 'optimisation'} | ||
|
|
||
| fragments = [f for f in os.environ['FRAGMENTS'].strip().split('\n') if f] | ||
| all_ok = True | ||
|
|
||
| for path in fragments: | ||
| try: | ||
| with open(path) as fh: | ||
| fragment = yaml.safe_load(fh) | ||
| except Exception as exc: | ||
| print(f"::error file={path}::Failed to parse YAML: {exc}") | ||
| all_ok = False | ||
| continue | ||
|
|
||
| if not isinstance(fragment, dict): | ||
| print(f"::error file={path}::Fragment must be a YAML mapping, got {type(fragment).__name__}") | ||
| all_ok = False | ||
| continue | ||
|
|
||
| errors = [] | ||
|
|
||
| kind = fragment.get('kind') | ||
| if not kind: | ||
| errors.append("missing required field 'kind'") | ||
| elif kind not in VALID_KINDS: | ||
| errors.append( | ||
| f"invalid kind '{kind}'; must be one of: {', '.join(sorted(VALID_KINDS))}" | ||
| ) | ||
|
|
||
| if not fragment.get('body', '').strip(): | ||
| errors.append("missing or empty required field 'body'") | ||
|
|
||
| custom = fragment.get('custom') or {} | ||
| if 'PR' not in custom: | ||
| errors.append("missing required field 'custom.PR' (PR number)") | ||
|
|
||
| if errors: | ||
| for err in errors: | ||
| print(f"::error file={path}::{err}") | ||
| all_ok = False | ||
| else: | ||
| print(f"Fragment OK: {path} (kind={kind}, PR={custom.get('PR')})") | ||
|
|
||
| sys.exit(0 if all_ok else 1) | ||
| PYEOF | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,101 +1,24 @@ | ||||||||||||||||||||||||||||||||
| name: Check if PR changelog was filled correctly | ||||||||||||||||||||||||||||||||
| # This workflow has been superseded by check-changelog-fragment.yml, which validates | ||||||||||||||||||||||||||||||||
| # per-PR changie fragment files instead of parsing the PR description YAML block. | ||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||
| # This job is kept as a no-op pass-through so that existing branch protection rules | ||||||||||||||||||||||||||||||||
| # that require "check-changelog / check-changelog" to pass are not broken during the | ||||||||||||||||||||||||||||||||
| # transition period. It can be removed once branch protection is updated to reference | ||||||||||||||||||||||||||||||||
| # "check-fragment / Changelog fragment present and valid" instead. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| name: Check if PR changelog was filled correctly (legacy — no-op) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||
| merge_group: | ||||||||||||||||||||||||||||||||
| pull_request: | ||||||||||||||||||||||||||||||||
| types: [opened, edited, synchronize, ready_for_review] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||||||||||||
| check-changelog: | ||||||||||||||||||||||||||||||||
| name: check-changelog (superseded — see check-changelog-fragment.yml) | ||||||||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||
| - uses: actions/checkout@v4 | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - uses: actions/setup-node@v4 | ||||||||||||||||||||||||||||||||
| if: ${{ github.event_name != 'merge_group' }} | ||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||
| node-version: 22 | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - run: npm install js-yaml@4.1.0 | ||||||||||||||||||||||||||||||||
| if: ${{ github.event_name != 'merge_group' }} | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: Fail if PR changelog is not correct | ||||||||||||||||||||||||||||||||
| if: ${{ github.event_name != 'merge_group' }} | ||||||||||||||||||||||||||||||||
| uses: actions/github-script@v8 | ||||||||||||||||||||||||||||||||
| id: check-changelog | ||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||
| github-token: ${{ secrets.GITHUB_TOKEN }} | ||||||||||||||||||||||||||||||||
| script: | | ||||||||||||||||||||||||||||||||
| const yaml = require('js-yaml'); | ||||||||||||||||||||||||||||||||
| const fs = require('fs'); | ||||||||||||||||||||||||||||||||
| const execSync = require('child_process').execSync; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const prDescription = await github.rest.pulls.get({ | ||||||||||||||||||||||||||||||||
| owner: context.repo.owner, | ||||||||||||||||||||||||||||||||
| repo: context.repo.repo, | ||||||||||||||||||||||||||||||||
| pull_number: context.issue.number | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const changelogRegex = /# Changelog[\s\S]*?```yaml([\s\S]*?)```/; | ||||||||||||||||||||||||||||||||
| const changelogMatch = prDescription.data.body.match(changelogRegex); | ||||||||||||||||||||||||||||||||
| const yamlContent = changelogMatch ? changelogMatch[1].trim() : ''; | ||||||||||||||||||||||||||||||||
| yamlContent || console.error('Failed to find changelog YAML section in the "Changelog" paragraph'); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||
| changelog = yaml.load(yamlContent)[0]; | ||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||
| console.error('Failed to parse YAML changelog as array:', yamlContent); | ||||||||||||||||||||||||||||||||
| process.exit(1); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||
| config = yaml.load(fs.readFileSync('.cardano-dev.yaml', 'utf8')); | ||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||
| console.error('Failed to load .cardano-dev.yaml config:', e); | ||||||||||||||||||||||||||||||||
| process.exit(1); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| let isCompatibilityValid = false; | ||||||||||||||||||||||||||||||||
| if (!changelog.compatibility) { | ||||||||||||||||||||||||||||||||
| isCompatibilityValid = true; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if (!isCompatibilityValid) { | ||||||||||||||||||||||||||||||||
| console.error('Changelog field "compatibility" is deprecated and no longer used. Please remove it.'); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| let isTypeValid = false; | ||||||||||||||||||||||||||||||||
| const validTypeValues = Object.keys(config.changelog.options.type); | ||||||||||||||||||||||||||||||||
| if (Array.isArray(changelog.type) && !!changelog.type) { | ||||||||||||||||||||||||||||||||
| isTypeValid = changelog.type.every(value => validTypeValues.includes(value)); | ||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||
| isTypeValid = validTypeValues.includes(changelog.type); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if (!isTypeValid) { | ||||||||||||||||||||||||||||||||
| console.error(`PR changelog has invalid type: ${changelog.type}\nExpected one, or more of: ${validTypeValues}`) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| let isProjectsValid = false; | ||||||||||||||||||||||||||||||||
| // .filter(Boolean) is a trick that removes empty values from the array (see https://michaeluloth.com/javascript-filter-boolean/) | ||||||||||||||||||||||||||||||||
| const validProjectsValues = execSync("ls */CHANGELOG* | cut -d/ -f1").toString().split('\n').filter(Boolean) | ||||||||||||||||||||||||||||||||
| if (Array.isArray(changelog.projects) && !!changelog.projects) { | ||||||||||||||||||||||||||||||||
| isProjectsValid = changelog.projects.every(value => validProjectsValues.includes(value)); | ||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||
| isProjectsValid = validProjectsValue.includes(changelog.projects); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if (!isProjectsValid) { | ||||||||||||||||||||||||||||||||
| console.error(`PR changelog has invalid project: ${changelog.projects}\nExpected one, or more of: ${validProjectsValues}`) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| let isDescriptionValid = true; | ||||||||||||||||||||||||||||||||
| if (changelog.description.trim() === '<insert-changelog-description-here>') { | ||||||||||||||||||||||||||||||||
| console.error('PR changelog description has not been updated!') | ||||||||||||||||||||||||||||||||
| isDescriptionValid = false; | ||||||||||||||||||||||||||||||||
| } else if (!changelog.description.trim()) { | ||||||||||||||||||||||||||||||||
| console.error('PR changelog description field is missing!') | ||||||||||||||||||||||||||||||||
| isDescriptionValid = false; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if (!isCompatibilityValid || !isTypeValid || !isProjectsValid || !isDescriptionValid) { | ||||||||||||||||||||||||||||||||
| console.error('Failed PR changelog checks!'); | ||||||||||||||||||||||||||||||||
| process.exit(1); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| - name: Pass (no-op) | ||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||
| echo "This check is a no-op. Changelog validation is now performed by" | ||||||||||||||||||||||||||||||||
| echo "the 'check-changelog-fragment' workflow (check-changelog-fragment.yml)." | ||||||||||||||||||||||||||||||||
|
Comment on lines
+18
to
+24
Check warningCode scanning / CodeQL Workflow does not contain permissions Medium
Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}
Copilot AutofixAI 13 days ago To fix the problem, explicitly restrict the The single best fix without changing functionality is to add a permissions:
contents: readNo imports or additional methods are needed, since this is a YAML workflow file, not application code.
Suggested changeset
1
.github/workflows/check-pr-changelog.yml
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
|
||||||||||||||||||||||||||||||||
Check warning
Code scanning / CodeQL
Workflow does not contain permissions Medium
Copilot Autofix
AI 13 days ago
In general, the fix is to explicitly define the GITHUB_TOKEN permissions for this workflow or job so that they are as restrictive as possible while still allowing the job to function. Since this job only checks changelog fragments and does not modify repository contents or PRs, it only needs read access to repository contents. GitHub recommends starting from
contents: readas a minimal safe baseline.The best targeted fix here is to add a
permissionsblock for thecheck-fragmentjob, directly under the job’snameorruns-onkeys. That keeps the scope narrow and avoids affecting other workflows. We will setcontents: read, which is sufficient foractions/checkoutand any localgitoperations. No other permission types (likepull-requestsorissues) are required because the job only uses the event payload already provided by GitHub, not additional API calls.Concretely: in
.github/workflows/check-changelog-fragment.yml, within thejobs: check-fragment:section starting at line 9, insert:indented to align with
name:andruns-on:. No imports, methods, or additional definitions are needed because this is a pure YAML configuration change.