Skip to content
Draft
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
Empty file added .changes/header.tpl.md
Empty file.
Empty file added .changes/unreleased/.gitkeep
Empty file.
59 changes: 59 additions & 0 deletions .changie.yaml
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
40 changes: 19 additions & 21 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
# Changelog

```yaml
- description: |
<insert-changelog-description-here>
# uncomment types applicable to the change:
type:
# - feature # introduces a new feature
# - breaking # the API has changed in a breaking way
# - compatible # the API has changed but is non-breaking
# - optimisation # measurable performance improvements
# - refactoring # QoL changes
# - bugfix # fixes a defect
# - test # fixes/modifies tests
# - maintenance # not directly related to the code
# - release # related to a new release preparation
# - documentation # change in code docs, haddocks...
# uncomment at least one main project this PR is associated with
projects:
# - cardano-api
# - cardano-api-gen
# - cardano-rpc
# - cardano-wasm
Add a changelog fragment by running the following command and committing the generated file:

```bash
changie new --project <package>
```

Available packages: `cardano-api`, `cardano-api-gen`, `cardano-rpc`, `cardano-wasm`

Available kinds:

| Kind | When to use |
|------|-------------|
| `breaking` | Removed or changed exported API in a backwards-incompatible way |
| `feature` | New exported function, type, or behaviour |
| `compatible` | Changed API in a backwards-compatible way |
| `bugfix` | Fixed incorrect behaviour |
| `optimisation` | Measurable performance improvement with no API change |

> If this PR does not need a changelog entry (e.g. documentation, CI, tests only),
> add the **`no-changelog`** label to skip this requirement.

# Context

Additional context for the PR goes here. If the PR fixes a particular issue please provide a [link](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword=) to the issue.
Expand Down
137 changes: 137 additions & 0 deletions .github/workflows/check-changelog-fragment.yml
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
Comment on lines +10 to +137

Check warning

Code 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: {contents: read}

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: read as a minimal safe baseline.

The best targeted fix here is to add a permissions block for the check-fragment job, directly under the job’s name or runs-on keys. That keeps the scope narrow and avoids affecting other workflows. We will set contents: read, which is sufficient for actions/checkout and any local git operations. No other permission types (like pull-requests or issues) 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 the jobs: check-fragment: section starting at line 9, insert:

    permissions:
      contents: read

indented to align with name: and runs-on:. No imports, methods, or additional definitions are needed because this is a pure YAML configuration change.

Suggested changeset 1
.github/workflows/check-changelog-fragment.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/check-changelog-fragment.yml b/.github/workflows/check-changelog-fragment.yml
--- a/.github/workflows/check-changelog-fragment.yml
+++ b/.github/workflows/check-changelog-fragment.yml
@@ -9,6 +9,8 @@
   check-fragment:
     name: Changelog fragment present and valid
     runs-on: ubuntu-latest
+    permissions:
+      contents: read
     steps:
       - name: Skip on merge_group
         if: github.event_name == 'merge_group'
EOF
@@ -9,6 +9,8 @@
check-fragment:
name: Changelog fragment present and valid
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Skip on merge_group
if: github.event_name == 'merge_group'
Copilot is powered by AI and may make mistakes. Always verify output.
107 changes: 15 additions & 92 deletions .github/workflows/check-pr-changelog.yml
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 warning

Code 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 Autofix

AI 13 days ago

To fix the problem, explicitly restrict the GITHUB_TOKEN permissions for this workflow to the minimum required. Since this job is a pure no-op that only runs echo commands and does not interact with the repository or GitHub APIs, it can safely run with contents: read (or even contents: none if supported). Setting the permissions at the workflow root applies them to all jobs in this file and best matches the CodeQL recommendation.

The single best fix without changing functionality is to add a permissions block at the top level of .github/workflows/check-pr-changelog.yml, alongside name and on. Insert this block after the name: declaration (around line 11), for example:

permissions:
  contents: read

No 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

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/check-pr-changelog.yml b/.github/workflows/check-pr-changelog.yml
--- a/.github/workflows/check-pr-changelog.yml
+++ b/.github/workflows/check-pr-changelog.yml
@@ -8,6 +8,9 @@
 
 name: Check if PR changelog was filled correctly (legacy — no-op)
 
+permissions:
+  contents: read
+
 on:
   merge_group:
   pull_request:
EOF
@@ -8,6 +8,9 @@

name: Check if PR changelog was filled correctly (legacy — no-op)

permissions:
contents: read

on:
merge_group:
pull_request:
Copilot is powered by AI and may make mistakes. Always verify output.
Loading
Loading