Skip to content

Commit 8218e00

Browse files
authored
ci: fetch draft HIPs live at build time (stop bot pushes to main) (#1510)
Signed-off-by: Michael Garber <michael.garber@hashgraph.com>
1 parent 55933f1 commit 8218e00

2 files changed

Lines changed: 82 additions & 160 deletions

File tree

Lines changed: 19 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,174 +1,36 @@
1-
name: Update Draft HIPs Data
1+
name: Refresh Draft HIPs on Site
2+
3+
# Draft HIPs (open PRs that add a new HIP) are fetched live during the site
4+
# build (see site/scripts/build-data.js). This job just triggers a Netlify
5+
# rebuild on a schedule so the published site stays current, WITHOUT committing
6+
# any generated data to main. Requires a NETLIFY_BUILD_HOOK repo secret; if it
7+
# is not set, the job is a no-op.
28

39
on:
410
schedule:
5-
- cron: "0 */6 * * *" # Runs every 6 hours
6-
workflow_dispatch: # Allows manual triggering
11+
- cron: "0 */6 * * *" # every 6 hours
12+
workflow_dispatch: # allow manual triggering
713

814
permissions:
915
contents: read
1016

1117
jobs:
12-
update-draft-hips:
13-
if: ${{ github.ref == 'refs/heads/main' }} # Only run on main branch
18+
trigger-site-rebuild:
19+
if: ${{ github.ref == 'refs/heads/main' }}
1420
runs-on: hiero-improvement-proposals-linux-medium
15-
permissions:
16-
contents: read
1721
steps:
1822
- name: Harden the runner (Audit all outbound calls)
1923
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
2024
with:
2125
egress-policy: audit
2226

23-
- name: Checkout Code
24-
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
25-
with:
26-
token: ${{ secrets.GH_ACCESS_TOKEN }}
27-
ref: 'main'
28-
29-
- name: Import GPG Key
30-
id: gpg_importer
31-
uses: step-security/ghaction-import-gpg@69c854a83c7f79463f8bdf46772ab09826c560cd # v6.3.1
32-
with:
33-
git_commit_gpgsign: true
34-
git_tag_gpgsign: true
35-
git_user_signingkey: true
36-
gpg_private_key: ${{ secrets.GPG_KEY_CONTENTS }}
37-
passphrase: ${{ secrets.GPG_KEY_PASSPHRASE }}
38-
39-
- name: Setup Node.js
40-
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
41-
with:
42-
node-version: "20"
43-
44-
- name: Create Script
45-
run: |
46-
mkdir -p _data
47-
cat << 'EOF' > fetch-draft-hips.js
48-
const https = require('https');
49-
const fs = require('fs');
50-
51-
async function makeGraphQLRequest(query, token) {
52-
return new Promise((resolve, reject) => {
53-
const options = {
54-
hostname: 'api.github.com',
55-
path: '/graphql',
56-
method: 'POST',
57-
headers: {
58-
'Authorization': `Bearer ${token}`,
59-
'Content-Type': 'application/json',
60-
'User-Agent': 'Node.js'
61-
}
62-
};
63-
64-
const req = https.request(options, (res) => {
65-
let data = '';
66-
res.on('data', chunk => { data += chunk; });
67-
res.on('end', () => resolve(JSON.parse(data)));
68-
});
69-
70-
req.on('error', reject);
71-
req.write(JSON.stringify({ query }));
72-
req.end();
73-
});
74-
}
75-
76-
async function getAllPRs() {
77-
const query = `
78-
query {
79-
repository(name: "hiero-improvement-proposals", owner: "hiero-ledger") {
80-
pullRequests(first: 100, states: [OPEN], orderBy: {field: CREATED_AT, direction: DESC}) {
81-
nodes {
82-
title
83-
number
84-
url
85-
headRefOid
86-
files(first: 100) {
87-
edges {
88-
node {
89-
path
90-
changeType
91-
additions
92-
deletions
93-
}
94-
}
95-
}
96-
author {
97-
login
98-
}
99-
}
100-
}
101-
}
102-
}
103-
`;
104-
105-
try {
106-
const result = await makeGraphQLRequest(query, process.env.GITHUB_TOKEN);
107-
108-
if (result.errors) {
109-
console.error('GraphQL errors:', result.errors);
110-
process.exit(1);
111-
}
112-
113-
// Check if data and the expected path to nodes exist
114-
if (!result.data || !result.data.repository || !result.data.repository.pullRequests || !result.data.repository.pullRequests.nodes) {
115-
console.error('Unexpected GraphQL response structure:', result);
116-
process.exit(1);
117-
}
118-
119-
return result.data.repository.pullRequests.nodes;
120-
} catch (error) {
121-
console.error('Error fetching PRs:', error);
122-
throw error;
123-
}
124-
}
125-
126-
// Run the main function
127-
getAllPRs().then(allPRs => {
128-
const draftHIPPRs = allPRs.filter(pr => {
129-
if (!pr.files || !pr.files.edges) {
130-
return false;
131-
}
132-
const hipFiles = pr.files.edges.filter(edge => {
133-
const fileNode = edge.node;
134-
const isNewHIPFile = /^HIP\/hip-[a-zA-Z0-9-]+\.md$/.test(fileNode.path);
135-
return fileNode.changeType === 'ADDED' && isNewHIPFile;
136-
}).map(edge => edge.node);
137-
138-
return hipFiles.length > 0;
139-
});
140-
141-
const outputPath = '_data/draft_hips.json';
142-
143-
if (fs.existsSync(outputPath)) {
144-
console.log(`Removing existing file: ${outputPath}`);
145-
fs.unlinkSync(outputPath);
146-
}
147-
148-
console.log(`Writing ${draftHIPPRs.length} filtered PRs to: ${outputPath}`);
149-
fs.writeFileSync(outputPath, JSON.stringify(draftHIPPRs, null, 2));
150-
}).catch(error => {
151-
console.error('Failed to fetch and filter PRs:', error);
152-
process.exit(1);
153-
});
154-
EOF
155-
156-
- name: Run Script
157-
run: node fetch-draft-hips.js
158-
env:
159-
GITHUB_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
160-
161-
- name: Commit and Push Changes
27+
- name: Trigger Netlify build (re-fetches open draft-HIP PRs at build time)
16228
env:
163-
GITHUB_USER_EMAIL: ${{ vars.GIT_USER_EMAIL }}
164-
GITHUB_USER_NAME: ${{ vars.GIT_USER_NAME }}
29+
NETLIFY_BUILD_HOOK: ${{ secrets.NETLIFY_BUILD_HOOK }}
16530
run: |
166-
set -e
167-
git config --local user.email "$GITHUB_USER_EMAIL"
168-
git config --local user.name "$GITHUB_USER_NAME"
169-
git add _data/draft_hips.json
170-
git diff --cached --quiet && echo "No changes to commit" && exit 0
171-
172-
git commit -s -S -m "Update draft HIPs data [skip ci]"
173-
git push origin main
174-
set +e
31+
if [ -z "$NETLIFY_BUILD_HOOK" ]; then
32+
echo "NETLIFY_BUILD_HOOK secret not set — skipping."
33+
echo "Add the secret (Netlify build hook URL) to enable scheduled draft-HIP refreshes."
34+
exit 0
35+
fi
36+
curl -fsS -X POST -d '{}' "$NETLIFY_BUILD_HOOK" && echo "Netlify build triggered."

site/scripts/build-data.js

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,72 @@ for (const file of files) {
5353
console.log(`Parsed ${hips.length} merged HIPs`);
5454

5555
// ---- Fetch draft HIPs from open PRs ----
56+
// Draft HIPs exist only as open PRs (not yet merged). We fetch that list live at
57+
// build time via the GitHub GraphQL API, so nothing has to be committed to the
58+
// repo. Falls back to a committed _data/draft_hips.json when no GITHUB_TOKEN is
59+
// available (e.g. local dev), preserving the previous behavior.
5660
const draftHipsPath = path.join(DATA_DIR, 'draft_hips.json');
5761

58-
async function fetchDraftHips() {
59-
if (!fs.existsSync(draftHipsPath)) return;
62+
async function getDraftPRs() {
63+
const token = process.env.GITHUB_TOKEN;
64+
65+
if (token) {
66+
const query = `query {
67+
repository(owner: "${REPO_OWNER}", name: "${REPO_NAME}") {
68+
pullRequests(first: 100, states: [OPEN], orderBy: { field: CREATED_AT, direction: DESC }) {
69+
nodes {
70+
title
71+
number
72+
url
73+
headRefOid
74+
files(first: 100) { edges { node { path changeType additions deletions } } }
75+
author { login }
76+
}
77+
}
78+
}
79+
}`;
6080

61-
const draftPRs = JSON.parse(fs.readFileSync(draftHipsPath, 'utf-8'));
81+
try {
82+
const res = await fetch('https://api.github.com/graphql', {
83+
method: 'POST',
84+
headers: {
85+
Authorization: `Bearer ${token}`,
86+
'Content-Type': 'application/json',
87+
'User-Agent': 'hips-build',
88+
},
89+
body: JSON.stringify({ query }),
90+
});
91+
const json = await res.json();
92+
if (json.errors) {
93+
console.warn(` Draft-HIP PR fetch: ${json.errors[0]?.message || 'GraphQL error'} — falling back to committed data`);
94+
} else {
95+
const nodes = json.data?.repository?.pullRequests?.nodes || [];
96+
// Keep only PRs that ADD a new HIP/hip-*.md file — the same filter the
97+
// old update-draft-hips.yml workflow used to produce _data/draft_hips.json.
98+
const drafts = nodes.filter(pr =>
99+
(pr.files?.edges || []).some(e =>
100+
e.node.changeType === 'ADDED' &&
101+
/^HIP\/hip-[A-Za-z0-9-]+\.md$/.test(e.node.path)
102+
)
103+
);
104+
console.log(`Fetched ${drafts.length} open draft-HIP PRs from GitHub`);
105+
return drafts;
106+
}
107+
} catch (e) {
108+
console.warn(` Draft-HIP PR fetch failed (${e.message}) — falling back to committed data`);
109+
}
110+
} else {
111+
console.log('No GITHUB_TOKEN set — using committed _data/draft_hips.json if present');
112+
}
113+
114+
if (fs.existsSync(draftHipsPath)) {
115+
return JSON.parse(fs.readFileSync(draftHipsPath, 'utf-8'));
116+
}
117+
return [];
118+
}
119+
120+
async function fetchDraftHips() {
121+
const draftPRs = await getDraftPRs();
62122
let fetched = 0;
63123
let skipped = 0;
64124

0 commit comments

Comments
 (0)