Skip to content

Commit 1eeef45

Browse files
committed
Merge branch 'main' into CLD-2707/add-delete-resources-changeset-for-batched-multi-resource-datastore-deletion
2 parents 1cad199 + 2960ced commit 1eeef45

61 files changed

Lines changed: 1389 additions & 1149 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/pull-request-conventional.yml

Lines changed: 157 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,15 @@ name: pull-request-conventional
33

44
on:
55
pull_request:
6-
branches: [ main ]
7-
types: [ opened, synchronize, reopened, edited ]
6+
branches: [main]
7+
types: [opened, synchronize, reopened, edited]
8+
merge_group:
9+
types: [checks_requested]
10+
11+
# Avoid stale sticky comments when the PR title or branch updates quickly.
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.merge_group.head_sha }}
14+
cancel-in-progress: true
815

916
permissions:
1017
contents: read
@@ -18,6 +25,153 @@ jobs:
1825
name: Lint PR title (Conventional Commits)
1926
runs-on: ubuntu-latest
2027
steps:
21-
- uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
28+
# Semantic PR action expects pull_request context; titles are enforced on PR events prior to merge queue.
29+
- name: Semantic PR Linter
30+
if: github.event_name != 'merge_group'
31+
uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
2232
env:
2333
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
34+
35+
# Auto-pass the step if it's running inside the Merge Queue
36+
- name: Bypass for Merge Queue
37+
if: github.event_name == 'merge_group'
38+
run: echo "PR title already verified prior to entering the merge queue. Bypassing."
39+
40+
release-bump-preview:
41+
name: Release bump preview
42+
if: github.event_name == 'pull_request'
43+
runs-on: ubuntu-latest
44+
permissions:
45+
contents: read
46+
pull-requests: write
47+
steps:
48+
- name: Checkout base branch
49+
uses: actions/checkout@v6
50+
with:
51+
ref: ${{ github.event.pull_request.base.sha }}
52+
sparse-checkout: |
53+
.release-please-manifest.json
54+
sparse-checkout-cone-mode: false
55+
56+
- name: Compute release bump
57+
id: bump
58+
uses: actions/github-script@v9.0.0
59+
with:
60+
script: |
61+
const fs = require('fs');
62+
63+
const title = context.payload.pull_request.title;
64+
const body = context.payload.pull_request.body ?? '';
65+
const manifest = JSON.parse(fs.readFileSync('.release-please-manifest.json', 'utf8'));
66+
const current = manifest['.'];
67+
68+
const releaseAsMatch = body.match(/Release-As:\s*(\d+\.\d+\.\d+)/i);
69+
// handle release-as in PR description
70+
if (releaseAsMatch) {
71+
const next = releaseAsMatch[1];
72+
core.setOutput('comment', [
73+
'## Release impact (release-please)',
74+
'',
75+
`| | |`,
76+
`|---|---|`,
77+
`| **Current version** | \`${current}\` (on \`${context.payload.pull_request.base.ref}\`) |`,
78+
`| **After merge** | \`${next}\` (**forced** via \`Release-As\`) |`,
79+
'',
80+
'This PR description contains `Release-As: x.y.z`, which release-please uses to force the next version.',
81+
'',
82+
'> Ensure the squash commit body includes `Release-As: ' + next + '` if your repo does not copy the PR description into the commit body.',
83+
].join('\n'));
84+
return;
85+
}
86+
87+
const headerMatch = title.match(/^(\w+)(?:\([^)]*\))?(!)?:\s*.+/);
88+
const breakingInBody = /BREAKING[- ]CHANGE:/i.test(body);
89+
90+
let bump = 'none';
91+
let type = null;
92+
let breaking = breakingInBody;
93+
94+
if (headerMatch) {
95+
type = headerMatch[1].toLowerCase();
96+
breaking = breaking || Boolean(headerMatch[2]);
97+
}
98+
99+
if (breaking) {
100+
bump = 'major';
101+
} else if (type === 'feat') {
102+
bump = 'minor';
103+
} else if (type === 'fix' || type === 'perf' || type === 'revert') {
104+
bump = 'patch';
105+
}
106+
107+
function bumpVersion(version, level) {
108+
const [major, minor, patch] = version.split('.').map(Number);
109+
switch (level) {
110+
case 'major':
111+
return `${major + 1}.0.0`;
112+
case 'minor':
113+
return `${major}.${minor + 1}.0`;
114+
case 'patch':
115+
return `${major}.${minor}.${patch + 1}`;
116+
default:
117+
return null;
118+
}
119+
}
120+
121+
const next = bumpVersion(current, bump);
122+
const bumpLabel = bump === 'none' ? 'no release' : `**${bump}**`;
123+
124+
const lines = [
125+
'## Release impact (release-please)',
126+
'',
127+
'| | |',
128+
'|---|---|',
129+
`| **Current version** | \`${current}\` (on \`${context.payload.pull_request.base.ref}\`) |`,
130+
];
131+
132+
if (next) {
133+
lines.push(`| **After merge** | \`${next}\` (${bumpLabel} bump) |`);
134+
} else {
135+
lines.push(`| **After merge** | no version bump (${bumpLabel}) |`);
136+
}
137+
138+
lines.push(
139+
'',
140+
`PR title: \`${title}\``,
141+
'',
142+
);
143+
144+
if (bump === 'none') {
145+
lines.push(
146+
'This title will **not** trigger a semver bump when squash-merged to `main`. Use `feat:` for a minor release, or `fix:` / `perf:` / `revert:` for a patch release.',
147+
'',
148+
);
149+
} else {
150+
lines.push(
151+
`Merging this PR as-is will contribute a **${bump}** bump to the next release-please release PR.`,
152+
'',
153+
);
154+
}
155+
156+
lines.push(
157+
'### Conventional commit → bump',
158+
'',
159+
'| Intent | PR title prefix | Bump |',
160+
'| --- | --- | --- |',
161+
'| Bug fix | `fix:` | patch |',
162+
'| New feature | `feat:` | minor |',
163+
'| Breaking change | `feat!:` / `fix!:` or `BREAKING CHANGE:` / `BREAKING-CHANGE:` in description | major |',
164+
'| No release | `chore:`, `docs:`, `ci:`, `refactor:`, etc. | none |',
165+
'',
166+
'Update the **PR title** before merge if you need a different bump (squash commit message = PR title).',
167+
'',
168+
'_Preview is based on this PR title only. The release-please release PR may include other unreleased commits already on `main`._',
169+
);
170+
171+
core.setOutput('comment', lines.join('\n'));
172+
173+
- name: Post release bump preview
174+
uses: marocchino/sticky-pull-request-comment@0ea0beb66eb9baf113663a64ec522f60e49231c0 # v3.0.4
175+
with:
176+
header: release-please-preview
177+
message: ${{ steps.bump.outputs.comment }}

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "0.6.0"
2+
".": "0.7.1"
33
}

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
# Changelog
22

3+
## [0.7.1](https://github.com/smartcontractkit/cld-changesets/compare/v0.7.0...v0.7.1) (2026-06-10)
4+
5+
6+
### Bug Fixes
7+
8+
* use specific type to avoid requiring the address in datastore delete ([#84](https://github.com/smartcontractkit/cld-changesets/issues/84)) ([846077c](https://github.com/smartcontractkit/cld-changesets/commit/846077c6291a1f2ecf0f37a1dabb66cb305778f6))
9+
10+
## [0.7.0](https://github.com/smartcontractkit/cld-changesets/compare/v0.6.0...v0.7.0) (2026-06-10)
11+
12+
13+
### ⚠ BREAKING CHANGES
14+
15+
* Delete `pkg/contract/mcms/view/v1_0` and `GenerateMCMSWithTimelockView`.
16+
17+
### Features
18+
19+
* remove EVM MCMS view generation ([#77](https://github.com/smartcontractkit/cld-changesets/issues/77)) ([dcfe322](https://github.com/smartcontractkit/cld-changesets/commit/dcfe322adee2dd8ff8cf89d0d3fbad9204631be9))
20+
21+
22+
### Bug Fixes
23+
24+
* change type from address ref key to address ref ([#83](https://github.com/smartcontractkit/cld-changesets/issues/83)) ([23a126d](https://github.com/smartcontractkit/cld-changesets/commit/23a126d5b1bbf17f44f79fa729140113986c7adf))
25+
326
## [0.6.0](https://github.com/smartcontractkit/cld-changesets/compare/v0.5.0...v0.6.0) (2026-06-02)
427

528

datastore/changesets/delete_address_ref.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,29 @@ import (
88
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
99
cldfops "github.com/smartcontractkit/chainlink-deployments-framework/operations"
1010

11+
"github.com/smartcontractkit/cld-changesets/datastore/internal/keys"
1112
"github.com/smartcontractkit/cld-changesets/datastore/operations"
1213
)
1314

1415
// DeleteAddressRefChangeset deletes address ref entries from the Datastore.
1516
type DeleteAddressRefChangeset struct{}
1617

1718
type DeleteAddressRefChangesetInput struct {
18-
AddressRefKeys []cldfdatastore.AddressRefKey `json:"addressRefKeys"`
19+
AddressRefKeys []keys.AddressRefKey `json:"addressRefKeys"`
20+
}
21+
22+
func (i DeleteAddressRefChangesetInput) addressRefKeys() ([]cldfdatastore.AddressRefKey, error) {
23+
fwKeys := make([]cldfdatastore.AddressRefKey, 0, len(i.AddressRefKeys))
24+
for idx, inputKey := range i.AddressRefKeys {
25+
key, err := inputKey.ToFrameworkKey()
26+
if err != nil {
27+
return nil, fmt.Errorf("addressRefKeys[%d]: %w", idx, err)
28+
}
29+
30+
fwKeys = append(fwKeys, key)
31+
}
32+
33+
return fwKeys, nil
1934
}
2035

2136
// VerifyPreconditions ensures the input is valid.
@@ -27,7 +42,12 @@ func (DeleteAddressRefChangeset) VerifyPreconditions(e cldf.Environment, input D
2742
return errors.New("missing datastore in environment")
2843
}
2944

30-
for _, key := range input.AddressRefKeys {
45+
fwKeys, err := input.addressRefKeys()
46+
if err != nil {
47+
return fmt.Errorf("invalid address ref keys input: %w", err)
48+
}
49+
50+
for _, key := range fwKeys {
3151
_, err := e.DataStore.Addresses().Get(key)
3252
if err != nil {
3353
if errors.Is(err, cldfdatastore.ErrAddressRefNotFound) {
@@ -45,6 +65,10 @@ func (DeleteAddressRefChangeset) VerifyPreconditions(e cldf.Environment, input D
4565

4666
// Apply executes the changeset, staging the address refs to be deleted from the Datastore.
4767
func (DeleteAddressRefChangeset) Apply(e cldf.Environment, input DeleteAddressRefChangesetInput) (cldf.ChangesetOutput, error) {
68+
if _, err := input.addressRefKeys(); err != nil {
69+
return cldf.ChangesetOutput{}, fmt.Errorf("invalid address ref keys input: %w", err)
70+
}
71+
4872
deps := operations.DeleteAddressRefDeps{DataStore: e.DataStore}
4973
opInput := operations.DeleteAddressRefInput{AddressRefKeys: input.AddressRefKeys}
5074

datastore/changesets/delete_address_ref_test.go

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package changesets
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"testing"
67

@@ -11,6 +12,8 @@ import (
1112
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
1213
cldfoperations "github.com/smartcontractkit/chainlink-deployments-framework/operations"
1314
cldflogger "github.com/smartcontractkit/chainlink-deployments-framework/pkg/logger"
15+
16+
"github.com/smartcontractkit/cld-changesets/datastore/internal/keys"
1417
)
1518

1619
func TestDeleteAddressRefChangeset_VerifyPreconditions(t *testing.T) {
@@ -32,14 +35,14 @@ func TestDeleteAddressRefChangeset_VerifyPreconditions(t *testing.T) {
3235
DataStore: testDataStoreWithAddressRefs(t, addressRef1).Seal(),
3336
},
3437
input: DeleteAddressRefChangesetInput{
35-
AddressRefKeys: []cldfdatastore.AddressRefKey{addressRef1.Key(), addressRef2.Key()},
38+
AddressRefKeys: []keys.AddressRefKey{addressRefKey(addressRef1), addressRefKey(addressRef2)},
3639
},
3740
},
3841
{
3942
name: "failure: missing datastore",
4043
env: cldf.Environment{},
4144
input: DeleteAddressRefChangesetInput{
42-
AddressRefKeys: []cldfdatastore.AddressRefKey{addressRef1.Key(), addressRef2.Key()},
45+
AddressRefKeys: []keys.AddressRefKey{addressRefKey(addressRef1), addressRefKey(addressRef2)},
4346
},
4447
wantErr: "missing datastore in environment",
4548
},
@@ -48,15 +51,15 @@ func TestDeleteAddressRefChangeset_VerifyPreconditions(t *testing.T) {
4851
env: cldf.Environment{
4952
DataStore: cldfdatastore.NewMemoryDataStore().Seal(),
5053
},
51-
input: DeleteAddressRefChangesetInput{AddressRefKeys: []cldfdatastore.AddressRefKey{}},
54+
input: DeleteAddressRefChangesetInput{AddressRefKeys: []keys.AddressRefKey{}},
5255
wantErr: "missing address ref keys input",
5356
},
5457
{
5558
name: "failure: address ref entry does not exist",
5659
env: cldf.Environment{
5760
DataStore: cldfdatastore.NewMemoryDataStore().Seal(),
5861
},
59-
input: DeleteAddressRefChangesetInput{AddressRefKeys: []cldfdatastore.AddressRefKey{addressRef2.Key()}},
62+
input: DeleteAddressRefChangesetInput{AddressRefKeys: []keys.AddressRefKey{addressRefKey(addressRef2)}},
6063
wantErr: fmt.Sprintf("address ref entry for chain selector %v, type %v, version %v and qualifier %q does not exist", addressRef2.ChainSelector, addressRef2.Type, addressRef2.Version, addressRef2.Qualifier),
6164
},
6265
}
@@ -75,6 +78,27 @@ func TestDeleteAddressRefChangeset_VerifyPreconditions(t *testing.T) {
7578
}
7679
}
7780

81+
func TestDeleteAddressRefChangeset_MissingVersion(t *testing.T) {
82+
t.Parallel()
83+
84+
input := DeleteAddressRefChangesetInput{
85+
AddressRefKeys: []keys.AddressRefKey{{
86+
ChainSelector: 1234,
87+
Type: "MyContract",
88+
Qualifier: "q1",
89+
}},
90+
}
91+
env := cldf.Environment{
92+
DataStore: cldfdatastore.NewMemoryDataStore().Seal(),
93+
}
94+
95+
err := DeleteAddressRefChangeset{}.VerifyPreconditions(env, input)
96+
require.ErrorIs(t, err, cldfdatastore.ErrAddressRefVersionRequired)
97+
98+
_, err = DeleteAddressRefChangeset{}.Apply(env, input)
99+
require.ErrorIs(t, err, cldfdatastore.ErrAddressRefVersionRequired)
100+
}
101+
78102
func TestDeleteAddressRefChangeset_Apply(t *testing.T) {
79103
t.Parallel()
80104

@@ -96,7 +120,7 @@ func TestDeleteAddressRefChangeset_Apply(t *testing.T) {
96120
OperationsBundle: cldfoperations.NewBundle(t.Context, cldflogger.Test(t), cldfoperations.NewMemoryReporter()),
97121
},
98122
input: DeleteAddressRefChangesetInput{
99-
AddressRefKeys: []cldfdatastore.AddressRefKey{addressRef1.Key(), addressRef2.Key()},
123+
AddressRefKeys: []keys.AddressRefKey{addressRefKey(addressRef1), addressRefKey(addressRef2)},
100124
},
101125
wantDeletedKeys: []string{addressRef1.Key().String(), addressRef2.Key().String()},
102126
},
@@ -119,3 +143,41 @@ func TestDeleteAddressRefChangeset_Apply(t *testing.T) {
119143
})
120144
}
121145
}
146+
147+
func TestDeleteAddressRefChangeset_JSONRoundTrip(t *testing.T) {
148+
t.Parallel()
149+
150+
version := semver.MustParse("1.0.0")
151+
addressRef1 := cldfdatastore.AddressRef{Address: "0x01", ChainSelector: 1234, Type: "MyContract", Version: version, Qualifier: "q1"}
152+
addressRef2 := cldfdatastore.AddressRef{Address: "0x02", ChainSelector: 5678, Type: "OtherContract", Version: version, Qualifier: ""}
153+
154+
raw := `{"addressRefKeys":[{"chainSelector":1234,"type":"MyContract","version":"1.0.0","qualifier":"q1"},{"chainSelector":5678,"type":"OtherContract","version":"1.0.0","qualifier":""}]}`
155+
156+
var got DeleteAddressRefChangesetInput
157+
require.NoError(t, json.Unmarshal([]byte(raw), &got))
158+
require.Equal(t, []keys.AddressRefKey{addressRefKey(addressRef1), addressRefKey(addressRef2)}, got.AddressRefKeys)
159+
160+
env := cldf.Environment{
161+
DataStore: testDataStoreWithAddressRefs(t, addressRef1, addressRef2).Seal(),
162+
OperationsBundle: cldfoperations.NewBundle(t.Context, cldflogger.Test(t), cldfoperations.NewMemoryReporter()),
163+
}
164+
165+
require.NoError(t, DeleteAddressRefChangeset{}.VerifyPreconditions(env, got))
166+
167+
out, err := DeleteAddressRefChangeset{}.Apply(env, got)
168+
require.NoError(t, err)
169+
memDS := out.DataStore.(*cldfdatastore.MemoryDataStore)
170+
require.ElementsMatch(t,
171+
[]string{addressRef1.Key().String(), addressRef2.Key().String()},
172+
memDS.AddressRefStore.DeletedRemoteKeys,
173+
)
174+
}
175+
176+
func addressRefKey(ref cldfdatastore.AddressRef) keys.AddressRefKey {
177+
return keys.AddressRefKey{
178+
ChainSelector: ref.ChainSelector,
179+
Type: ref.Type,
180+
Version: ref.Version,
181+
Qualifier: ref.Qualifier,
182+
}
183+
}

0 commit comments

Comments
 (0)