From 21a2ab306bf290cb219a2255e8811cb6e318601a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 3 Sep 2026 11:55:58 +0000
Subject: [PATCH 1/6] chore(deps): bump @humanfs/node in /_scripts/contributors
Bumps [@humanfs/node](https://github.com/humanwhocodes/humanfs/tree/HEAD/packages/node) from 0.16.7 to 0.16.8.
- [Release notes](https://github.com/humanwhocodes/humanfs/releases)
- [Changelog](https://github.com/humanwhocodes/humanfs/blob/main/packages/node/CHANGELOG.md)
- [Commits](https://github.com/humanwhocodes/humanfs/commits/node-v0.16.8/packages/node)
---
updated-dependencies:
- dependency-name: "@humanfs/node"
dependency-version: 0.16.8
dependency-type: indirect
...
Signed-off-by: dependabot[bot]
---
_scripts/contributors/package-lock.json | 27 ++++++++++++++++++-------
1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/_scripts/contributors/package-lock.json b/_scripts/contributors/package-lock.json
index 0dc78de4..d64cc2f5 100644
--- a/_scripts/contributors/package-lock.json
+++ b/_scripts/contributors/package-lock.json
@@ -146,27 +146,40 @@
}
},
"node_modules/@humanfs/core": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
- "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
+ "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
"license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/types": "^0.15.0"
+ },
"engines": {
"node": ">=18.18.0"
}
},
"node_modules/@humanfs/node": {
- "version": "0.16.7",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
- "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
+ "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
"license": "Apache-2.0",
"dependencies": {
- "@humanfs/core": "^0.19.1",
+ "@humanfs/core": "^0.19.2",
+ "@humanfs/types": "^0.15.0",
"@humanwhocodes/retry": "^0.4.0"
},
"engines": {
"node": ">=18.18.0"
}
},
+ "node_modules/@humanfs/types": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
+ "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
From 214bf4708332f98203130c08af09acf0168374ed Mon Sep 17 00:00:00 2001
From: Marc Durdin
Date: Thu, 17 Sep 2026 06:16:15 +0200
Subject: [PATCH 2/6] fix: prevent uncontrolled redirect on /go/app/download
The `/go/app/download/` endpoint has a parameter `url` which
is intended for internal use on this site, but the parameter was not
validated, so it could be abused for random redirects to any site. Now
requires the url to point to downloads.keyman.com; a more comprehensive
fix could remove the hostname altogether from the redirector and just
have a path (but this mitigates for now).
Test-bot: skip
---
go/app/download.php | 6 ++++++
go/package/download.php | 3 +++
2 files changed, 9 insertions(+)
diff --git a/go/app/download.php b/go/app/download.php
index 91554bcb..5704ea9e 100644
--- a/go/app/download.php
+++ b/go/app/download.php
@@ -30,6 +30,12 @@ class AppDownloadPage {
public static function redirect_to_file($url, $product, $version, $tier) {
if(empty($url)) {
JsonApiFailure::InvalidParameters("url");
+ return;
+ }
+
+ if(!preg_match("/^https?:\/\/downloads\\.keyman(-staging)?\\.com(\\.localhost)?\//", $url)) {
+ JsonApiFailure::Failure(400, JsonApiFailure::ERROR_InvalidParameters, "url parameter must start with downloads.keyman.com");
+ return;
}
if(empty($product)) {
diff --git a/go/package/download.php b/go/package/download.php
index c76f09d1..eca631cb 100644
--- a/go/package/download.php
+++ b/go/package/download.php
@@ -35,10 +35,12 @@ public static function redirect_to_file($type, $id, $version, $platform, $tier,
if($type !== 'keyboard' && $type !== 'model') {
JsonApiFailure::InvalidParameters("type");
+ return;
}
if(empty($id)) {
JsonApiFailure::InvalidParameters("id, version");
+ return;
}
if(empty($version)) {
@@ -50,6 +52,7 @@ public static function redirect_to_file($type, $id, $version, $platform, $tier,
if(empty($json)) {
JsonApiFailure::Failure(404, JsonApiFailure::ERROR_NotFound, "$type package with id $id was not found");
+ return;
}
$version = $json->version;
From 65f041af6e484c9c3c5c6df8d32a0bcdaec89d41 Mon Sep 17 00:00:00 2001
From: Marc Durdin
Date: Tue, 22 Sep 2026 10:37:53 +0200
Subject: [PATCH 3/6] refactor: support mac .pkg and cleanup download path
references
Consolidate references to downloads across various pages and DRY out
repeated content. Add support for .pkg downloads when they become
available (will be automatic based on content from download server).
Test-bot: skip
---
_content/10/index.php | 12 +-
_content/11/index.php | 10 +-
_content/12/index.php | 2 +-
_content/13/index.php | 3 +-
_content/14/index.php | 13 +-
_content/15/index.php | 13 +-
_content/developer/download.php | 4 +-
_content/downloads/_downloads.php | 16 +-
_content/downloads/all-versions/index.php | 1 -
_content/downloads/index.php | 2 +-
_content/downloads/pre-release/index.php | 17 +-
.../downloads/releases/_version_downloads.php | 17 +-
_content/linux/download.php | 2 -
_content/mac/download.php | 5 +-
_content/windows/download.php | 5 +-
_content/windows/keyboards.php | 1 -
_includes/2020/DownloadUI.php | 186 ++++++++++++++++++
_includes/2020/KeymanDownloadVersions.php | 41 ----
_includes/includes/ui/downloads.php | 116 -----------
19 files changed, 250 insertions(+), 216 deletions(-)
create mode 100644 _includes/2020/DownloadUI.php
delete mode 100644 _includes/2020/KeymanDownloadVersions.php
delete mode 100644 _includes/includes/ui/downloads.php
diff --git a/_content/10/index.php b/_content/10/index.php
index 5d4a2eca..ef5321a8 100644
--- a/_content/10/index.php
+++ b/_content/10/index.php
@@ -1,12 +1,12 @@
What's New in Keyman Desktop 10 for Windows?
@@ -58,7 +58,7 @@
supports Unicode 11.0 and BCP 47 language identifiers. Desktop 10 also includes additional user interface for Turkish.
What's New in Keyman 10 for macOS?
@@ -68,7 +68,7 @@
and easily install keyboard packages by double-clicking the kmp file.
Keyman for Android is also available on the Play Store.
= $playstoreTable ?>
@@ -92,7 +92,7 @@
Swift 4.0 app.
What's New in KeymanWeb 10?
@@ -104,7 +104,7 @@
Developer Software
Keyman Developer 10 allows you to create
diff --git a/_content/11/index.php b/_content/11/index.php
index 1482d7ac..7c88ab19 100644
--- a/_content/11/index.php
+++ b/_content/11/index.php
@@ -1,11 +1,11 @@
What's New in Keyman 11 for macOS?
@@ -92,7 +92,7 @@
Keyman for Android is available on the Play Store.
= $playstoreTable ?>
@@ -122,7 +122,7 @@
@@ -131,7 +131,7 @@
Developer Software
What's new in Keyman Developer 11?
diff --git a/_content/12/index.php b/_content/12/index.php
index 91db9d88..a784b67d 100644
--- a/_content/12/index.php
+++ b/_content/12/index.php
@@ -1,8 +1,8 @@
User Software
What's New in Keyman 14 for Windows?
@@ -98,7 +99,7 @@
What's New in Keyman 14 for macOS?
@@ -144,7 +145,7 @@
= $playstoreTable ?>
@@ -188,7 +189,7 @@
What's New in KeymanWeb 14?
@@ -208,7 +209,7 @@
Developer Software
What's new in Keyman Developer 14?
diff --git a/_content/15/index.php b/_content/15/index.php
index 79fc5237..89bc2e9c 100644
--- a/_content/15/index.php
+++ b/_content/15/index.php
@@ -1,11 +1,12 @@
User Software
What's New in Keyman 15 for Windows?
@@ -111,7 +112,7 @@
What's New in Keyman 15 for macOS?
@@ -146,7 +147,7 @@
= $playstoreTable ?>
@@ -184,7 +185,7 @@
What's New in KeymanWeb 15?
@@ -201,7 +202,7 @@
Developer Software
What's new in Keyman Developer 15?
diff --git a/_content/developer/download.php b/_content/developer/download.php
index 55853861..2b391220 100644
--- a/_content/developer/download.php
+++ b/_content/developer/download.php
@@ -1,8 +1,8 @@
Keyman Developer Command Line Tools
diff --git a/_content/downloads/_downloads.php b/_content/downloads/_downloads.php
index 2bd26a40..4a7d7b89 100644
--- a/_content/downloads/_downloads.php
+++ b/_content/downloads/_downloads.php
@@ -1,7 +1,9 @@
= _m_Downloads('available_on_play_store') ?>
@@ -20,10 +22,10 @@
= _m_Downloads('products_for_software_developers') ?>
diff --git a/_content/downloads/all-versions/index.php b/_content/downloads/all-versions/index.php
index d286c7b4..1d3063ad 100644
--- a/_content/downloads/all-versions/index.php
+++ b/_content/downloads/all-versions/index.php
@@ -1,6 +1,5 @@
Keyman version history (all products)
Keyman for Linux
@@ -64,8 +65,8 @@
sudo apt install keyman onboard-keyman
You can also sign up to access pre-release versions through Google Play.
@@ -74,10 +75,10 @@
Please register for the pre-release versions through the links below for Keyman beta or
alpha pre-releases on your iOS device. This will grant access to the respective app version
through Apple's TestFlight app, which facilitates direct installation on iOS devices.
-= iosTestFlightTable(); ?>
+= DownloadUI::iosTestFlightTable(); ?>
diff --git a/_content/downloads/releases/_version_downloads.php b/_content/downloads/releases/_version_downloads.php
index 75c43bae..a32a5508 100644
--- a/_content/downloads/releases/_version_downloads.php
+++ b/_content/downloads/releases/_version_downloads.php
@@ -1,9 +1,10 @@
Keyman for Android is also available on the Play Store.
@@ -144,10 +145,10 @@ function array_key_first(array $arr) {
Products for Software Developers
diff --git a/_content/linux/download.php b/_content/linux/download.php
index fb5facfa..d66ccfc2 100644
--- a/_content/linux/download.php
+++ b/_content/linux/download.php
@@ -1,7 +1,5 @@
Install directly from keyman.com
Install via Homebrew
diff --git a/_content/windows/download.php b/_content/windows/download.php
index 8a408255..f77e301d 100644
--- a/_content/windows/download.php
+++ b/_content/windows/download.php
@@ -1,6 +1,7 @@
diff --git a/_content/windows/keyboards.php b/_content/windows/keyboards.php
index ccf5f082..eb5c8e13 100644
--- a/_content/windows/keyboards.php
+++ b/_content/windows/keyboards.php
@@ -1,6 +1,5 @@
= 1073741824) {
+ $bytes = number_format($bytes / 1073741824, 2) . ' GB';
+ } elseif ($bytes >= 1048576) {
+ $bytes = number_format($bytes / 1048576, 2) . ' MB';
+ } elseif ($bytes >= 1024) {
+ $bytes = number_format($bytes / 1024, 2) . ' KB';
+ } elseif ($bytes > 1) {
+ $bytes = $bytes . ' bytes';
+ } elseif ($bytes == 1) {
+ $bytes = $bytes . ' byte';
+ } else {
+ $bytes = '0 bytes';
+ }
+
+ return $bytes;
+ }
+
+ private static function filenamePatterns($target, $tier) {
+ global $versions;
+ switch($target) {
+ case 'android': return 'keyman-$version.apk';
+ case 'android-engine': return 'keyman-engine-android-$version.zip';
+ case 'developer': return ['keymandeveloper-$version.exe', 'kmcomp-$version.exe'];
+ case 'ios': return 'keyman-ios-$version.ipa'; // not currently used
+ case 'ios-engine': return 'keyman-engine-ios-$version.zip';
+ case 'linux': return ''; // not currently used
+ case 'mac': return ['keyman-$version.pkg','keyman-$version.dmg'];
+ case 'web': return 'keymanweb-$version.zip';
+ case 'windows': return ['keyman-$version.exe', 'keymandesktop-$version.exe'];
+ default: throw new Exception("Invalid target $target");
+ }
+ }
+
+ private static function productName($target) {
+ switch($target) {
+ case 'android': return 'product_android';
+ case 'android-engine': return 'product_engine_android';
+ case 'developer': return 'product_developer';
+ case 'ios': return 'product_ios'; // not currently used
+ case 'ios-engine': return 'product_engine_ios';
+ case 'linux': return 'product_linux'; // not currently used
+ case 'mac': return 'product_macos'; // note mismatched platform name
+ case 'web': return 'product_keymanweb'; // note mismatched platform name
+ case 'windows': return 'product_windows';
+ default: throw new Exception("Invalid target $target");
+ }
+ }
+
+ private static function title($target) {
+ switch($target) {
+ case 'android': return 'Keyman for Android';
+ case 'android-engine': return 'Keyman Engine for Android';
+ case 'developer': return 'Keyman Developer';
+ case 'ios': return 'Keyman for iPhone and iPad'; // not currently used
+ case 'ios-engine': return 'Keyman Engine for iPhone and iPad';
+ case 'linux': return 'Keyman for Linux'; // not currently used
+ case 'mac': return 'Keyman for mac';
+ case 'web': return 'KeymanWeb';
+ case 'windows': return 'Keyman for Windows';
+ default: throw new Exception("Invalid target $target");
+ }
+ }
+
+
+ public static function downloadSection($platform, $tiers = '', $target = '') {
+
+ if($target == '') $target = $platform;
+
+ echo sprintf("%s
\n\n",
+ $target, _m_Downloads(DownloadUI::productName($target)));
+ $tiers = explode(' ',$tiers);
+ foreach($tiers as $tier) {
+ $filepatterns = DownloadUI::filenamePatterns($target, $tier);
+ echo DownloadUI::downloadLinks($platform, $tier, $filepatterns);
+ }
+ }
+
+ private static function downloadLinks($platform, $tier, $filepatterns) {
+ global $versions;
+ $product = DownloadUI::productName($platform);
+ echo sprintf("%s
\n\n", ucFirst(_m_Downloads($tier)));
+ if(!empty($versions->$platform->$tier)) {
+ if(!is_array($filepatterns)) $filepatterns = array($filepatterns);
+ foreach($filepatterns as $filepattern) {
+ $file = str_replace('$version', $versions->$platform->$tier->version, $filepattern);
+ $file = str_replace('$tier', $tier, $file);
+
+ if(!empty($versions->$platform->$tier->files->$file)) {
+ $fileData = $versions->$platform->$tier->files->$file;
+ $fileSize = DownloadUI::formatSizeUnits($fileData->size);
+ echo sprintf("- %s %s %s
\n",
+ KeymanHosts::Instance()->downloads_keyman_com ,
+ $platform, $tier,
+ $versions->$platform->$tier->version,
+ $file, $file, $tier,
+ _m_Downloads('released_date_size', $fileData->date, $fileSize));
+ }
+ }
+ }
+ echo sprintf("- %s
\n
\n",
+ KeymanHosts::Instance()->downloads_keyman_com, $platform, $tier,
+ _m_Downloads('all_product_releases', _m_Downloads($product), _m_Downloads($tier)));
+ }
+
+ public static function downloadLargeCTA($platform, $tier) {
+ global $versions;
+
+ if(empty($versions)) return false;
+
+ $found = false;
+
+ $filepatterns = DownloadUI::filenamePatterns($platform, $tier);
+ $title = DownloadUI::title($platform);
+ // CTA only supports the first download (which will typically be the right one?)
+ if(!is_array($filepatterns)) $filepatterns = [$filepatterns];
+ foreach($filepatterns as $filepattern) {
+ $file = str_replace('$version', $versions->$platform->$tier->version, $filepattern);
+ $file = str_replace('$tier', $tier, $file);
+
+ if(empty($versions->$platform->$tier->files->$file)) {
+ continue;
+ }
+
+ $found = true;
+
+ $fileData = $versions->$platform->$tier->files->$file;
+ $fileSize = DownloadUI::formatSizeUnits($fileData->size);
+ $host = KeymanHosts::Instance()->downloads_keyman_com;
+ $downloadSiteUrl = "$host/$platform/$tier/{$versions->$platform->$tier->version}/$file";
+ $downloadUrl = htmlentities("/go/app/download/$platform/{$versions->$platform->$tier->version}/$tier?url=".
+ rawurlencode($downloadSiteUrl));
+
+ echo <<
+
+
$title {$versions->$platform->$tier->version}
+
Released: {$fileData->date}
+
Size: $fileSize
+
+
+
Download Now
+
+
+
+END;
+ }
+
+ if(!$found) {
+ echo "No downloads found for $title.
";
+ return false;
+ }
+ }
+
+ public static function iosTestflightTable() {
+ $testflight = 'https://itunes.apple.com/us/app/testflight/id899247664?mt=8';
+ $testflight_beta_signup = 'https://testflight.apple.com/join/9W4XIoxQ';
+ $testflight_alpha_signup = 'https://testflight.apple.com/join/vnCV2EiH';
+ $testflightTable_cdn = cdn('img/testflight-64x64.png');
+ $testflight_about = 'https://developer.apple.com/testflight/testers/';
+ $testflightTable = <<
+ Available through TestFlight
+
+ Sign up here for beta-only Keyman access.
+ Sign up for here alpha-only Keyman access.
+ |
+END;
+ return $testflightTable;
+ }
+
+ }
+?>
\ No newline at end of file
diff --git a/_includes/2020/KeymanDownloadVersions.php b/_includes/2020/KeymanDownloadVersions.php
deleted file mode 100644
index 550c5616..00000000
--- a/_includes/2020/KeymanDownloadVersions.php
+++ /dev/null
@@ -1,41 +0,0 @@
- 'keyman-(\d+\.\d+\.\d+)\.apk', // Generally, use Play Store
- 'ios' => 'keyman-ios-(\d+\.\d+\.\d+)\.ipa', // Always use App Store; included for completeness but not generally used
- 'mac' => 'keyman-(\d+\.\d+\.\d+)\.dmg',
- 'linux' => '-', // Linux distribution is multiple archives, use a package manager
- 'windows' => 'keyman(desktop)?-(\d+\.\d+\.\d+(\.\d+)?).exe'
- ];
-
- static function getDownloadUrls() {
- if(empty(self::$versions))
- self::$versions = @json_decode(Util::call_downloads_keyman_com('/api/version/2.0', 'downloads.keyman.com-api_version_2.0.json'));
- return self::$versions;
- }
-
- static function getDownloadUrl($platform, $tier = 'stable') {
- $versions = self::getDownloadUrls();
- if(empty($versions->$platform->$tier)) {
- return null;
- }
- $files = $versions->$platform->$tier;
- $pattern = self::filenamePatterns[$platform];
- foreach($files->files as $name => $details) {
- //echo $name;
- if(preg_match("/^$pattern\$/", $name)) {
- return KeymanHosts::Instance()->downloads_keyman_com . "/$platform/$tier/{$files->version}/{$name}";
- }
- }
- return null;
- }
- }
diff --git a/_includes/includes/ui/downloads.php b/_includes/includes/ui/downloads.php
deleted file mode 100644
index 29974e56..00000000
--- a/_includes/includes/ui/downloads.php
+++ /dev/null
@@ -1,116 +0,0 @@
-= 1073741824) {
- $bytes = number_format($bytes / 1073741824, 2) . ' GB';
- } elseif ($bytes >= 1048576) {
- $bytes = number_format($bytes / 1048576, 2) . ' MB';
- } elseif ($bytes >= 1024) {
- $bytes = number_format($bytes / 1024, 2) . ' KB';
- } elseif ($bytes > 1) {
- $bytes = $bytes . ' bytes';
- } elseif ($bytes == 1) {
- $bytes = $bytes . ' byte';
- } else {
- $bytes = '0 bytes';
- }
-
- return $bytes;
- }
-
- function downloadSection($product, $platform, $filepattern, $tiers = '', $target = '') {
- if($target == '') $target = $platform;
-
- echo sprintf("%s
\n\n",
- $target, _m_Downloads($product));
- $tiers = explode(' ',$tiers);
- foreach($tiers as $tier) {
- echo downloadLinks($product, $platform, $tier, $filepattern);
- }
- }
-
- function downloadLinks($product, $platform, $tier, $filepatterns) {
- global $versions;
- echo sprintf("%s
\n\n", ucFirst(_m_Downloads($tier)));
- if(!empty($versions->$platform->$tier)) {
- if(!is_array($filepatterns)) $filepatterns = array($filepatterns);
- foreach($filepatterns as $filepattern) {
- $file = str_replace('$version', $versions->$platform->$tier->version, $filepattern);
- $file = str_replace('$tier', $tier, $file);
-
- if(!empty($versions->$platform->$tier->files->$file)) {
- $fileData = $versions->$platform->$tier->files->$file;
- $fileSize = formatSizeUnits($fileData->size);
- echo sprintf("- %s %s %s
\n",
- KeymanHosts::Instance()->downloads_keyman_com ,
- $platform, $tier,
- $versions->$platform->$tier->version,
- $file, $file, $tier,
- _m_Downloads('released_date_size', $fileData->date, $fileSize));
- }
- }
- }
- echo sprintf("- %s
\n
\n",
- KeymanHosts::Instance()->downloads_keyman_com, $platform, $tier,
- _m_Downloads('all_product_releases', _m_Downloads($product), _m_Downloads($tier)));
- }
-
- function downloadLargeCTA($product, $platform, $tier, $filepattern) {
- global $versions;
-
- if(empty($versions)) return;
- $file = str_replace('$version', $versions->$platform->$tier->version, $filepattern);
- $file = str_replace('$tier', $tier, $file);
-
- if(empty($versions->$platform->$tier->files->$file)) {
- echo "No downloads found for $product.
";
- return false;
- }
-
- $fileData = $versions->$platform->$tier->files->$file;
- $fileSize = formatSizeUnits($fileData->size);
- $host = KeymanHosts::Instance()->downloads_keyman_com;
- $downloadSiteUrl = "$host/$platform/$tier/{$versions->$platform->$tier->version}/$file";
- $downloadUrl = htmlentities("/go/app/download/$platform/{$versions->$platform->$tier->version}/$tier?url=".
- rawurlencode($downloadSiteUrl));
-
- echo <<
-
-
$product {$versions->$platform->$tier->version}
-
Released: {$fileData->date}
-
Size: $fileSize
-
-
-
Download Now
-
-
-
-END;
- }
-
- function iosTestflightTable() {
- $testflight = 'https://itunes.apple.com/us/app/testflight/id899247664?mt=8';
- $testflight_beta_signup = 'https://testflight.apple.com/join/9W4XIoxQ';
- $testflight_alpha_signup = 'https://testflight.apple.com/join/vnCV2EiH';
- $testflightTable_cdn = cdn('img/testflight-64x64.png');
- $testflight_about = 'https://developer.apple.com/testflight/testers/';
- $testflightTable = <<
- Available through TestFlight
-
- Sign up here for beta-only Keyman access.
- Sign up for here alpha-only Keyman access.
- |
-END;
- return $testflightTable;
- }
-?>
\ No newline at end of file
From a854461509926079a68bbb62b9d775194884a945 Mon Sep 17 00:00:00 2001
From: Marc Durdin
Date: Wed, 23 Sep 2026 07:50:36 +0200
Subject: [PATCH 4/6] feat: support .pkg extension in /go/download
Test-bot: skip
---
go/download/_download.php | 26 +++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/go/download/_download.php b/go/download/_download.php
index 5517fef5..46875635 100644
--- a/go/download/_download.php
+++ b/go/download/_download.php
@@ -1,4 +1,24 @@
[?tier=][&version=]
+ *
+ * PRODUCT: kmcomp|keyman-developer|keyman-windows|keyman-mac
+ * TIER: alpha|beta|stable (default 'stable')
+ * VERSION: any valid version number (default to latest for tier)
+ *
+ * Redirects to the referenced executable, e.g. keyman-18.0.252.dmg for Mac.
+ *
+ * This page is referenced by Keyman Developer Server, e.g.
+ * /go/download/keyman-mac and by Keyman Developer kmc documentation
+ * /go/download/kmcomp. These paths are rewritten by .htaccess to call into
+ * _download.php.
+ */
+
require_once _KEYMANCOM_INCLUDES . '/autoload.php';
use Keyman\Site\com\keyman\KeymanComSentry;
use Keyman\Site\Common\KeymanHosts;
@@ -34,11 +54,15 @@
$MAC_VERSION = $versions->mac->$TIER->version;
}
+ // epic/mac-config changes the mac installer from a .dmg to .pkg, in version 19.0.2xx-alpha
+ // TODO: update to actual release version
+ $MAC_FILENAME = version_compare($MAC_VERSION, "19.0.288") >= 0 ? "keyman-$MAC_VERSION.pkg" : "keyman-$MAC_VERSION.dmg";
+
$packages = [
"kmcomp" => ["developer", $DEVELOPER_VERSION, "kmcomp-$DEVELOPER_VERSION.zip"],
"keyman-developer" => ["developer", $DEVELOPER_VERSION, "keymandeveloper-$DEVELOPER_VERSION.exe"],
"keyman-windows" => ["windows", $WINDOWS_VERSION, "keyman-$WINDOWS_VERSION.exe"],
- "keyman-mac" => ["mac", $MAC_VERSION, "keyman-$MAC_VERSION.dmg"]
+ "keyman-mac" => ["mac", $MAC_VERSION, $MAC_FILENAME],
];
if(!isset($packages[$object])) {
From 9195f35ce85a14a6e7fa22721f2bdf93b965ff3f Mon Sep 17 00:00:00 2001
From: Marc Durdin
Date: Wed, 23 Sep 2026 09:47:12 +0200
Subject: [PATCH 5/6] fix: use kmcomp-version.zip, not .exe
Co-authored-by: Ross Cruickshank
---
_includes/2020/DownloadUI.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/_includes/2020/DownloadUI.php b/_includes/2020/DownloadUI.php
index aff40e69..77fa5fab 100644
--- a/_includes/2020/DownloadUI.php
+++ b/_includes/2020/DownloadUI.php
@@ -34,7 +34,7 @@ private static function filenamePatterns($target, $tier) {
switch($target) {
case 'android': return 'keyman-$version.apk';
case 'android-engine': return 'keyman-engine-android-$version.zip';
- case 'developer': return ['keymandeveloper-$version.exe', 'kmcomp-$version.exe'];
+ case 'developer': return ['keymandeveloper-$version.exe', 'kmcomp-$version.zip'];
case 'ios': return 'keyman-ios-$version.ipa'; // not currently used
case 'ios-engine': return 'keyman-engine-ios-$version.zip';
case 'linux': return ''; // not currently used
From c23229e702743c773a6ac67e90542164edac9fe6 Mon Sep 17 00:00:00 2001
From: Marc Durdin
Date: Thu, 24 Sep 2026 09:59:17 +0200
Subject: [PATCH 6/6] chore: use 19.0.291-alpha as changeover to .pkg for mac
builds
---
go/download/_download.php | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/go/download/_download.php b/go/download/_download.php
index 46875635..8fc6d356 100644
--- a/go/download/_download.php
+++ b/go/download/_download.php
@@ -54,9 +54,8 @@
$MAC_VERSION = $versions->mac->$TIER->version;
}
- // epic/mac-config changes the mac installer from a .dmg to .pkg, in version 19.0.2xx-alpha
- // TODO: update to actual release version
- $MAC_FILENAME = version_compare($MAC_VERSION, "19.0.288") >= 0 ? "keyman-$MAC_VERSION.pkg" : "keyman-$MAC_VERSION.dmg";
+ // epic/mac-config changes the mac installer from a .dmg to .pkg, in version 19.0.291-alpha
+ $MAC_FILENAME = version_compare($MAC_VERSION, "19.0.291") >= 0 ? "keyman-$MAC_VERSION.pkg" : "keyman-$MAC_VERSION.dmg";
$packages = [
"kmcomp" => ["developer", $DEVELOPER_VERSION, "kmcomp-$DEVELOPER_VERSION.zip"],