Merge branch 'master' into chore/ios/additional-logging-for-xcodebuild

This commit is contained in:
Marc Durdin 2026-05-20 13:32:37 +10:00 committed by GitHub
commit 43756e5e78
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 399 additions and 54 deletions

View file

@ -1,5 +1,11 @@
# Keyman Version History
## 19.0.234 alpha 2026-05-19
* chore(deps): bump brace-expansion from 5.0.5 to 5.0.6 in /developer/src/server/src/win32/trayicon/addon-src (#15969)
* chore(deps): bump ws from 8.18.1 to 8.20.1 (#15971)
* chore: move to .localhost for local server URLs (#15963)
## 19.0.233 alpha 2026-05-18
* chore(deps): bump path-to-regexp and express (#15902)

View file

@ -1 +1 @@
19.0.234
19.0.235

View file

@ -66,11 +66,11 @@ const
URLPath_Community = '/go/'+SKeymanVersion+'/community';
// Keyboard download and installation
URLPath_RegEx_MatchKeyboardsInstall = '^http(?:s)?://keyman(?:-staging)?\.com(?:\.local)?/keyboards/install/([^?/]+)(?:\?(.+))?$';
URLPath_RegEx_MatchKeyboardsInstall = '^http(?:s)?://keyman(?:-staging)?\.com(?:\.localhost)?/keyboards/install/([^?/]+)(?:\?(.+))?$';
// e.g. https://keyman.com/keyboards/install/foo
UrlPath_RegEx_MatchKeyboardsRoot = '^http(?:s)?://keyman(?:-staging)?\.com(?:\.local)?/keyboards([/?].*)?$';
// e.g. http://keyman.com.local/keyboards/foo
UrlPath_RegEx_MatchKeyboardsGo = '^http(?:s)?://keyman(?:-staging)?\.com(?:\.local)?/go/windows/[^/]+/download-keyboards';
UrlPath_RegEx_MatchKeyboardsRoot = '^http(?:s)?://keyman(?:-staging)?\.com(?:\.localhost)?/keyboards([/?].*)?$';
// e.g. http://keyman.com.localhost/keyboards/foo
UrlPath_RegEx_MatchKeyboardsGo = '^http(?:s)?://keyman(?:-staging)?\.com(?:\.localhost)?/go/windows/[^/]+/download-keyboards';
// e.g. https://keyman-staging.com/go/windows/14.0/download-keyboards?version=14.0.146.0
// Cloning keyboards - Keyman Developer

View file

@ -10,6 +10,7 @@
#include <algorithm>
#include <sstream>
#include <memory>
#include <cstring>
#include "action.hpp"
@ -183,3 +184,34 @@ bool km::core::state::set_actions(
return true;
}
using namespace km::core;
namespace {
km_core_usv * duplicate_km_core_usv(const km_core_usv *src) {
if (!src) {
return nullptr;
}
size_t len = 0;
while (src[len]) {
++len;
}
km_core_usv *result = new km_core_usv[len + 1];
std::copy(src, src + len + 1, result);
return result;
}
} // namespace
km_core_actions km::core::clone_actions_object(km_core_actions const &src) {
km_core_actions result;
memset(&result, 0, sizeof(result));
result.code_points_to_delete = src.code_points_to_delete;
result.do_alert = src.do_alert;
result.emit_keystroke = src.emit_keystroke;
result.new_caps_lock_state = src.new_caps_lock_state;
result.deleted_context = duplicate_km_core_usv(src.deleted_context);
result.output = duplicate_km_core_usv(src.output);
result.persist_options = clone_options(src.persist_options);
return result;
}

View file

@ -39,5 +39,9 @@ namespace core
unsigned int code_points_to_delete
);
km_core_actions clone_actions_object(
km_core_actions const &src
);
} // namespace core
} // namespace km

View file

@ -37,7 +37,9 @@ km_core_event(
return KM_CORE_STATUS_INVALID_ARGUMENT;
}
return state->processor().external_event(state, event, data);
km_core_status status = state->processor().external_event(state, event, data);
state->apply_actions_and_merge_app_context();
return status;
}
km_core_status

View file

@ -82,3 +82,18 @@ json & km::core::operator << (json &j, abstract_processor const &)
return j;
}
km_core_option_item *
km::core::clone_options(km_core_option_item const *src) {
if (!src) {
return nullptr;
}
size_t count = km_core_options_list_size(src);
km_core_option_item *result = new km_core_option_item[count + 1];
for (size_t i = 0; i < count; ++i) {
km::core::option opt(static_cast<km_core_option_scope>(src[i].scope), src[i].key, src[i].value);
result[i] = opt.release();
}
result[count] = KM_CORE_OPTIONS_END;
return result;
}

View file

@ -93,7 +93,7 @@ namespace core
return key == nullptr;
}
km_core_option_item * clone_options(km_core_option_item const *src);
} // namespace core
} // namespace km

View file

@ -56,6 +56,9 @@ state::state(km::core::abstract_processor & ap, km_core_option_item const *env)
_imx_callback = nullptr;
_imx_object = nullptr;
memset(const_cast<km_core_actions*>(&_action_struct), 0, sizeof(km_core_actions));
// Ensure _action_struct is initialized to the default values
km_core_action_item no_actions = {KM_CORE_IT_END, {0,}, {0}};
action_item_list_to_actions_object(&no_actions, &this->_action_struct);
}
void state::imx_register_callback(
@ -82,6 +85,19 @@ void state::imx_callback(uint32_t imx_id) {
_imx_callback(static_cast<km_core_state *>(this), imx_id, _imx_object);
}
state::state(state const &other)
: _ctxt(other._ctxt)
, _app_ctxt(other._app_ctxt)
, _processor(other._processor)
, _actions(other._actions)
, _action_struct(clone_actions_object(other._action_struct))
, _debug_items(other._debug_items)
, _imx_callback(other._imx_callback)
, _imx_object(other._imx_object)
, _backspace_handled_internally(other._backspace_handled_internally)
{
}
state::~state() {
km::core::actions_dispose(this->_action_struct);
}

View file

@ -135,7 +135,7 @@ protected:
public:
state(core::abstract_processor & kb, km_core_option_item const *env);
state(state const &) = default;
state(state const &other);
state(state const &&) = delete;
~state();

View file

@ -42,6 +42,165 @@ namespace
return buf;
}
inline
bool action_options_equal(km_core_option_item const * lhs,
km_core_option_item const * rhs)
{
if (lhs == rhs) return true;
if (!lhs || !rhs) return false;
while (lhs->key && rhs->key) {
if (lhs->scope != rhs->scope) return false;
if (std::u16string(lhs->key) != std::u16string(rhs->key)) return false;
if (std::u16string(lhs->value) != std::u16string(rhs->value)) return false;
++lhs;
++rhs;
}
return lhs->key == nullptr && rhs->key == nullptr;
}
inline
bool expect_action_struct(
km_core_actions const & actions,
unsigned int expected_code_points_to_delete,
km_core_usv const * expected_output,
km_core_option_item const * expected_persist_options,
km_core_bool expected_do_alert,
km_core_bool expected_emit_keystroke,
km_core_caps_state expected_new_caps_lock_state,
km_core_usv const * expected_deleted_context
) {
bool all_passed = true;
std::cout << "\n=== Comparing action_struct fields ===" << std::endl;
// code_points_to_delete
std::cout << "code_points_to_delete: " << actions.code_points_to_delete
<< " (expected: " << expected_code_points_to_delete << ")";
if (actions.code_points_to_delete != expected_code_points_to_delete) {
std::cout << " [FAIL]" << std::endl;
all_passed = false;
} else {
std::cout << " [PASS]" << std::endl;
}
// output
std::cout << "output: " << (actions.output ? std::u32string(actions.output) : U"(null)")
<< " expected: " << (expected_output ? std::u32string(expected_output) : U"(null)") << std::endl;
bool output_equal = false;
if (expected_output == actions.output) { // nullptr or same pointer
output_equal = true;
} else if (!expected_output || !actions.output) {
output_equal = false;
} else if (expected_output && actions.output) {
if (std::u32string(actions.output) != std::u32string(expected_output)) {
output_equal = false;
} else {
output_equal = true;
}
}
if (!output_equal) {
std::cout << " [FAIL]" << std::endl;
all_passed = false;
} else {
std::cout << " [PASS]" << std::endl;
}
// persist_options
std::cout << "persist_options comparison:" << std::endl;
if (!action_options_equal(actions.persist_options, expected_persist_options)) {
std::cout << " actual: ";
if (actions.persist_options) {
for (auto opt = actions.persist_options; opt->scope; ++opt) {
std::cout << "[scope=" << opt->scope << ", key=" << opt->key << ", value=" << opt->value << "] ";
}
} else {
std::cout << "(null)";
}
std::cout << std::endl;
std::cout << " expected: ";
if (expected_persist_options) {
for (auto opt = expected_persist_options; opt->key; ++opt) {
std::cout << "[scope=" << opt->scope << ", key=" << opt->key << ", value=" << opt->value << "] ";
}
} else {
std::cout << "(null)";
}
std::cout << " [FAIL]" << std::endl;
all_passed = false;
} else {
std::cout << " [PASS]" << std::endl;
}
// do_alert
std::cout << "do_alert: " << (int)actions.do_alert
<< " (expected: " << (int)expected_do_alert << ")";
if (actions.do_alert != expected_do_alert) {
std::cout << " [FAIL]" << std::endl;
all_passed = false;
} else {
std::cout << " [PASS]" << std::endl;
}
// emit_keystroke
std::cout << "emit_keystroke: " << (int)actions.emit_keystroke
<< " (expected: " << (int)expected_emit_keystroke << ")";
if (actions.emit_keystroke != expected_emit_keystroke) {
std::cout << " [FAIL]" << std::endl;
all_passed = false;
} else {
std::cout << " [PASS]" << std::endl;
}
// new_caps_lock_state
std::cout << "new_caps_lock_state: " << (int)actions.new_caps_lock_state
<< " (expected: " << (int)expected_new_caps_lock_state << ")";
if (actions.new_caps_lock_state != expected_new_caps_lock_state) {
std::cout << " [FAIL]" << std::endl;
all_passed = false;
} else {
std::cout << " [PASS]" << std::endl;
}
// deleted_context
std::cout << "deleted_context: " << (actions.deleted_context ? std::u32string(actions.deleted_context) : U"(null)")
<< " (expected: " << (expected_deleted_context ? std::u32string(expected_deleted_context) : U"(null)") << ")";
if (expected_deleted_context != actions.deleted_context) {
if (std::u32string(actions.deleted_context) != std::u32string(expected_deleted_context)) {
std::cout << " [FAIL]" << std::endl;
all_passed = false;
} else {
std::cout << " [PASS]" << std::endl;
}
} else {
std::cout << " [PASS]" << std::endl;
}
bool deleted_equal = false;
if (expected_deleted_context == actions.deleted_context) { // nullptr or same pointer
deleted_equal = true;
} else if (!expected_deleted_context || !actions.deleted_context) {
deleted_equal = false;
} else if (expected_deleted_context && actions.deleted_context) {
if (std::u32string(actions.deleted_context) != std::u32string(expected_deleted_context)) {
deleted_equal = false;
} else {
deleted_equal = true;
}
}
if (!deleted_equal) {
std::cout << " [FAIL]" << std::endl;
all_passed = false;
} else {
std::cout << " [PASS]" << std::endl;
}
std::cout << "=== End comparison ===" << std::endl << std::endl;
return all_passed;
}
km_core_option_item test_env_opts[] =
{
{u"hello", u"world", 0},
@ -168,6 +327,13 @@ int main(int argc, char * argv[])
try_status(km_core_process_event(test_state, KM_CORE_VKEY_L,
KM_CORE_MODIFIER_SHIFT, 1, KM_CORE_EVENT_FLAG_DEFAULT));
test_assert(action_items(test_state, {{KM_CORE_IT_CHAR, {0,}, {km_core_usv('L')}}, {KM_CORE_IT_END}}));
// Without the calling `km_core_state_context_set_if_needed` action struct has a delete for 'L'?
// Issue raised to investigate further: https://github.com/keymanapp/keyman/issues/15962
km_core_cu const *state_context = get_context_as_string(km_core_state_context(test_state));
km_core_state_context_set_if_needed(test_state, state_context);
delete [] state_context;
try_status(km_core_process_event(test_state, KM_CORE_VKEY_F2, 0, 1, KM_CORE_EVENT_FLAG_DEFAULT));
km_core_action_item action = {KM_CORE_IT_PERSIST_OPT, {0,}, };
@ -187,11 +353,51 @@ int main(int argc, char * argv[])
if (doc1 != doc1_expected) return __LINE__;
if (doc2 != doc2_expected) return __LINE__;
// Destroy them
km_core_state_dispose(test_state);
km_core_state_dispose(test_clone);
km_core_keyboard_dispose(test_kb);
// Test the action_struct values for the active and cloned states are
// independent and match their respective expected values.
const unsigned int expected_state_code_points_to_delete = 0;
const km_core_usv * expected_state_output = U"";
const km_core_bool expected_state_do_alert = KM_CORE_FALSE;
const km_core_bool expected_state_emit_keystroke = KM_CORE_FALSE;
const km_core_caps_state expected_state_new_caps_lock_state = KM_CORE_CAPS_UNCHANGED;
km_core_option_item expected_options[] = {expected_persist_opt, KM_CORE_OPTIONS_END };
const km_core_usv * expected_deleted_text = U"";
// Cloned expected values
const unsigned int clone_state_code_points_to_delete = 0;
const km_core_usv * clone_state_output = U"";
const km_core_bool clone_state_do_alert = KM_CORE_FALSE;
const km_core_bool clone_state_emit_keystroke = KM_CORE_FALSE;
const km_core_caps_state clone_state_new_caps_lock_state = KM_CORE_CAPS_UNCHANGED;
km_core_option_item clone_state_options[] = {KM_CORE_OPTIONS_END};
const km_core_usv * clone_state_deleted_text = nullptr;
const auto & state_actions = test_state->action_struct();
const auto & clone_actions = test_clone->action_struct();
test_assert (expect_action_struct(state_actions,
expected_state_code_points_to_delete,
expected_state_output,
expected_options,
expected_state_do_alert,
expected_state_emit_keystroke,
expected_state_new_caps_lock_state,
expected_deleted_text
));
test_assert (expect_action_struct(clone_actions,
clone_state_code_points_to_delete,
clone_state_output,
clone_state_options,
clone_state_do_alert,
clone_state_emit_keystroke,
clone_state_new_caps_lock_state,
clone_state_deleted_text
));
// Destroy them
km_core_state_dispose(test_state);
km_core_state_dispose(test_clone);
km_core_keyboard_dispose(test_kb);
return 0;
}

View file

@ -19,7 +19,7 @@
"restructure": "^3.0.1",
"sax": ">=0.6.0",
"semver": "^7.5.4",
"ws": "^8.17.1",
"ws": "^8.20.1",
"xmlbuilder": "~11.0.0"
},
"optionalDependencies": {

View file

@ -16,7 +16,7 @@ import Foundation
*/
public enum KeymanHosts {
/**
* Used to enable '.local' variants of the endpoints for use in local development testing.
* Used to enable '.localhost' variants of the endpoints for use in local development testing.
*/
internal static let useLocal = false
@ -24,7 +24,7 @@ public enum KeymanHosts {
// save for use in automated testing.
internal static func getApiSiteURL(forTier: Version.Tier, useLocal: Bool) -> URL {
if useLocal {
return URL.init(string: "http://api.keyman.com.local")!
return URL.init(string: "http://api.keyman.com.localhost")!
} else {
switch forTier {
case .alpha:
@ -48,7 +48,7 @@ public enum KeymanHosts {
// save for use in automated testing.
internal static func getHelpSiteURL(forTier: Version.Tier, useLocal: Bool) -> URL {
if useLocal {
return URL.init(string: "http://help.keyman.com.local")!
return URL.init(string: "http://help.keyman.com.localhost")!
} else {
switch forTier {
case .alpha:
@ -72,7 +72,7 @@ public enum KeymanHosts {
// save for use in automated testing.
internal static func getMainSiteURL(forTier: Version.Tier, useLocal: Bool) -> URL {
if useLocal {
return URL.init(string: "http://keyman.com.local")!
return URL.init(string: "http://keyman.com.localhost")!
} else {
switch forTier {
case .alpha:

View file

@ -25,11 +25,11 @@ public class UniversalLinks {
public static var externalLinkLauncher: ((URL) -> Void)? = nil
// e.g. https://keyman.com/keyboards/install/foo
private static let KEYBOARD_INSTALL_LINK_REGEX = try! NSRegularExpression(pattern: "^http(?:s)?://keyman(?:-staging)?\\.com(?:\\.local)?/keyboards/install/([^?/]+)(?:\\?(.+))?$")
// e.g. http://keyman.com.local/keyboards/foo
private static let KEYBOARD_MATCH_ROOT_REGEX = try! NSRegularExpression(pattern: "^http(?:s)?://keyman(?:-staging)?\\.com(?:\\.local)?/keyboards([/?].*)?$");
private static let KEYBOARD_INSTALL_LINK_REGEX = try! NSRegularExpression(pattern: "^http(?:s)?://keyman(?:-staging)?\\.com(?:\\.localhost)?/keyboards/install/([^?/]+)(?:\\?(.+))?$")
// e.g. http://keyman.com.localhost/keyboards/foo
private static let KEYBOARD_MATCH_ROOT_REGEX = try! NSRegularExpression(pattern: "^http(?:s)?://keyman(?:-staging)?\\.com(?:\\.localhost)?/keyboards([/?].*)?$");
// e.g. https://keyman-staging.com/go/windows/14.0/download-keyboards?version=14.0.146.0
private static let KEYBOARD_MATCH_GO_REGEX = try! NSRegularExpression(pattern: "^http(?:s)?://keyman(?:-staging)?\\.com(?:\\.local)?/go/windows/[^/]+/download-keyboards")
private static let KEYBOARD_MATCH_GO_REGEX = try! NSRegularExpression(pattern: "^http(?:s)?://keyman(?:-staging)?\\.com(?:\\.localhost)?/go/windows/[^/]+/download-keyboards")
public static func tryParseKeyboardInstallLink(_ link: URL) -> ParsedKeyboardInstallLink? {
let linkString = link.absoluteString

View file

@ -18,14 +18,14 @@ class KeymanHostTests: XCTestCase {
}
/**
* Ensures no accidental permanent edits to the .local variant URLs occur.
* Ensures no accidental permanent edits to the .localhost variant URLs occur.
*/
func testLocalSitesUnchanged() {
XCTAssertEqual(KeymanHosts.getApiSiteURL(forTier: .stable, useLocal: true),
URL.init(string: "http://api.keyman.com.local"))
URL.init(string: "http://api.keyman.com.localhost"))
XCTAssertEqual(KeymanHosts.getHelpSiteURL(forTier: .stable, useLocal: true),
URL.init(string: "http://help.keyman.com.local"))
URL.init(string: "http://help.keyman.com.localhost"))
XCTAssertEqual(KeymanHosts.getMainSiteURL(forTier: .stable, useLocal: true),
URL.init(string: "http://keyman.com.local"))
URL.init(string: "http://keyman.com.localhost"))
}
}

View file

@ -1,6 +1,8 @@
#!/usr/bin/env bash
# Build source packages from nightly builds and upload to PPA
# shellcheck disable=SC2310 # -e will be disabled in if
## START STANDARD BUILD SCRIPT INCLUDE
# adjust relative paths as necessary
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
@ -18,6 +20,7 @@ builder_describe \
"--upload Upload to launchpad." \
"--simulate Simulate the upload to launchpad." \
"--no-lintian Don't run lintian while creating source package." \
"--no-sign Don't sign the source package." \
"--dist=DIST Only upload this distribution. Default: upload all supported dists." \
"--packageversion=PACKAGEVERSION String to append to the package version. Default: '1~sil1'." \
"--outputdir=OUTPUTDIR Directory for resulting artifacts. Default: \$KEYMAN_ROOT/linux/launchpad." \
@ -39,10 +42,13 @@ else
SIM=""
fi
DEBUILD_OPTS=()
if builder_has_option --no-lintian; then
LINTIAN_OPTS="--no-lintian"
else
LINTIAN_OPTS=""
DEBUILD_OPTS+=("--no-lintian")
fi
if builder_has_option --no-sign; then
DEBUILD_OPTS+=("--no-sign")
fi
if [[ "${KEYMAN_TIER}" == "stable" ]]; then
@ -115,8 +121,9 @@ for dist in ${distributions}; do
cp "../keyman-changelog" debian/changelog
dch -v "${version}-${packageversion}~${dist}" "source package for PPA"
dch -D "${dist}" -r ""
# shellcheck disable=SC2248 # no quotes for $LINTIAN_OPTS - might be empty string
debuild ${LINTIAN_OPTS} -d -S -sa -Zxz
# According to the docs, -S is equivalent to --build=source, but that causes debuild to fail.
# There is no long option for -sa (passed to dpkg-genchanges)
debuild "${DEBUILD_OPTS[@]}" --no-check-builddeps --compression=xz -S -sa
done
if builder_has_option --upload || builder_has_option --simulate; then
cd ..

View file

@ -11,6 +11,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "${THIS_SCRIPT%/*}/../../resources/build/builder-full.inc.sh"
## END STANDARD BUILD SCRIPT INCLUDE
. "${KEYMAN_ROOT}/resources/locate_emscripten.inc.sh"
builder_describe \
"Verify source tarball and source package" \
build \
@ -81,7 +83,7 @@ create_source_package() {
cp -r "${KEYMAN_ROOT}/linux/debian" "keyman-${KEYMAN_VERSION}"
cp "${target_dir}/keyman_${KEYMAN_VERSION}.pkg.tar.xz" "keyman_${KEYMAN_VERSION}.orig.tar.xz"
"keyman-${KEYMAN_VERSION}/linux/scripts/launchpad.sh" --no-download \
--dist "$(lsb_release -c -s)" --outputdir "${target_dir}/launchpad" --no-lintian
--dist "$(lsb_release -c -s)" --outputdir "${target_dir}/launchpad" --no-lintian --no-sign
}
verify_lintian() {
@ -89,6 +91,18 @@ verify_lintian() {
lintian "keyman_${KEYMAN_VERSION}"*source.changes
}
install_emscripten() {
local target_dir="$1"
if [[ ! -d "${target_dir}/emsdk" ]]; then
builder_echo heading "Installing Emscripten for build verification"
install_emscripten_into "${target_dir}/emsdk"
else
builder_echo heading "Emscripten already exists in ${target_dir}/emsdk, skipping installation"
fi
EMSCRIPTEN_BASE="${target_dir}/emsdk/upstream/emscripten"
export EMSCRIPTEN_BASE
}
cd "${KEYMAN_ROOT}/linux"
if ! builder_has_option --no-create-tarball; then
@ -100,6 +114,9 @@ fi
if ! builder_has_option --launchpad-only; then
builder_echo start verifySource "Verifying source tarball"
extract_source_tarball "${TARGET_DIR}"
if builder_is_ci_test_build; then
install_emscripten "${TARGET_DIR}"
fi
verify_can_build "${TARGET_DIR}/keyman"
rm -rf "${TARGET_DIR}/keyman"
builder_echo end verifySource success "Finished verifying source tarball"

View file

@ -22,15 +22,15 @@
- (void)windowDidLoad {
[super windowDidLoad];
[self.webView setFrameLoadDelegate:(id<WebFrameLoadDelegate>)self];
[self.webView setGroupName:@"KMDownloadKB"];
[self.webView setPolicyDelegate:(id<WebPolicyDelegate>)self];
NSString *version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
KeymanVersionInfo keymanVersionInfo = [[self AppDelegate] versionInfo];
NSString *url = [NSString stringWithFormat:@"https://%@/go/macos/14.0/download-keyboards/?version=%@", keymanVersionInfo.keymanCom, version];
os_log_debug([KMLogs uiLog], "KMDownloadKBWindowController opening url = %@, version = '%@'", url, version);
[self.webView.mainFrame loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
}
@ -45,21 +45,21 @@
os_log_debug([KMLogs uiLog], "decidePolicyForNavigationAction, navigating to %@", url);
// The pattern for matching links matches work in #3602
NSString* urlPathMatchKeyboardsInstall = @"^http(?:s)?://keyman(?:-staging)?\\.com(?:\\.local)?/keyboards/install/([^?/]+)(?:\\?(.+))?$";
NSString* urlPathMatchKeyboardsInstall = @"^http(?:s)?://keyman(?:-staging)?\\.com(?:\\.localhost)?/keyboards/install/([^?/]+)(?:\\?(.+))?$";
// e.g. https://keyman.com/keyboards/install/foo
NSString* urlPathMatchKeyboardsRoot = @"^http(?:s)?://keyman(?:-staging)?\\.com(?:\\.local)?/keyboards([/?].*)?$";
// http://keyman.com.local/keyboards/foo
NSString* urlPathMatchKeyboardsGo = @"^http(?:s)?://keyman(?:-staging)?\\.com(?:\\.local)?/go/macos/[^/]+/download-keyboards";
NSString* urlPathMatchKeyboardsRoot = @"^http(?:s)?://keyman(?:-staging)?\\.com(?:\\.localhost)?/keyboards([/?].*)?$";
// http://keyman.com.localhost/keyboards/foo
NSString* urlPathMatchKeyboardsGo = @"^http(?:s)?://keyman(?:-staging)?\\.com(?:\\.localhost)?/go/macos/[^/]+/download-keyboards";
// https://keyman-staging.com/go/macos/14.0/download-keyboards?version=14.0.146.0
NSRange range = NSMakeRange(0, url.length);
NSError* error;
NSRegularExpression* regexInstall = [NSRegularExpression regularExpressionWithPattern: urlPathMatchKeyboardsInstall options: 0 error: &error];
NSRegularExpression* regexRoot = [NSRegularExpression regularExpressionWithPattern: urlPathMatchKeyboardsRoot options: 0 error: &error];
NSRegularExpression* regexGo = [NSRegularExpression regularExpressionWithPattern: urlPathMatchKeyboardsGo options: 0 error: &error];
NSArray* matchesInstall = [regexInstall matchesInString:url options:0 range:range];
if(matchesInstall.count > 0) {
os_log_debug([KMLogs uiLog], "Delegating download to app delegate.");
[listener ignore];

24
package-lock.json generated
View file

@ -1139,7 +1139,7 @@
"restructure": "^3.0.1",
"sax": ">=0.6.0",
"semver": "^7.5.4",
"ws": "^8.17.1",
"ws": "^8.20.1",
"xmlbuilder": "~11.0.0"
},
"devDependencies": {
@ -1186,6 +1186,27 @@
"url": "https://opencollective.com/express"
}
},
"developer/src/server/node_modules/ws": {
"version": "8.20.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz",
"integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/@75lb/deep-merge": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@75lb/deep-merge/-/deep-merge-1.1.2.tgz",
@ -13680,6 +13701,7 @@
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"

View file

@ -24,6 +24,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "$KEYMAN_ROOT/resources/build/ci/ci-publish.inc.sh"
. "$KEYMAN_ROOT/resources/build/ci/npm-packages.inc.sh"
. "$KEYMAN_ROOT/resources/build/minimum-versions.inc.sh"
. "$KEYMAN_ROOT/resources/locate_emscripten.inc.sh"
builder_describe \
"Publish @keymanapp packages to NPM" \
@ -107,17 +108,10 @@ function install_emscripten() {
local EMSDK_TEMP
EMSDK_TEMP=$(mktemp -d)
pushd "${EMSDK_TEMP}"
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install "${KEYMAN_MIN_VERSION_EMSCRIPTEN}"
./emsdk activate "${KEYMAN_MIN_VERSION_EMSCRIPTEN}"
cd upstream/emscripten
npm install
echo "EMSCRIPTEN_BASE=$(pwd)" >> $GITHUB_ENV
EMSCRIPTEN_BASE="$(pwd)"
install_emscripten_into "${EMSDK_TEMP}"
EMSCRIPTEN_BASE="${EMSDK_TEMP}/upstream/emscripten"
export EMSCRIPTEN_BASE
popd
echo "EMSCRIPTEN_BASE=${EMSCRIPTEN_BASE}" >> "${GITHUB_ENV}"
}
function install_meson() {

View file

@ -124,7 +124,12 @@ function add_zip_files() {
if [[ -z "${SEVENZ+x}" ]]; then
if builder_is_windows; then
if [[ -z "${SEVENZ_HOME+x}" ]]; then
SEVENZ="$(command -v 7z.exe)"
SEVENZ="$(command -v 7z.exe || true)"
if [[ -z "${SEVENZ}" ]]; then
builder_die "7z.exe not found on path. Please install 7-Zip " \
"and ensure 7z.exe is on the path or set SEVENZ_HOME " \
"environment variable to the folder containing 7z.exe."
fi
else
SEVENZ="${SEVENZ_HOME}/7z.exe"
fi

View file

@ -38,7 +38,7 @@ locate_emscripten() {
if [[ -z "${EMSCRIPTEN_BASE:-}" ]]; then
if [[ -z "${EMCC:-}" ]]; then
local EMCC
EMCC="$(command -v "${EMCC_EXECUTABLE}")"
EMCC="$(command -v "${EMCC_EXECUTABLE}" || true)"
[[ -z "${EMCC}" ]] && builder_die "locate_emscripten: Could not locate emscripten (${EMCC_EXECUTABLE}) on the path or with \$EMCC or \$EMSCRIPTEN_BASE"
fi
[[ -f "${EMCC}" && ! -x "${EMCC}" ]] && builder_die "locate_emscripten: Variable EMCC (${EMCC}) points to ${EMCC_EXECUTABLE} but it is not executable"
@ -117,3 +117,22 @@ _select_emscripten_version_with_emsdk() {
fi
)
}
install_emscripten_into() {
if [[ -z "${1:-}" ]]; then
builder_die "${FUNCNAME[0]} requires a directory argument"
fi
local EMSDK_DIR=$1
builder_heading "Installing emscripten into ${EMSDK_DIR}"
mkdir -p "${EMSDK_DIR}"
(
cd "${EMSDK_DIR}"
git clone https://github.com/emscripten-core/emsdk.git .
./emsdk install "${KEYMAN_MIN_VERSION_EMSCRIPTEN}"
./emsdk activate "${KEYMAN_MIN_VERSION_EMSCRIPTEN}"
cd upstream/emscripten
npm install
)
}