Skip to content

Commit ee86a6b

Browse files
authored
Merge pull request #687 from keymanapp/auto/A19S32-merge-master-into-staging
auto: A19S32 merge master into staging
2 parents 71b6baa + 51e9524 commit ee86a6b

61 files changed

Lines changed: 8914 additions & 14354 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/labeler.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
steps:
1010
- name: Update labels based on PR title
1111
id: labeler
12-
uses: fuxingloh/multi-labeler@f5bd7323b53b0833c1e4ed8d7b797ae995ef75b4 # v2.0.1
12+
uses: fuxingloh/multi-labeler@bcd50af464202999e57f556b4aefcf05a34abf85 # v5.0.0
1313
with:
1414
github-token: ${{secrets.GITHUB_TOKEN}}
1515
config-path: .github/multi-labeler.yml

.github/workflows/master_com-keyman-status.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
- name: Set up Node.js version
2020
uses: actions/setup-node@v1
2121
with:
22-
node-version: '22.x'
22+
node-version: '24.x'
2323

2424
- name: npm ci + build - public
2525
run: |
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#
2+
name: Redeliver failed webhook deliveries
3+
4+
# This workflow runs every 10 minutes starting at 3 minutes past the hour or when manually triggered.
5+
on:
6+
# It appears that cron scheduling is not currently reliable on GitHub, e.g.
7+
# https://upptime.js.org/blog/2021/01/22/github-actions-schedule-not-working/
8+
# schedule:
9+
# - cron: '3,13,23,33,43,53 * * * *'
10+
workflow_dispatch:
11+
12+
# This workflow will use the built in `GITHUB_TOKEN` to check out the repository contents. This grants `GITHUB_TOKEN` permission to do that.
13+
permissions:
14+
contents: read
15+
16+
#
17+
jobs:
18+
redeliver-failed-deliveries:
19+
name: Redeliver failed deliveries
20+
runs-on: ubuntu-latest
21+
steps:
22+
# This workflow will run a script that is stored in the repository. This step checks out the repository contents so that the workflow can access the script.
23+
- name: Check out repo content
24+
uses: actions/checkout@v6
25+
26+
# This step sets up Node.js. The script that this workflow will run uses Node.js.
27+
- name: Setup Node.js
28+
uses: actions/setup-node@v4
29+
with:
30+
node-version: '24.x'
31+
32+
# This step installs the octokit library. The script that this workflow will run uses the octokit library.
33+
- name: Install dependencies
34+
run: npm install octokit
35+
36+
# This step sets some environment variables, then runs a script to find and redeliver failed webhook deliveries.
37+
# - Replace `YOUR_SECRET_NAME` with the name of the secret where you stored your personal access token.
38+
# - Replace `YOUR_ORGANIZATION_NAME` with the name of the organization where the webhook was created.
39+
# - Replace `YOUR_HOOK_ID` with the ID of the webhook.
40+
# - Replace `YOUR_LAST_REDELIVERY_VARIABLE_NAME` with the name that you want to use for a configuration variable that will be stored in the repository where this workflow is stored. The name can be any string that contains only alphanumeric characters and `_`, and does not start with `GITHUB_` or a number. For more information, see [AUTOTITLE](/actions/learn-github-actions/variables#defining-configuration-variables-for-multiple-workflows).
41+
42+
- name: Run script
43+
env:
44+
TOKEN: ${{ secrets.KEYMAN_SERVER_STATUS_KEYMANAPP_WEBHOOK }}
45+
ORGANIZATION_NAME: 'keymanapp'
46+
HOOK_ID: '288390178'
47+
LAST_REDELIVERY_VARIABLE_NAME: 'keyman_github_webhook_last_redelivery'
48+
49+
WORKFLOW_REPO_NAME: ${{ github.event.repository.name }}
50+
WORKFLOW_REPO_OWNER: ${{ github.repository_owner }}
51+
run: |
52+
node .github/workflows/scripts/redeliver-failed-deliveries.mjs > $GITHUB_STEP_SUMMARY
53+
cat $GITHUB_STEP_SUMMARY
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
// This script uses GitHub's Octokit SDK to make API requests. For more information, see [AUTOTITLE](/rest/guides/scripting-with-the-rest-api-and-javascript).
2+
import { Octokit } from 'octokit';
3+
4+
//
5+
async function checkAndRedeliverWebhooks(TOKEN, ORGANIZATION_NAME, HOOK_ID, LAST_REDELIVERY_VARIABLE_NAME, WORKFLOW_REPO_NAME, WORKFLOW_REPO_OWNER) {
6+
// Create an instance of `Octokit` using the token values that were set in the GitHub Actions workflow.
7+
const octokit = new Octokit({
8+
auth: TOKEN,
9+
});
10+
11+
try {
12+
// Get the last time that this script ran from the configuration variable. If the variable is not defined, use the current time minus 24 hours.
13+
const lastStoredRedeliveryTime = await getVariable({
14+
variableName: LAST_REDELIVERY_VARIABLE_NAME,
15+
repoOwner: WORKFLOW_REPO_OWNER,
16+
repoName: WORKFLOW_REPO_NAME,
17+
octokit,
18+
});
19+
20+
const lastWebhookRedeliveryTime = lastStoredRedeliveryTime || (Date.now() - (24 * 60 * 60 * 1000)).toString();
21+
22+
// Record the time that this script started redelivering webhooks, less 1 second so we don't miss events that failed right now
23+
const newWebhookRedeliveryTime = (Date.now() - 1000).toString();
24+
25+
// Get the webhook deliveries that were delivered after `lastWebhookRedeliveryTime`.
26+
const deliveries = await fetchWebhookDeliveriesSince({
27+
lastWebhookRedeliveryTime,
28+
organizationName: ORGANIZATION_NAME,
29+
hookId: HOOK_ID,
30+
octokit,
31+
});
32+
33+
// Consolidate deliveries that have the same globally unique identifier (GUID). The GUID is constant across redeliveries of the same delivery.
34+
let deliveriesByGuid = {};
35+
for (const delivery of deliveries) {
36+
deliveriesByGuid[delivery.guid]
37+
? deliveriesByGuid[delivery.guid].push(delivery)
38+
: (deliveriesByGuid[delivery.guid] = [delivery]);
39+
}
40+
41+
// For each GUID value, if no deliveries for that GUID have been successfully delivered within the time frame, get the delivery ID of one of the deliveries with that GUID.
42+
//
43+
// This will prevent duplicate redeliveries if a delivery has failed multiple times.
44+
// This will also prevent redelivery of failed deliveries that have already been successfully redelivered.
45+
let failedDeliveryIDs = [];
46+
for (const guid in deliveriesByGuid) {
47+
const deliveries = deliveriesByGuid[guid];
48+
const anySucceeded = deliveries.some(
49+
(delivery) => delivery.status === "OK"
50+
);
51+
if (!anySucceeded) {
52+
failedDeliveryIDs.push(deliveries[0].id);
53+
}
54+
}
55+
56+
// Redeliver any failed deliveries.
57+
for (const deliveryId of failedDeliveryIDs) {
58+
await redeliverWebhook({
59+
deliveryId,
60+
organizationName: ORGANIZATION_NAME,
61+
hookId: HOOK_ID,
62+
octokit,
63+
});
64+
}
65+
66+
// Update the configuration variable (or create the variable if it doesn't already exist) to store the time that this script started.
67+
// This value will be used next time this script runs.
68+
await updateVariable({
69+
variableName: LAST_REDELIVERY_VARIABLE_NAME,
70+
value: newWebhookRedeliveryTime,
71+
variableExists: Boolean(lastStoredRedeliveryTime),
72+
repoOwner: WORKFLOW_REPO_OWNER,
73+
repoName: WORKFLOW_REPO_NAME,
74+
octokit,
75+
});
76+
77+
// Log the number of redeliveries.
78+
console.log(
79+
`Redelivered ${
80+
failedDeliveryIDs.length
81+
} failed webhook deliveries out of ${
82+
deliveries.length
83+
} total deliveries between ${new Date(lastWebhookRedeliveryTime)} and ${new Date(newWebhookRedeliveryTime)}.`
84+
);
85+
} catch (error) {
86+
// If there was an error, log the error so that it appears in the workflow run log, then throw the error so that the workflow run registers as a failure.
87+
if (error.response) {
88+
console.error(
89+
`Failed to check and redeliver webhooks: ${error.response.data.message}`
90+
);
91+
}
92+
console.error(error);
93+
throw(error);
94+
}
95+
}
96+
97+
// This function will fetch all of the webhook deliveries that were delivered since `lastWebhookRedeliveryTime`.
98+
// It uses the `octokit.paginate.iterator()` method to iterate through paginated results. For more information, see [AUTOTITLE](/rest/guides/scripting-with-the-rest-api-and-javascript#making-paginated-requests).
99+
//
100+
// If a page of results includes deliveries that occurred before `lastWebhookRedeliveryTime`,
101+
// it will store only the deliveries that occurred after `lastWebhookRedeliveryTime` and then stop.
102+
// Otherwise, it will store all of the deliveries from the page and request the next page.
103+
async function fetchWebhookDeliveriesSince({
104+
lastWebhookRedeliveryTime,
105+
organizationName,
106+
hookId,
107+
octokit,
108+
}) {
109+
const iterator = octokit.paginate.iterator(
110+
"GET /orgs/{org}/hooks/{hook_id}/deliveries",
111+
{
112+
org: organizationName,
113+
hook_id: hookId,
114+
per_page: 100,
115+
headers: {
116+
"x-github-api-version": "2026-03-10",
117+
},
118+
}
119+
);
120+
121+
const deliveries = [];
122+
123+
for await (const { data } of iterator) {
124+
const oldestDeliveryTimestamp = new Date(
125+
data[data.length - 1].delivered_at
126+
).getTime();
127+
128+
if (oldestDeliveryTimestamp < lastWebhookRedeliveryTime) {
129+
for (const delivery of data) {
130+
if (
131+
new Date(delivery.delivered_at).getTime() > lastWebhookRedeliveryTime
132+
) {
133+
deliveries.push(delivery);
134+
} else {
135+
break;
136+
}
137+
}
138+
break;
139+
} else {
140+
deliveries.push(...data);
141+
}
142+
}
143+
144+
return deliveries;
145+
}
146+
147+
// This function will redeliver a failed webhook delivery.
148+
async function redeliverWebhook({
149+
deliveryId,
150+
organizationName,
151+
hookId,
152+
octokit,
153+
}) {
154+
await octokit.request(
155+
"POST /orgs/{org}/hooks/{hook_id}/deliveries/{delivery_id}/attempts",
156+
{
157+
org: organizationName,
158+
hook_id: hookId,
159+
delivery_id: deliveryId,
160+
}
161+
);
162+
}
163+
164+
// This function gets the value of a configuration variable.
165+
// If the variable does not exist, the endpoint returns a 404 response and this function returns `undefined`.
166+
async function getVariable({ variableName, repoOwner, repoName, octokit }) {
167+
try {
168+
const {
169+
data: { value },
170+
} = await octokit.request(
171+
"GET /repos/{owner}/{repo}/actions/variables/{name}",
172+
{
173+
owner: repoOwner,
174+
repo: repoName,
175+
name: variableName,
176+
}
177+
);
178+
return value;
179+
} catch (error) {
180+
if (error.status === 404) {
181+
return undefined;
182+
} else {
183+
throw error;
184+
}
185+
}
186+
}
187+
188+
// This function will update a configuration variable (or create the variable if it doesn't already exist). For more information, see [AUTOTITLE](/actions/learn-github-actions/variables#defining-configuration-variables-for-multiple-workflows).
189+
async function updateVariable({
190+
variableName,
191+
value,
192+
variableExists,
193+
repoOwner,
194+
repoName,
195+
octokit,
196+
}) {
197+
if (variableExists) {
198+
await octokit.request(
199+
"PATCH /repos/{owner}/{repo}/actions/variables/{name}",
200+
{
201+
owner: repoOwner,
202+
repo: repoName,
203+
name: variableName,
204+
value: value,
205+
}
206+
);
207+
} else {
208+
await octokit.request("POST /repos/{owner}/{repo}/actions/variables", {
209+
owner: repoOwner,
210+
repo: repoName,
211+
name: variableName,
212+
value: value,
213+
});
214+
}
215+
}
216+
217+
// This will execute the `checkAndRedeliverWebhooks` function.
218+
(async () => {
219+
// Get the values of environment variables that were set by the GitHub Actions workflow.
220+
const TOKEN = process.env.TOKEN;
221+
const ORGANIZATION_NAME = process.env.ORGANIZATION_NAME;
222+
const HOOK_ID = process.env.HOOK_ID;
223+
const LAST_REDELIVERY_VARIABLE_NAME = process.env.LAST_REDELIVERY_VARIABLE_NAME;
224+
225+
const WORKFLOW_REPO_NAME = process.env.WORKFLOW_REPO_NAME;
226+
const WORKFLOW_REPO_OWNER = process.env.WORKFLOW_REPO_OWNER;
227+
228+
await checkAndRedeliverWebhooks(TOKEN, ORGANIZATION_NAME, HOOK_ID, LAST_REDELIVERY_VARIABLE_NAME, WORKFLOW_REPO_NAME, WORKFLOW_REPO_OWNER);
229+
})();
230+

.github/workflows/staging_com-keyman-staging-status.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
- name: Set up Node.js version
2020
uses: actions/setup-node@v1
2121
with:
22-
node-version: '22.x'
22+
node-version: '24.x'
2323

2424
- name: npm ci + build - public
2525
run: |

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
- name: Set up Node.js version
2626
uses: actions/setup-node@v1
2727
with:
28-
node-version: '22.x'
28+
node-version: '24.x'
2929

3030
- name: npm install, build, and test
3131
run: |

0 commit comments

Comments
 (0)