Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
6ca6d60
chore(deps): bump qs and body-parser in /public
dependabot[bot] May 22, 2026
ff7e5c1
chore(deps): bump tmp from 0.2.5 to 0.2.7 in /public
dependabot[bot] May 28, 2026
54c26d4
chore: update multi-labeler to 5.0.0
mcdurdin Jun 16, 2026
0020246
Merge pull request #664 from keymanapp/chore/1607-multi-labeler-5.0.0
mcdurdin Jun 16, 2026
e7c29ab
chore(deps-dev): bump tar from 7.5.11 to 7.5.16 in /public
dependabot[bot] Jun 16, 2026
29d037a
feat: redeliver failed GitHub webhook events
mcdurdin Jun 17, 2026
c0144a4
Merge pull request #669 from keymanapp/feat/redeliver-failed-webhook-…
mcdurdin Jun 17, 2026
d7245c8
fix: path for redeliver
mcdurdin Jun 17, 2026
387e157
Merge pull request #671 from keymanapp/fix/redeliver-path
mcdurdin Jun 17, 2026
ce94a6a
fix: redeliver as a module
mcdurdin Jun 17, 2026
d7d696e
Merge pull request #672 from keymanapp/fix/redeliver-module
mcdurdin Jun 17, 2026
2b154b6
fix: redeliver pat
mcdurdin Jun 17, 2026
459d114
Merge pull request #673 from keymanapp/fix/redeliver-pat
mcdurdin Jun 17, 2026
89190f3
fix: use different cron format
mcdurdin Jun 17, 2026
6957add
Merge pull request #674 from keymanapp/fix/redeliver-cron
mcdurdin Jun 17, 2026
a88ca91
fix: redeliver -- don't use cron
mcdurdin Jun 17, 2026
f884303
Merge pull request #675 from keymanapp/fix/redeliver-dont-use-cron
mcdurdin Jun 17, 2026
6199fec
fix: tweak logs for redelivery reports
mcdurdin Jun 18, 2026
c1b39a7
Merge pull request #676 from keymanapp/fix/redeliver-log
mcdurdin Jun 18, 2026
820b481
fix: report correct date in redeliver log
mcdurdin Jun 18, 2026
a4e6feb
Merge pull request #677 from keymanapp/fix/redeliver-log-date
mcdurdin Jun 18, 2026
a3bc59d
fix: handle closed PRs better
mcdurdin Jun 18, 2026
7f94185
Merge pull request #678 from keymanapp/fix/handle-closed-prs
mcdurdin Jun 18, 2026
1bf6511
fix: get PR state in queries
mcdurdin Jun 18, 2026
cedee29
Merge pull request #679 from keymanapp/fix/get-pr-state
mcdurdin Jun 18, 2026
d256322
Merge pull request #659 from keymanapp/dependabot/npm_and_yarn/public…
mcdurdin Jun 18, 2026
4c1645a
chore(deps): bump qs and express in /server
dependabot[bot] Jun 18, 2026
9430a01
feat: add .pkg support for mac links
mcdurdin Jun 18, 2026
80623ca
Merge pull request #680 from keymanapp/feat/add-mac-pkg
sgschantz Jun 18, 2026
62cdbea
Merge pull request #660 from keymanapp/dependabot/npm_and_yarn/public…
mcdurdin Jun 19, 2026
fadef92
chore: remove typescript-eslint temporarily
mcdurdin Jun 19, 2026
cdf2705
chore: upgrade Angular to v22.0
mcdurdin Jun 22, 2026
7b13484
chore: node v24
mcdurdin Jun 22, 2026
bdd90e8
Merge pull request #682 from keymanapp/chore/ng-upgrade
mcdurdin Jun 22, 2026
769c346
chore(deps): bump ws from 7.5.10 to 7.5.11 in /server
dependabot[bot] Jun 22, 2026
eb3d97e
Merge pull request #667 from keymanapp/dependabot/npm_and_yarn/public…
mcdurdin Jun 22, 2026
1b94032
Merge pull request #666 from keymanapp/dependabot/npm_and_yarn/server…
mcdurdin Jun 22, 2026
4b6f944
Merge pull request #658 from keymanapp/dependabot/npm_and_yarn/server…
mcdurdin Jun 22, 2026
8d1d53d
chore(deps): bump uuid and probot in /server
dependabot[bot] Apr 23, 2026
d191c55
maint: upgrade probot to 14.x
mcdurdin Apr 29, 2026
1c0b93e
chore: logging
mcdurdin Jun 22, 2026
410757c
chore: cleanup
mcdurdin Jun 22, 2026
259301f
Merge pull request #637 from keymanapp/maint/upgrade-probot
mcdurdin Jun 22, 2026
504b8ad
chore(deps): bump js-yaml, @typescript-eslint/eslint-plugin, @typescr…
dependabot[bot] Jun 22, 2026
22cdb82
chore: increase express JSON parse from 100kb to 10mb
mcdurdin Jun 22, 2026
98812c7
Merge pull request #684 from keymanapp/chore/json-size
mcdurdin Jun 22, 2026
0f57910
chore: probot use must be before express.json
mcdurdin Jun 22, 2026
e829adc
Merge pull request #685 from keymanapp/chore/probot
mcdurdin Jun 22, 2026
b60eb08
chore: probot again -- avoid 404
mcdurdin Jun 22, 2026
5eafb64
Merge pull request #686 from keymanapp/chore/probot-again
mcdurdin Jun 22, 2026
1bac75d
Merge pull request #683 from keymanapp/dependabot/npm_and_yarn/server…
mcdurdin Jun 22, 2026
127960c
chore(deps): bump @opentelemetry/core and @sentry/node in /server
dependabot[bot] Jun 22, 2026
79ef034
Merge pull request #665 from keymanapp/dependabot/npm_and_yarn/server…
mcdurdin Jun 22, 2026
51e9524
chore: Merge branch 'staging' into auto/A19S32-merge-master-into-staging
mcdurdin Jun 24, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
steps:
- name: Update labels based on PR title
id: labeler
uses: fuxingloh/multi-labeler@f5bd7323b53b0833c1e4ed8d7b797ae995ef75b4 # v2.0.1
uses: fuxingloh/multi-labeler@bcd50af464202999e57f556b4aefcf05a34abf85 # v5.0.0
with:
github-token: ${{secrets.GITHUB_TOKEN}}
config-path: .github/multi-labeler.yml
2 changes: 1 addition & 1 deletion .github/workflows/master_com-keyman-status.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Set up Node.js version
uses: actions/setup-node@v1
with:
node-version: '22.x'
node-version: '24.x'

- name: npm ci + build - public
run: |
Expand Down
53 changes: 53 additions & 0 deletions .github/workflows/redeliver-failed-deliveries.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#
name: Redeliver failed webhook deliveries

# This workflow runs every 10 minutes starting at 3 minutes past the hour or when manually triggered.
on:
# It appears that cron scheduling is not currently reliable on GitHub, e.g.
# https://upptime.js.org/blog/2021/01/22/github-actions-schedule-not-working/
# schedule:
# - cron: '3,13,23,33,43,53 * * * *'
workflow_dispatch:

# This workflow will use the built in `GITHUB_TOKEN` to check out the repository contents. This grants `GITHUB_TOKEN` permission to do that.
permissions:
contents: read

#
jobs:
redeliver-failed-deliveries:
name: Redeliver failed deliveries
runs-on: ubuntu-latest
steps:
# 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.
- name: Check out repo content
uses: actions/checkout@v6

# This step sets up Node.js. The script that this workflow will run uses Node.js.
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24.x'

# This step installs the octokit library. The script that this workflow will run uses the octokit library.
- name: Install dependencies
run: npm install octokit

# This step sets some environment variables, then runs a script to find and redeliver failed webhook deliveries.
# - Replace `YOUR_SECRET_NAME` with the name of the secret where you stored your personal access token.
# - Replace `YOUR_ORGANIZATION_NAME` with the name of the organization where the webhook was created.
# - Replace `YOUR_HOOK_ID` with the ID of the webhook.
# - 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).

- name: Run script
env:
TOKEN: ${{ secrets.KEYMAN_SERVER_STATUS_KEYMANAPP_WEBHOOK }}
ORGANIZATION_NAME: 'keymanapp'
HOOK_ID: '288390178'
LAST_REDELIVERY_VARIABLE_NAME: 'keyman_github_webhook_last_redelivery'

WORKFLOW_REPO_NAME: ${{ github.event.repository.name }}
WORKFLOW_REPO_OWNER: ${{ github.repository_owner }}
run: |
node .github/workflows/scripts/redeliver-failed-deliveries.mjs > $GITHUB_STEP_SUMMARY
cat $GITHUB_STEP_SUMMARY
230 changes: 230 additions & 0 deletions .github/workflows/scripts/redeliver-failed-deliveries.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
// 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).
import { Octokit } from 'octokit';

//
async function checkAndRedeliverWebhooks(TOKEN, ORGANIZATION_NAME, HOOK_ID, LAST_REDELIVERY_VARIABLE_NAME, WORKFLOW_REPO_NAME, WORKFLOW_REPO_OWNER) {
// Create an instance of `Octokit` using the token values that were set in the GitHub Actions workflow.
const octokit = new Octokit({
auth: TOKEN,
});

try {
// 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.
const lastStoredRedeliveryTime = await getVariable({
variableName: LAST_REDELIVERY_VARIABLE_NAME,
repoOwner: WORKFLOW_REPO_OWNER,
repoName: WORKFLOW_REPO_NAME,
octokit,
});

const lastWebhookRedeliveryTime = lastStoredRedeliveryTime || (Date.now() - (24 * 60 * 60 * 1000)).toString();

// Record the time that this script started redelivering webhooks, less 1 second so we don't miss events that failed right now
const newWebhookRedeliveryTime = (Date.now() - 1000).toString();

// Get the webhook deliveries that were delivered after `lastWebhookRedeliveryTime`.
const deliveries = await fetchWebhookDeliveriesSince({
lastWebhookRedeliveryTime,
organizationName: ORGANIZATION_NAME,
hookId: HOOK_ID,
octokit,
});

// Consolidate deliveries that have the same globally unique identifier (GUID). The GUID is constant across redeliveries of the same delivery.
let deliveriesByGuid = {};
for (const delivery of deliveries) {
deliveriesByGuid[delivery.guid]
? deliveriesByGuid[delivery.guid].push(delivery)
: (deliveriesByGuid[delivery.guid] = [delivery]);
}

// 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.
//
// This will prevent duplicate redeliveries if a delivery has failed multiple times.
// This will also prevent redelivery of failed deliveries that have already been successfully redelivered.
let failedDeliveryIDs = [];
for (const guid in deliveriesByGuid) {
const deliveries = deliveriesByGuid[guid];
const anySucceeded = deliveries.some(
(delivery) => delivery.status === "OK"
);
if (!anySucceeded) {
failedDeliveryIDs.push(deliveries[0].id);
}
}

// Redeliver any failed deliveries.
for (const deliveryId of failedDeliveryIDs) {
await redeliverWebhook({
deliveryId,
organizationName: ORGANIZATION_NAME,
hookId: HOOK_ID,
octokit,
});
}

// Update the configuration variable (or create the variable if it doesn't already exist) to store the time that this script started.
// This value will be used next time this script runs.
await updateVariable({
variableName: LAST_REDELIVERY_VARIABLE_NAME,
value: newWebhookRedeliveryTime,
variableExists: Boolean(lastStoredRedeliveryTime),
repoOwner: WORKFLOW_REPO_OWNER,
repoName: WORKFLOW_REPO_NAME,
octokit,
});

// Log the number of redeliveries.
console.log(
`Redelivered ${
failedDeliveryIDs.length
} failed webhook deliveries out of ${
deliveries.length
} total deliveries between ${new Date(lastWebhookRedeliveryTime)} and ${new Date(newWebhookRedeliveryTime)}.`
);
} catch (error) {
// 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.
if (error.response) {
console.error(
`Failed to check and redeliver webhooks: ${error.response.data.message}`
);
}
console.error(error);
throw(error);
}
}

// This function will fetch all of the webhook deliveries that were delivered since `lastWebhookRedeliveryTime`.
// 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).
//
// If a page of results includes deliveries that occurred before `lastWebhookRedeliveryTime`,
// it will store only the deliveries that occurred after `lastWebhookRedeliveryTime` and then stop.
// Otherwise, it will store all of the deliveries from the page and request the next page.
async function fetchWebhookDeliveriesSince({
lastWebhookRedeliveryTime,
organizationName,
hookId,
octokit,
}) {
const iterator = octokit.paginate.iterator(
"GET /orgs/{org}/hooks/{hook_id}/deliveries",
{
org: organizationName,
hook_id: hookId,
per_page: 100,
headers: {
"x-github-api-version": "2026-03-10",
},
}
);

const deliveries = [];

for await (const { data } of iterator) {
const oldestDeliveryTimestamp = new Date(
data[data.length - 1].delivered_at
).getTime();

if (oldestDeliveryTimestamp < lastWebhookRedeliveryTime) {
for (const delivery of data) {
if (
new Date(delivery.delivered_at).getTime() > lastWebhookRedeliveryTime
) {
deliveries.push(delivery);
} else {
break;
}
}
break;
} else {
deliveries.push(...data);
}
}

return deliveries;
}

// This function will redeliver a failed webhook delivery.
async function redeliverWebhook({
deliveryId,
organizationName,
hookId,
octokit,
}) {
await octokit.request(
"POST /orgs/{org}/hooks/{hook_id}/deliveries/{delivery_id}/attempts",
{
org: organizationName,
hook_id: hookId,
delivery_id: deliveryId,
}
);
}

// This function gets the value of a configuration variable.
// If the variable does not exist, the endpoint returns a 404 response and this function returns `undefined`.
async function getVariable({ variableName, repoOwner, repoName, octokit }) {
try {
const {
data: { value },
} = await octokit.request(
"GET /repos/{owner}/{repo}/actions/variables/{name}",
{
owner: repoOwner,
repo: repoName,
name: variableName,
}
);
return value;
} catch (error) {
if (error.status === 404) {
return undefined;
} else {
throw error;
}
}
}

// 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).
async function updateVariable({
variableName,
value,
variableExists,
repoOwner,
repoName,
octokit,
}) {
if (variableExists) {
await octokit.request(
"PATCH /repos/{owner}/{repo}/actions/variables/{name}",
{
owner: repoOwner,
repo: repoName,
name: variableName,
value: value,
}
);
} else {
await octokit.request("POST /repos/{owner}/{repo}/actions/variables", {
owner: repoOwner,
repo: repoName,
name: variableName,
value: value,
});
}
}

// This will execute the `checkAndRedeliverWebhooks` function.
(async () => {
// Get the values of environment variables that were set by the GitHub Actions workflow.
const TOKEN = process.env.TOKEN;
const ORGANIZATION_NAME = process.env.ORGANIZATION_NAME;
const HOOK_ID = process.env.HOOK_ID;
const LAST_REDELIVERY_VARIABLE_NAME = process.env.LAST_REDELIVERY_VARIABLE_NAME;

const WORKFLOW_REPO_NAME = process.env.WORKFLOW_REPO_NAME;
const WORKFLOW_REPO_OWNER = process.env.WORKFLOW_REPO_OWNER;

await checkAndRedeliverWebhooks(TOKEN, ORGANIZATION_NAME, HOOK_ID, LAST_REDELIVERY_VARIABLE_NAME, WORKFLOW_REPO_NAME, WORKFLOW_REPO_OWNER);
})();

2 changes: 1 addition & 1 deletion .github/workflows/staging_com-keyman-staging-status.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Set up Node.js version
uses: actions/setup-node@v1
with:
node-version: '22.x'
node-version: '24.x'

- name: npm ci + build - public
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Set up Node.js version
uses: actions/setup-node@v1
with:
node-version: '22.x'
node-version: '24.x'

- name: npm install, build, and test
run: |
Expand Down
Loading
Loading