Skip to content

feat(Registration): redeemable release window with permissionless reclaim after expiry #392

feat(Registration): redeemable release window with permissionless reclaim after expiry

feat(Registration): redeemable release window with permissionless reclaim after expiry #392

Workflow file for this run

name: PR
on:
pull_request:
types: [opened, edited, synchronize, reopened, labeled, unlabeled, ready_for_review]
permissions:
contents: read
pull-requests: write
issues: write
jobs:
title:
name: PR Title
runs-on: ubuntu-latest
steps:
- name: Check PR title
id: title
uses: amannn/action-semantic-pull-request@v5
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
feat
fix
docs
style
refactor
test
chore
ci
perf
revert
requireScope: false
subjectPattern: ^[a-zA-Z][-a-zA-Z0-9 .,():+/]+$
- name: Process results
id: run
run: |
if [ "${{ steps.title.outcome }}" == "success" ]; then
echo "result=PR Title Valid" >> "$GITHUB_OUTPUT"
else
echo "result=Invalid - Use type(scope): description (e.g. feat(registry): add protocol lookup)" >> "$GITHUB_OUTPUT"
fi
- name: Update PR comment
if: always()
uses: actions/github-script@v7
env:
SECTION: PR Title
RESULT: ${{ steps.run.outputs.result }}
with:
script: |
const marker = "<!-- ci-summary -->";
const detailsMarker = "<!-- details-section -->";
const section = process.env.SECTION;
const result = process.env.RESULT || "PR Title Check Passed";
const { owner, repo } = context.repo;
const issue_number = context.payload.pull_request.number;
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number, per_page: 100,
});
const existing = comments.find(c =>
c.user?.login === "github-actions[bot]" && c.body?.includes(marker)
);
let rows = {};
let detailsSection = "";
if (existing?.body) {
const parts = existing.body.split(detailsMarker);
const tableSection = parts[0] || "";
if (parts[1]) detailsSection = `\n${detailsMarker}${parts[1]}`;
const lines = tableSection.split("\n");
for (const line of lines) {
const match = line.match(/^\| ([^|]+) \| ([^|]+) \|$/);
if (match) {
const name = match[1].trim();
if (name && name !== "Check" && !name.startsWith(":")) {
rows[name] = match[2].trim();
}
}
}
}
rows[section] = result;
const order = ["4naly3er Analysis", "Slither Analysis", "Contract Tests", "Coverage", "Documentation", "Format & Lint", "Deploy Contracts", "PR Title", "Labels"];
const sortedKeys = Object.keys(rows).sort((a, b) => {
const ai = order.indexOf(a), bi = order.indexOf(b);
return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
});
let table = `| Check | Result |\n|:------|:-------|\n`;
for (const key of sortedKeys) {
table += `| ${key} | ${rows[key]} |\n`;
}
let body = `${marker}\n## CI Summary\n\n${table}${detailsSection}`;
// GitHub issue/PR comments are capped at 65536 characters. Fall back
// to the summary table only when the cumulative body would overflow.
const MAX_BODY = 65000;
if (body.length > MAX_BODY) {
body = `${marker}\n## CI Summary\n\n${table}\n\n_Per-section details omitted: combined body exceeded the ${MAX_BODY}-char comment limit. Follow the **View Report** links above for each section's full output._`;
}
if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
} else {
await github.rest.issues.createComment({ owner, repo, issue_number, body });
}
- name: Fail if title invalid
if: steps.title.outcome == 'failure'
run: exit 1
labels:
name: Labels
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Auto-label
uses: actions/labeler@v5
continue-on-error: true
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Validate labels
id: run
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const pull_number = context.payload.pull_request.number;
const { data: pr } = await github.rest.pulls.get({
owner, repo, pull_number
});
const labels = pr.labels.map(l => l.name);
const required = [
"dotns-sdk", "smartcontracts", "other",
"scope: registration", "scope: resolver", "scope: store", "scope: pop",
"type: docs", "type: test", "dependencies"
];
const matched = labels.filter(l => required.includes(l));
const allLabels = labels.join(", ") || "None";
if (matched.length > 0) {
core.setOutput("result", `Passed - ${matched.join(", ")}`);
core.setOutput("details", allLabels);
} else {
core.setOutput("result", "Failed - Requires one of: dotns-sdk, smartcontracts, other, a scope label, type: docs, type: test, or dependencies");
core.setOutput("details", allLabels);
core.setFailed("Missing required label");
}
- name: Update PR comment
if: always()
uses: actions/github-script@v7
env:
SECTION: Labels
RESULT: ${{ steps.run.outputs.result }}
DETAILS: ${{ steps.run.outputs.details }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
with:
script: |
const marker = "<!-- ci-summary -->";
const detailsMarker = "<!-- details-section -->";
const section = process.env.SECTION;
const result = process.env.RESULT || "Unknown";
const details = process.env.DETAILS;
const runUrl = process.env.RUN_URL;
const { owner, repo } = context.repo;
const issue_number = context.payload.pull_request.number;
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number, per_page: 100,
});
const existing = comments.find(c =>
c.user?.login === "github-actions[bot]" && c.body?.includes(marker)
);
let rows = {};
let existingDetails = {};
if (existing?.body) {
const parts = existing.body.split(detailsMarker);
const tableSection = parts[0] || "";
const lines = tableSection.split("\n");
for (const line of lines) {
const match = line.match(/^\| ([^|]+) \| ([^|]+) \|$/);
if (match) {
const name = match[1].trim();
if (name && name !== "Check" && !name.startsWith(":")) {
rows[name] = match[2].trim();
}
}
}
const detailsRegex = /<details>\s*<summary><strong>([^<]+)<\/strong>.*?<\/summary>([\s\S]*?)<\/details>/g;
let detailMatch;
while ((detailMatch = detailsRegex.exec(existing.body)) !== null) {
existingDetails[detailMatch[1].trim()] = detailMatch[0];
}
}
rows[section] = result;
if (result.startsWith("Failed")) {
existingDetails[section] = `<details>\n<summary><strong>${section}</strong></summary>\n\nApplied: ${details}\n\nRequired: dotns-sdk, smartcontracts, other, a scope label, type: docs, type: test, or dependencies\n\n[View run](${runUrl})\n\n</details>`;
} else if (details && details !== "None") {
existingDetails[section] = `<details>\n<summary><strong>${section}</strong></summary>\n\n${details}\n\n</details>`;
} else {
delete existingDetails[section];
}
const order = ["4naly3er Analysis", "Slither Analysis", "Contract Tests", "Coverage", "Documentation", "Format & Lint", "Deploy Contracts", "PR Title", "Labels"];
const sortedKeys = Object.keys(rows).sort((a, b) => {
const ai = order.indexOf(a), bi = order.indexOf(b);
return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
});
let table = `| Check | Result |\n|:------|:-------|\n`;
for (const key of sortedKeys) {
table += `| ${key} | ${rows[key]} |\n`;
}
const detailsOrder = ["4naly3er Analysis", "Slither Analysis", "Contract Tests", "Coverage", "Documentation", "Format & Lint", "Deploy Contracts", "PR Title", "Labels"];
const sortedDetails = Object.keys(existingDetails).sort((a, b) => {
const ai = detailsOrder.indexOf(a), bi = detailsOrder.indexOf(b);
return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi);
});
let body = `${marker}\n## CI Summary\n\n${table}`;
if (sortedDetails.length > 0) {
body += `\n${detailsMarker}\n\n---\n\n${sortedDetails.map(k => existingDetails[k]).join("\n\n")}`;
}
// GitHub issue/PR comments are capped at 65536 characters. Fall back
// to the summary table only when the cumulative body would overflow.
const MAX_BODY = 65000;
if (body.length > MAX_BODY) {
body = `${marker}\n## CI Summary\n\n${table}\n\n_Per-section details omitted: combined body exceeded the ${MAX_BODY}-char comment limit. Follow the **View Report** links above for each section's full output._`;
}
if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
} else {
await github.rest.issues.createComment({ owner, repo, issue_number, body });
}