From 017a26435e7eaa3326cc9d3c31885799535066e3 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 29 Aug 2025 15:45:50 +1000 Subject: [PATCH 01/52] fix(windows): continue when waitingrestart return to idle When waiting restart on a handlekmshell event and returning to idle kmshell execution should continue --- .../desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 7b831b97c4..16a748bdd5 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -955,7 +955,7 @@ begin // Return to Idle state and check for Updates state ChangeState(IdleState); bucStateContext.CurrentState.HandleCheck; - Result := kmShellExit; + Result := kmShellContinue; end else begin From 68c9a77884729bb0ad7ef0f64003a247babd124e Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 29 Aug 2025 12:54:25 +0200 Subject: [PATCH 02/52] refactor(common): unify name of What's New file Standardize on `whatsnew.md`. Build-bot: skip Test-bot: skip --- developer/docs/help/index.md | 4 ++-- developer/docs/help/{whats-new.md => whatsnew.md} | 0 web/docs/engine/index.md | 2 +- web/docs/engine/{whats-new.md => whatsnew.md} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename developer/docs/help/{whats-new.md => whatsnew.md} (100%) rename web/docs/engine/{whats-new.md => whatsnew.md} (100%) diff --git a/developer/docs/help/index.md b/developer/docs/help/index.md index d7fbc4098d..888d8c57f0 100644 --- a/developer/docs/help/index.md +++ b/developer/docs/help/index.md @@ -8,7 +8,7 @@ and tutorials, and full reference information. ## Guides and Tutorials - [What is Keyman Developer?](guides/intro) -- [What's new in 19.0](whats-new) +- [What's new in 19.0](whatsnew) - [Developing Keyman keyboard layouts](guides/develop) - [Testing Keyman keyboards](guides/test) - [Distributing Keyman keyboards](guides/distribute) @@ -35,4 +35,4 @@ and tutorials, and full reference information. - [Keyman Engine for Desktop](/developer/engine/desktop/current-version/) - [Keyman Engine for Web](/developer/engine/web/) - [Keyman Engine for iPhone and iPad](/developer/engine/iphone-and-ipad/current-version/) -- [Keyman Engine for Android](/developer/engine/android/current-version/) \ No newline at end of file +- [Keyman Engine for Android](/developer/engine/android/current-version/) diff --git a/developer/docs/help/whats-new.md b/developer/docs/help/whatsnew.md similarity index 100% rename from developer/docs/help/whats-new.md rename to developer/docs/help/whatsnew.md diff --git a/web/docs/engine/index.md b/web/docs/engine/index.md index 5515112f88..377b1ae66f 100644 --- a/web/docs/engine/index.md +++ b/web/docs/engine/index.md @@ -20,7 +20,7 @@ computer browsers. -[What's New](whats-new) +[What's New](whatsnew) : What's new in Keyman Engine for Web 19.0 diff --git a/web/docs/engine/whats-new.md b/web/docs/engine/whatsnew.md similarity index 100% rename from web/docs/engine/whats-new.md rename to web/docs/engine/whatsnew.md From 50950c6737ef537e8d7ffd7ea6cde7a037032890 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 1 Sep 2025 17:39:21 +0200 Subject: [PATCH 03/52] docs(web): Improve empty What's New page for Web Build-bot: skip Test-bot: skip --- web/docs/engine/whatsnew.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/docs/engine/whatsnew.md b/web/docs/engine/whatsnew.md index e220fb9c13..8828f3b81f 100644 --- a/web/docs/engine/whatsnew.md +++ b/web/docs/engine/whatsnew.md @@ -1,3 +1,7 @@ --- title: What's New in KeymanWeb 19.0 --- + +## See also + +* [Keyman Engine for Web Documentation](index) From 33e216fbc61766b1571cf1c62829e268b5dc8270 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 3 Sep 2025 09:47:43 +0700 Subject: [PATCH 04/52] fix(android): Update KMSample1 when changing orientation --- .../com/keyman/kmsample1/MainActivity.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java index 86e1f5af22..add6bc5d63 100644 --- a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java +++ b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java @@ -2,7 +2,9 @@ package com.keyman.kmsample1; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Point; import android.os.Bundle; +import android.util.TypedValue; import android.view.Menu; import android.view.MenuItem; @@ -25,6 +27,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene public static Context context; private ConstraintLayout constraintLayout; private KMTextView textView; + private int lastOrientation = Configuration.ORIENTATION_UNDEFINED; @Override protected void onCreate(Bundle savedInstanceState) { @@ -99,6 +102,18 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene protected void onResume() { super.onResume(); KMManager.onResume(); + + // onConfigurationChanged() only triggers when device is rotated while app is in foreground + // This handles when device is rotated while app is in background + // using KMManager.getOrientation() since getConfiguration().orientation is unreliable #10241 + int newOrientation = KMManager.getOrientation(context); + if (newOrientation != lastOrientation) { + lastOrientation = newOrientation; + Configuration newConfig = this.getResources().getConfiguration(); + KMManager.onConfigurationChanged(newConfig); + } + resizeTextView(textView.isKeyboardVisible()); + KMManager.addKeyboardEventListener(this); KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this); } @@ -116,6 +131,10 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + KMManager.onConfigurationChanged(newConfig); + + resizeTextView(textView.isKeyboardVisible()); + lastOrientation = newConfig.orientation; } @Override @@ -131,11 +150,44 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene @Override public void onKeyboardShown() { // Handle Keyman keyboard shown event here if needed + resizeTextView(true); } @Override public void onKeyboardDismissed() { // Handle Keyman keyboard dismissed event here if needed + resizeTextView(false); + } + + public void resizeTextView(boolean isKeyboardVisible) { + int bannerHeight = 0; + int keyboardHeight = 0; + if (isKeyboardVisible) { + bannerHeight = KMManager.getBannerHeight(this); + keyboardHeight = KMManager.getKeyboardHeight(this); + } + + TypedValue outValue = new TypedValue(); + getTheme().resolveAttribute(android.R.attr.actionBarSize, outValue, true); + int actionBarHeight = getResources().getDimensionPixelSize(outValue.resourceId); + + // *** TO DO: Try to check if status bar is visible, set statusBarHeight to 0 if it is not visible *** + int statusBarHeight = 0; + int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) + statusBarHeight = getResources().getDimensionPixelSize(resourceId); + + // Navigation bar height + int navigationBarHeight = 0; + resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android"); + if (resourceId > 0) { + navigationBarHeight = getResources().getDimensionPixelSize(resourceId); + } + + Point size = KMManager.getWindowSize(context); + int screenHeight = size.y; + textView.setHeight( + screenHeight - statusBarHeight - actionBarHeight - bannerHeight - keyboardHeight - navigationBarHeight); } @Override From 9d8c346e9e538931fe9bb17a806acfb53e594521 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Wed, 3 Sep 2025 14:06:51 -0400 Subject: [PATCH 05/52] auto: increment master version to 19.0.113 Test-bot: skip Build-bot: skip --- HISTORY.md | 12 ++++++++++++ VERSION.md | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index e553e13395..b9d82e41ff 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,17 @@ # Keyman Version History +## 19.0.112 alpha 2025-09-03 + +* fix(developer): add clear messages about validation failures to New LM Project Parameters form (#14659) +* docs(developer): tweak get-started for kmc and add entry to HISTORY.md (#14667) +* fix(android): Cleanup list layout in Engine and FirstVoices for edge to edge (#14655) +* docs(common): update license date and owner (#14668) +* maint(linux): skip tarball creation for `Build-bot: build` buildLevel (#14671) +* docs(common): update docs Keyman Desktop → Keyman for Windows (#14669) +* feat(web): adds transform cataloging methods to predictive-text worker (#14581) +* feat(web): batch correction-search paths by related subsets (#14592) +* refactor(web): simplify edit-distance calculation-object inputs (#14618) + ## 19.0.111 alpha 2025-09-02 * change(developer): update SIL logo (#14656) diff --git a/VERSION.md b/VERSION.md index ee647f65c3..8b442ffb93 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.112 \ No newline at end of file +19.0.113 \ No newline at end of file From 3ea87561929301384ae17a9f94e61bf30575f3c2 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 4 Sep 2025 14:30:12 +1000 Subject: [PATCH 06/52] fix(windows): refactor ReadyToInstall It became apparent that we need to abort the "waitingtorestart" state before promting the user if they want to install. Otherwise it is confusing as to why nothing happens and Keyman just starts. In doing that it was easier to refactor ReadyToInstall to validate the cache, and check that automatic updates was still set. It can now also trigger an abort clearing the cache and returning to idle. This has slight flow on effect in that the HandleKMShell event can still be called following in the exectution flow. --- .../main/Keyman.System.UpdateStateMachine.pas | 41 ++++++++++++++----- windows/src/desktop/kmshell/main/initprog.pas | 17 ++------ 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 16a748bdd5..98b0080842 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -88,14 +88,18 @@ type procedure HandleFirstRun; function CurrentStateName: string; (** - * Checks if Keyman is the WaitingRestartState and that - * Keyman has not run in this Windows session. - * The sole purpose is for the calling code then produce - * a UI to confirm the user wants to continue install. + * Checks if Keyman is the WaitingRestartState, the cache is valid + * and that Keyman has not run in this Windows session. + * The purpose is for the calling code to produce + * a UI to confirm the user wants to install an update, + * if everything is ready to start a update. + * + * NOTE: If it finds the cache is invalid it will abort causing + * the State Machine to change state, returning to idle. * * @returns True if the Keyman is ready to install. *) - function ReadyToInstall: Boolean; + function ValidateReadyToInstall: Boolean; function IsInstallingState: Boolean; function GetAutomaticUpdates: Boolean; @@ -558,14 +562,31 @@ begin Result := CurrentState.ClassName; end; -function TUpdateStateMachine.ReadyToInstall: Boolean; +function TUpdateStateMachine.ValidateReadyToInstall: Boolean; begin if not IsCurrentStateAssigned then Exit(False); - if (CurrentState is WaitingRestartState) and not HasKeymanRun then - Result := True - else - Result := False; + + if not (CurrentState is WaitingRestartState) then + Exit(False); + + // Validate the cache otherwise we could + // prompt the caller for update but then fail. + // Also combined #14295 moved check here from initprog.pas + // The setting for automatic updates may have been set to disable + // after updates were checked and downloaded. The user should + // not be prompted for an update in this case and the update should + // abort. + if not (TUpdateCheckStorage.CheckMetaDataForUpdate and Self.GetAutomaticUpdates) then + begin + HandleAbort; + Exit(False); + end; + + if not HasKeymanRun then + Exit(True); + + Result := False; end; function TUpdateStateMachine.IsInstallingState: Boolean; diff --git a/windows/src/desktop/kmshell/main/initprog.pas b/windows/src/desktop/kmshell/main/initprog.pas index 8a9e037de6..1f02c696e3 100644 --- a/windows/src/desktop/kmshell/main/initprog.pas +++ b/windows/src/desktop/kmshell/main/initprog.pas @@ -668,20 +668,11 @@ function ShouldSendToBUpdateSM(FSilent: Boolean; BUpdateSM: TUpdateStateMachine; // UI elements from the state machine we have bring some of logic here. var frmStartInstall: TfrmStartInstall; - ReadyToInstall: Boolean; + ValidateReadyToInstall: Boolean; begin - ReadyToInstall := BUpdateSM.ReadyToInstall; - // The setting for automatic updates may have been set to disable - // after updates were checked and downloaded. The user should - // not be prompted for an update in this case and the update should - // abort. - if not BUpdateSM.GetAutomaticUpdates and ReadyToInstall then - begin - BUpdateSM.HandleAbort; - Exit(False); - end; + ValidateReadyToInstall := BUpdateSM.ValidateReadyToInstall; - if not FSilent and ReadyToInstall and + if not FSilent and ValidateReadyToInstall and (FMode in [fmStart, fmSplash, fmMain, fmAbout, fmHelp, fmShowHelp, fmSettings, fmBoot]) then begin @@ -692,7 +683,7 @@ begin frmStartInstall.Free; end; end - else if FSilent and ReadyToInstall then + else if FSilent and ValidateReadyToInstall then Result := False else Result := True; From 74ef39726bd47356f9f89b61db9f74e2c7b6fa00 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 3 Sep 2025 15:49:56 +0200 Subject: [PATCH 07/52] fix(web): fix error trying to calculate style scaling effects PR #13860 fixed a similar error by adding a check for NaN. However, it missed doing it for `layoutWidth` and only changed `layoutHeight`. This PR adds a similar check to `layoutWidth` and will thus fix the error we're still seeing in Sentry. Also add unit tests for both methods. User Testing ============ TEST_INUKTITUT_KEYBOARD: Using Keyman for Android, install the `inuktitut_pirurvik` and verify that no error notifications appear. Fixes: #14108 Fixes: [KEYMAN-WEB-RA](https://keyman.sentry.io/issues/6579536471/?referrer=github_integration) --- web/src/engine/osk/src/visualKeyboard.ts | 1 + .../engine/osk/visualKeyboard.tests.ts | 164 ++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 web/src/test/auto/headless/engine/osk/visualKeyboard.tests.ts diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index 2774885e04..9c2bef3609 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -797,6 +797,7 @@ export default class VisualKeyboard extends EventEmitter implements Ke if (this.usesFixedWidthScaling) { let baseWidth = this.width; baseWidth -= this._borderWidth * 2; + baseWidth = (isNaN(baseWidth) || baseWidth < 0) ? 0 : baseWidth; return ParsedLengthStyle.inPixels(baseWidth); } else { return ParsedLengthStyle.forScalar(1); diff --git a/web/src/test/auto/headless/engine/osk/visualKeyboard.tests.ts b/web/src/test/auto/headless/engine/osk/visualKeyboard.tests.ts new file mode 100644 index 0000000000..d6aa132ac2 --- /dev/null +++ b/web/src/test/auto/headless/engine/osk/visualKeyboard.tests.ts @@ -0,0 +1,164 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ +import { assert } from 'chai'; +import sinon from 'sinon'; +// @ts-ignore // Type info unavailable; doing a npm-install for it breaks things much worse in other ways. +import { JSDOM } from 'jsdom'; + +import { VisualKeyboard } from 'keyman/engine/osk'; +import { ActiveLayout, Keyboard, KeyboardProperties } from 'keyman/engine/keyboard'; +import { StylesheetManager } from 'keyman/engine/dom-utils'; +import { DeviceSpec } from "@keymanapp/web-utils"; + +describe('VisualKeyboard', () => { + let dom: JSDOM; + let mockConfig: any; + + beforeEach(() => { + dom = new JSDOM('

Hello world

'); + global.document = dom.window.document; + global.getComputedStyle = dom.window.getComputedStyle; + + const mockDevice = { browser: DeviceSpec.Browser.Chrome, formFactor: DeviceSpec.FormFactor.Desktop, OS: DeviceSpec.OperatingSystem.Windows, touchable: false }; + const mockLayout = sinon.createStubInstance(ActiveLayout) as any; + + const keyboard = sinon.createStubInstance(Keyboard) as any; + keyboard.layout.returns(mockLayout); + sinon.stub(keyboard, 'isRTL').get(() => false); + sinon.stub(keyboard, 'id').get(() => 'test'); + sinon.stub(keyboard, 'name').get(() => 'Test Keyboard'); + + mockConfig = { + device: mockDevice, + hostDevice: mockDevice, + pathConfig: { fonts: '', resources: '' }, + keyboard: keyboard, + keyboardMetadata: sinon.createStubInstance(KeyboardProperties), + topContainer: document.createElement('div'), + styleSheetManager: sinon.createStubInstance(StylesheetManager), + }; + }); + + function createVisualKeyboard(fixedScaling: boolean = true): VisualKeyboard { + const vk = new VisualKeyboard(mockConfig); + vk.usesFixedHeightScaling = fixedScaling; + vk.usesFixedWidthScaling = fixedScaling; + vk['_borderWidth'] = 3; + return vk; + } + + describe('LayoutHeight', () => { + it('calculates LayoutHeight correctly for number', () => { + const vk = createVisualKeyboard(); + vk['_height'] = 100; + assert.equal(vk.layoutHeight.val, 94); + }); + + it('calculates LayoutHeight correctly for number string', () => { + const vk = createVisualKeyboard(); + vk['_height'] = "100"; + assert.equal(vk.layoutHeight.val, 94); + }); + + it('accepts small heights for LayoutHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = "5"; + assert.equal(vk.layoutHeight.val, 0); + }); + + it('ignores non-number string for LayoutHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = "100px"; + assert.equal(vk.layoutHeight.val, 0); + }); + + it('ignores invalid string for LayoutHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = "invalid"; + assert.equal(vk.layoutHeight.val, 0); + }); + + it('ignores undefined value for LayoutHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = undefined; + assert.equal(vk.layoutHeight.val, 0); + }); + + it('ignores null value for LayoutHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = null; + assert.equal(vk.layoutHeight.val, 0); + }); + + it('calculates LayoutHeight correctly with fixed scaling', () => { + const vk = createVisualKeyboard(true); + vk['_height'] = 100; + assert.equal(vk.layoutHeight.val, 94); + }); + + it('calculates LayoutHeight correctly with fixed scaling and string', () => { + const vk = createVisualKeyboard(true); + vk['_height'] = "100px"; + assert.equal(vk.layoutHeight.val, 0); + }); + + }); + + describe('LayoutWidth', () => { + it('calculates LayoutWidth correctly for number', () => { + const vk = createVisualKeyboard(); + vk['_width'] = 100; + assert.equal(vk.layoutWidth.val, 94); + }); + + it('calculates LayoutWidth correctly for number string', () => { + const vk = createVisualKeyboard(); + vk['_width'] = "100"; + assert.equal(vk.layoutWidth.val, 94); + }); + + it('accepts small heights for LayoutWidth', () => { + const vk = createVisualKeyboard(); + vk['_width'] = "5"; + assert.equal(vk.layoutWidth.val, 0); + }); + + it('ignores non-number string for LayoutWidth', () => { + const vk = createVisualKeyboard(); + vk['_width'] = "100px"; + assert.equal(vk.layoutWidth.val, 0); + }); + + it('ignores invalid string for LayoutWidth', () => { + const vk = createVisualKeyboard(); + vk['_width'] = "invalid"; + assert.equal(vk.layoutWidth.val, 0); + }); + + it('ignores undefined value for LayoutWidth', () => { + const vk = createVisualKeyboard(); + vk['_width'] = undefined; + assert.equal(vk.layoutWidth.val, 0); + }); + + it('ignores null value for LayoutWidth', () => { + const vk = createVisualKeyboard(); + vk['_width'] = null; + assert.equal(vk.layoutWidth.val, 0); + }); + + it('calculates LayoutWidth correctly with fixed scaling', () => { + const vk = createVisualKeyboard(true); + vk['_width'] = 100; + assert.equal(vk.layoutWidth.val, 94); + }); + + it('calculates LayoutWidth correctly with fixed scaling and string', () => { + const vk = createVisualKeyboard(true); + vk['_width'] = "100px"; + assert.equal(vk.layoutWidth.val, 0); + }); + + }); +}); From 6402da52aaa216e7802bdf2305a0ed3f4be58d39 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Thu, 4 Sep 2025 14:02:30 -0400 Subject: [PATCH 08/52] auto: increment master version to 19.0.114 Test-bot: skip Build-bot: skip --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index b9d82e41ff..769d8ddd22 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 19.0.113 alpha 2025-09-04 + +* fix(android): Update KMSample1 layout when changing orientation (#14678) + ## 19.0.112 alpha 2025-09-03 * fix(developer): add clear messages about validation failures to New LM Project Parameters form (#14659) diff --git a/VERSION.md b/VERSION.md index 8b442ffb93..668434973c 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.113 \ No newline at end of file +19.0.114 \ No newline at end of file From f4640083265c1575365c4598f915e919a7467f3a Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 5 Sep 2025 09:35:50 +0200 Subject: [PATCH 09/52] fix(web): add check to `internalHeight` The same error could potentially happen in `internalHeight`, so we add the check for NaN there as well. Addresses code review comments. --- web/src/engine/osk/src/visualKeyboard.ts | 4 +- .../engine/osk/visualKeyboard.tests.ts | 77 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/web/src/engine/osk/src/visualKeyboard.ts b/web/src/engine/osk/src/visualKeyboard.ts index 9c2bef3609..000b491bf6 100644 --- a/web/src/engine/osk/src/visualKeyboard.ts +++ b/web/src/engine/osk/src/visualKeyboard.ts @@ -822,7 +822,9 @@ export default class VisualKeyboard extends EventEmitter implements Ke // Touch OSKs may apply internal padding to prevent row cropping at the edges. // ... why not precompute both, rather than recalculate each time? // - appears to contribute to layout reflow costs on layer swaps! - return ParsedLengthStyle.inPixels(this.layoutHeight.val - this._borderWidth * 2 - this.layerGroup.verticalPadding); + let baseHeight = this.layoutHeight.val - this._borderWidth * 2 - this.layerGroup.verticalPadding; + baseHeight = (isNaN(baseHeight) || baseHeight < 0) ? 0 : baseHeight; + return ParsedLengthStyle.inPixels(baseHeight); } else { return ParsedLengthStyle.forScalar(1); } diff --git a/web/src/test/auto/headless/engine/osk/visualKeyboard.tests.ts b/web/src/test/auto/headless/engine/osk/visualKeyboard.tests.ts index d6aa132ac2..991c4be98b 100644 --- a/web/src/test/auto/headless/engine/osk/visualKeyboard.tests.ts +++ b/web/src/test/auto/headless/engine/osk/visualKeyboard.tests.ts @@ -103,6 +103,12 @@ describe('VisualKeyboard', () => { assert.equal(vk.layoutHeight.val, 0); }); + it('ignores invalid borderWidth for LayoutHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = 100; + vk['_borderWidth'] = "3px"; + assert.equal(vk.layoutHeight.val, 0); + }); }); describe('LayoutWidth', () => { @@ -160,5 +166,76 @@ describe('VisualKeyboard', () => { assert.equal(vk.layoutWidth.val, 0); }); + it('ignores invalid borderWidth for LayoutWidth', () => { + const vk = createVisualKeyboard(); + vk['_width'] = 100; + vk['_borderWidth'] = "3px"; + assert.equal(vk.layoutWidth.val, 0); + }); + + }); + + describe('InternalHeight', () => { + it('calculates InternalHeight correctly for number', () => { + const vk = createVisualKeyboard(); + vk['_height'] = 100; + assert.equal(vk.internalHeight.val, 88); + }); + + it('calculates InternalHeight correctly for number string', () => { + const vk = createVisualKeyboard(); + vk['_height'] = "100"; + assert.equal(vk.internalHeight.val, 88); + }); + + it('accepts small heights for InternalHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = "5"; + assert.equal(vk.internalHeight.val, 0); + }); + + it('ignores non-number string for InternalHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = "100px"; + assert.equal(vk.internalHeight.val, 0); + }); + + it('ignores invalid string for InternalHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = "invalid"; + assert.equal(vk.internalHeight.val, 0); + }); + + it('ignores undefined value for InternalHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = undefined; + assert.equal(vk.internalHeight.val, 0); + }); + + it('ignores null value for InternalHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = null; + assert.equal(vk.internalHeight.val, 0); + }); + + it('calculates InternalHeight correctly with fixed scaling', () => { + const vk = createVisualKeyboard(true); + vk['_height'] = 100; + assert.equal(vk.internalHeight.val, 88); + }); + + it('calculates InternalHeight correctly with fixed scaling and string', () => { + const vk = createVisualKeyboard(true); + vk['_height'] = "100px"; + assert.equal(vk.internalHeight.val, 0); + }); + + it('ignores invalid borderWidth for InternalHeight', () => { + const vk = createVisualKeyboard(); + vk['_height'] = 100; + vk['_borderWidth'] = "3px"; + assert.equal(vk.internalHeight.val, 0); + }); + }); }); From 5c9e4f2c08de15e30b506675f40304cfc272815a Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 5 Sep 2025 10:28:35 +0200 Subject: [PATCH 10/52] maint(linux): remove obsolete Makefile All commands from `linux/Makefile` are now available elsewhere and not used anymore. The one exception was `make tmpsources` - this change moves the code to the TC build script. Test-bot: skip --- linux/Makefile | 49 ------------------- resources/teamcity/linux/keyman-linux-test.sh | 2 +- 2 files changed, 1 insertion(+), 50 deletions(-) delete mode 100644 linux/Makefile diff --git a/linux/Makefile b/linux/Makefile deleted file mode 100644 index 0df264e43d..0000000000 --- a/linux/Makefile +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/make - -default: # builds only - BUILDONLY="yes" ./scripts/build.sh - -clean: - rm -rf build-* - -reconf: clean - ./scripts/reconf.sh - -configure: - CONFIGUREONLY="yes" ./scripts/build.sh - -fullbuild: # configure and build - ./scripts/build.sh - -tmpinstall: reconf - INSTALLDIR="/tmp/keyman" ./scripts/build.sh - INSTALLDIR="/tmp/keyman" ./scripts/install.sh - -install: # run as sudo - SUDOINSTALL="yes" ./scripts/install.sh - -uninstall: # run as sudo - SUDOINSTALL="uninstall" ./scripts/install.sh - -sources: - ./scripts/dist.sh - -tmpsources: - PKG_CONFIG_PATH="../../core/build/arch/release/meson-private" ./scripts/dist.sh - -origdist: - ./scripts/dist.sh origdist - -sourcepackage: reconf origdist - ./scripts/deb.sh - -nightlydist: reconf sources - echo "Built nightly source release in dist/" - -check: - cd keyman-config && ./run-tests.sh - cd ibus-keyman && ./build.sh test - -tmpcheck: check - -tests: check diff --git a/resources/teamcity/linux/keyman-linux-test.sh b/resources/teamcity/linux/keyman-linux-test.sh index 950750b255..d89a57a75e 100755 --- a/resources/teamcity/linux/keyman-linux-test.sh +++ b/resources/teamcity/linux/keyman-linux-test.sh @@ -33,7 +33,7 @@ function make_source_tarball_action() { if builder_is_ci_build_level_release; then builder_echo start "make source tarball" "Make source tarball" rm -rf dist - make tmpsources + PKG_CONFIG_PATH="${KEYMAN_ROOT}/core/build/arch/release/meson-private" "${KEYMAN_ROOT}/linux/scripts/dist.sh" builder_echo end "make source tarball" success "Finished making source tarball" else builder_echo "Skipping source tarball creation - not release build level" From 1becb87547ffa729d1ae56bad95878e35a3b4be3 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 5 Sep 2025 10:45:58 +0200 Subject: [PATCH 11/52] maint(resources): honor configure comment character in commit message Git allows to specify a comment character different from the default `#`. This is helpful when using markdown in commit messages, especially when adding a user test section that has to start with `# User testing` in order for the test bot to detect it. This change modifies the `prepare-commit-msg` hook and adds a check for a configured `core.commentString` or `core.commentChar`. If both are not set it falls back to the default `#` character. You can set a different comment character with `git config core.commentChar //`. Build-bot: skip Test-bot: skip --- resources/git-hooks/prepare-commit-msg | 29 ++++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/resources/git-hooks/prepare-commit-msg b/resources/git-hooks/prepare-commit-msg index 4fd7bdffea..0db32abff3 100755 --- a/resources/git-hooks/prepare-commit-msg +++ b/resources/git-hooks/prepare-commit-msg @@ -114,18 +114,25 @@ function prepend_scope() { fi fi + commentchar="#" + if [[ -n "$(git config core.commentString)" ]]; then + commentchar="$(git config core.commentString)" + elif [[ -n "$(git config core.commentChar)" ]]; then + commentchar="$(git config core.commentChar)" + fi + postfix=$( cat < -# - Use imperative, present tense ('attach' not 'attaches', 'attached' etc) -# - Don't include a period at the end of the title -# - Always include a blank line before trailers -# - More: https://github.com/keymanapp/keyman/wiki/Pull-Request-and-Commit-workflow-notes +\n${commentchar} Keyman Conventional Commit suggestions: +${commentchar} +${commentchar} - Link to a Sentry issue with git trailer: +${commentchar} Fixes: _MODULE_-_ID_ +${commentchar} - Give credit to co-authors: +${commentchar} Co-authored-by: _Name_ <_email_> +${commentchar} - Use imperative, present tense ('attach' not 'attaches', 'attached' etc) +${commentchar} - Don't include a period at the end of the title +${commentchar} - Always include a blank line before trailers +${commentchar} - More: https://github.com/keymanapp/keyman/wiki/Pull-Request-and-Commit-workflow-notes EOF ) @@ -170,4 +177,4 @@ case $COMMIT_SOURCE in # Known possibilities: message (-m), template (-t) exit 0 ;; -esac \ No newline at end of file +esac From a9597b58da97da3a5feb846e58204187a8be2202 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 5 Sep 2025 18:21:46 +0200 Subject: [PATCH 12/52] refactor(resources): remove shellcheck warnings --- resources/build/ci/trigger-builds.inc.sh | 62 +++++----- .../teamcity/triggers/trigger-test-builds.sh | 112 +++++++++--------- 2 files changed, 89 insertions(+), 85 deletions(-) diff --git a/resources/build/ci/trigger-builds.inc.sh b/resources/build/ci/trigger-builds.inc.sh index 0d54971b15..c5df3d30a9 100644 --- a/resources/build/ci/trigger-builds.inc.sh +++ b/resources/build/ci/trigger-builds.inc.sh @@ -96,19 +96,20 @@ function triggerGitHubActionsBuild() { local GITHUB_SERVER=https://api.github.com/repos/keymanapp/keyman - if [ "${action:-""}" == "commit" ]; then + if [[ "${action:-""}" == "commit" ]]; then # This will only be true if we created and pushed a tag - GIT_BUILD_SHA="$(git rev-parse "refs/tags/release@$KEYMAN_VERSION_WITH_TAG")" + GIT_BUILD_SHA="$(git rev-parse "refs/tags/release@${KEYMAN_VERSION_WITH_TAG}")" GIT_BASE_REF="$(git rev-parse "${GIT_BUILD_SHA}^")" GIT_EVENT_TYPE="${GITHUB_ACTION}: release@${KEYMAN_VERSION_WITH_TAG}" - elif [[ $GIT_BRANCH =~ ^[0-9]+$ ]]; then + elif [[ ${GIT_BRANCH} =~ ^[0-9]+$ ]]; then # pull request - local JSON=$(call_curl "${GITHUB_SERVER}/pulls/${GIT_BRANCH}" --header "Authorization: token $GITHUB_TOKEN") - GIT_BUILD_SHA="$(echo "$JSON" | $JQ -r '.head.sha')" + local JSON + JSON=$(call_curl "${GITHUB_SERVER}/pulls/${GIT_BRANCH}" --header "Authorization: token ${GITHUB_TOKEN}") + GIT_BUILD_SHA="$(echo "${JSON}" | ${JQ} -r '.head.sha')" GIT_EVENT_TYPE="${GITHUB_ACTION}: PR #${GIT_BRANCH}" - GIT_USER="$(echo "$JSON" | $JQ -r '.user.login')" - GIT_BASE_BRANCH="$(echo "$JSON" | $JQ -r '.base.ref')" - GIT_BASE_REF="$(echo "$JSON" | $JQ -r '.base.sha')" + GIT_USER="$(echo "${JSON}" | ${JQ} -r '.user.login')" + GIT_BASE_BRANCH="$(echo "${JSON}" | ${JQ} -r '.base.ref')" + GIT_BASE_REF="$(echo "${JSON}" | ${JQ} -r '.base.sha')" GIT_BRANCH="PR-${GIT_BRANCH}" else # another branch: stable-x.y, beta, or master @@ -118,20 +119,20 @@ function triggerGitHubActionsBuild() { fi local DATA="{ - \"event_type\": \"$GIT_EVENT_TYPE\", \ + \"event_type\": \"${GIT_EVENT_TYPE}\", \ \"client_payload\": { \ - \"buildSha\": \"$GIT_BUILD_SHA\", \ - \"branch\": \"$GIT_BRANCH\", \ - \"baseBranch\": \"$GIT_BASE_BRANCH\", \ - \"baseRef\": \"$GIT_BASE_REF\", \ - \"user\": \"$GIT_USER\", \ - \"isTestBuild\": \"$IS_TEST_BUILD\", \ - \"buildLevel\": \"$BUILD_LEVEL\" \ + \"buildSha\": \"${GIT_BUILD_SHA}\", \ + \"branch\": \"${GIT_BRANCH}\", \ + \"baseBranch\": \"${GIT_BASE_BRANCH}\", \ + \"baseRef\": \"${GIT_BASE_REF}\", \ + \"user\": \"${GIT_USER}\", \ + \"isTestBuild\": \"${IS_TEST_BUILD}\", \ + \"buildLevel\": \"${BUILD_LEVEL}\" \ }}" - echo "GitHub Action Data: $DATA" + echo "GitHub Action Data: ${DATA}" - if [[ "$GIT_BUILD_SHA" == null ]]; then + if [[ "${GIT_BUILD_SHA}" == null ]]; then echo "Invalid GIT_BUILD_SHA" exit 1 fi @@ -141,8 +142,8 @@ function triggerGitHubActionsBuild() { call_curl "${GITHUB_SERVER}/dispatches" \ --request POST \ --header "Accept: application/vnd.github+json" \ - --header "Authorization: token $GITHUB_TOKEN" \ - --data "$DATA" + --header "Authorization: token ${GITHUB_TOKEN}" \ + --data "${DATA}" echo } @@ -157,31 +158,32 @@ function call_curl() { local curl_result local http_code - local output_file=$(mktemp) + local output_file + output_file=$(mktemp) set +e if [[ $# -gt 0 ]]; then - http_code=$(curl --silent --output $output_file --no-progress-meter --write-out "%{http_code}" "$@" "$url") + http_code=$(curl --silent --output "${output_file}" --no-progress-meter --write-out "%{http_code}" "$@" "${url}") else - http_code=$(curl --silent --output $output_file --no-progress-meter --write-out "%{http_code}" "$url") + http_code=$(curl --silent --output "${output_file}" --no-progress-meter --write-out "%{http_code}" "${url}") fi curl_result=$? if [[ ${curl_result} != 0 ]]; then echo "curl call to '${url}' failed with code '${curl_result}'" >& 2 - >&2 cat $output_file - rm -f $output_file - exit $curl_result + >&2 cat "${output_file}" + rm -f "${output_file}" + exit ${curl_result} fi if [[ ${http_code} -lt 200 || ${http_code} -gt 299 ]] ; then echo "curl call to '${url}' failed with HTTP error code '${http_code}'" >& 2 - >&2 cat $output_file - rm -f $output_file + >&2 cat "${output_file}" + rm -f "${output_file}" exit 22 fi set -e - cat $output_file - rm -f $output_file + cat "${output_file}" + rm -f "${output_file}" } diff --git a/resources/teamcity/triggers/trigger-test-builds.sh b/resources/teamcity/triggers/trigger-test-builds.sh index 635f133687..ce1af8e3ee 100755 --- a/resources/teamcity/triggers/trigger-test-builds.sh +++ b/resources/teamcity/triggers/trigger-test-builds.sh @@ -12,6 +12,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../resources/build/builder-full.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE +# shellcheck disable=SC2154 . "${KEYMAN_ROOT}/resources/build/ci/trigger-definitions.inc.sh" . "${KEYMAN_ROOT}/resources/build/ci/trigger-builds.inc.sh" . "${KEYMAN_ROOT}/resources/build/ci/trigger-build-bot.inc.sh" @@ -29,15 +30,15 @@ if ! builder_has_option --branch; then builder_die "--branch parameter is required" fi -if [[ ! "$PRNUM" =~ ^([0-9]+|master|beta|stable-[0-9]+\.[0-9]+)$ ]]; then - builder_die "Invalid parameter --branch '$PRNUM'; expected 'master', 'beta', 'stable-x.y', or PR number" +if [[ ! "${PRNUM}" =~ ^([0-9]+|master|beta|stable-[0-9]+\.[0-9]+)$ ]]; then + builder_die "Invalid parameter --branch '${PRNUM}'; expected 'master', 'beta', 'stable-x.y', or PR number" fi # # Debug logging # function debug_echo() { - if [ ! -z ${DEBUG:-} ]; then + if [[ ! -z ${DEBUG:-} ]]; then echo "DEBUG: $1" fi true @@ -59,44 +60,45 @@ function triggerTestBuilds() { # Cancel any already running builds for this branch if builder_has_option --dry-run; then - builder_echo "DRY RUN: cancel current builds for $branch" + builder_echo "DRY RUN: cancel current builds for ${branch}" else - node "$KEYMAN_ROOT/resources/build/ci/cancel-builds/cancel-test-builds.mjs" "$branch" "$TEAMCITY_TOKEN" + node "${KEYMAN_ROOT}/resources/build/ci/cancel-builds/cancel-test-builds.mjs" "${branch}" "${TEAMCITY_TOKEN}" fi for platform in "${!platforms[@]}"; do - local platformBuildLevel=${platforms[$platform]} - if [[ $platformBuildLevel == skip ]]; then - builder_echo heading "$platform: skipping build" + local platformBuildLevel=${platforms[${platform}]} + if [[ ${platformBuildLevel} == skip ]]; then + builder_echo heading "${platform}: skipping build" continue fi - builder_echo heading "$platform: checking for git changes" - eval test_builds='(${'bc_test_$platform'[@]})' + builder_echo heading "${platform}: checking for git changes" + eval test_builds='(${'bc_test_${platform}'[@]})' + # shellcheck disable=SC2154 for test_build in "${test_builds[@]}"; do - if [[ $test_build == "" ]]; then continue; fi + if [[ ${test_build} == "" ]]; then continue; fi found_build=true - if [ "${test_build:(-7)}" == "_GitHub" ]; then + if [[ "${test_build:(-7)}" == "_GitHub" ]]; then local job=${test_build%_GitHub} if builder_has_option --dry-run; then - builder_echo "DRY RUN: Triggering GitHub action build $job/$branch, level = $platformBuildLevel" + builder_echo "DRY RUN: Triggering GitHub action build ${job}/${branch}, level = ${platformBuildLevel}" else - builder_echo "Triggering GitHub action build $job/$branch, level = $platformBuildLevel" - triggerGitHubActionsBuild true $platformBuildLevel "$job" "$branch" + builder_echo "Triggering GitHub action build ${job}/${branch}, level = ${platformBuildLevel}" + triggerGitHubActionsBuild true "${platformBuildLevel}" "${job}" "${branch}" fi else if builder_has_option --dry-run; then - builder_echo "DRY RUN: Triggering build configuration $test_build on teamcity for $branch, level = $platformBuildLevel" + builder_echo "DRY RUN: Triggering build configuration ${test_build} on teamcity for ${branch}, level = ${platformBuildLevel}" else - builder_echo "Triggering build configuration $test_build on teamcity for $branch, level = $platformBuildLevel" - triggerTeamCityBuild true $platformBuildLevel "$test_build" "$vcs_test" "$branch" + builder_echo "Triggering build configuration ${test_build} on teamcity for ${branch}, level = ${platformBuildLevel}" + triggerTeamCityBuild true "${platformBuildLevel}" "${test_build}" "${vcs_test}" "${branch}" fi fi done done - if [[ $found_build == false ]]; then + if [[ ${found_build} == false ]]; then postSkippedBuildsStatusResult fi } @@ -111,7 +113,7 @@ function postSkippedBuildsStatusResult() { curl --silent --write-out '\n' \ --request POST \ --header "Accept: application/vnd.github+json" \ - --header "Authorization: token $GITHUB_TOKEN" \ + --header "Authorization: token ${GITHUB_TOKEN}" \ --data '{"state":"success","description":"Skipping since no platform builds necessary","context":"Test Build (Keyman)"}' \ "https://api.github.com/repos/keymanapp/keyman/statuses/${BUILD_VCS_NUMBER}" fi @@ -122,10 +124,10 @@ function postSkippedBuildsStatusResult() { # branches as well as pull requests # -if [[ ! "$PRNUM" =~ ^[[:digit:]]+$ ]]; then +if [[ ! "${PRNUM}" =~ ^[[:digit:]]+$ ]]; then # branch name is 'master', 'beta', or 'stable-x.y' - builder_echo "Branch $PRNUM needs to pass tests on all platforms." - triggerTestBuilds main_branch_platform_build_levels $PRNUM + builder_echo "Branch ${PRNUM} needs to pass tests on all platforms." + triggerTestBuilds main_branch_platform_build_levels "${PRNUM}" exit 0 fi @@ -136,20 +138,20 @@ fi # targeted by the PR other than to ask GitHub, anyway.) # -builder_echo grey "# Get information about pull request #$PRNUM from GitHub" -prinfo=`curl -s -H "User-Agent: @keymanapp" -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/repos/keymanapp/keyman/pulls/$PRNUM` -prbase=`echo ${prinfo} | "$JQ" -r '.base.ref'` -prhead=`echo ${prinfo} | "$JQ" -r '.head.ref'` -prbaserepo=`echo ${prinfo} | "$JQ" -r '.base.repo.html_url'` -prheadrepo=`echo ${prinfo} | "$JQ" -r '.head.repo.html_url'` +builder_echo grey "# Get information about pull request #${PRNUM} from GitHub" +prinfo=$(curl -s -H "User-Agent: @keymanapp" -H "Authorization: Bearer ${GITHUB_TOKEN}" "https://api.github.com/repos/keymanapp/keyman/pulls/${PRNUM}") +prbase=$(echo "${prinfo}" | "${JQ}" -r '.base.ref') +prhead=$(echo "${prinfo}" | "${JQ}" -r '.head.ref') +prbaserepo=$(echo "${prinfo}" | "${JQ}" -r '.base.repo.html_url') +prheadrepo=$(echo "${prinfo}" | "${JQ}" -r '.head.repo.html_url') -debug_echo "PRNUM: $PRNUM" -debug_echo "BASE: $prbase ($prbaserepo)" -debug_echo "HEAD: $prhead ($prheadrepo)" +debug_echo "PRNUM: ${PRNUM}" +debug_echo "BASE: ${prbase} (${prbaserepo})" +debug_echo "HEAD: ${prhead} (${prheadrepo})" prremote=origin -if [ "$prbaserepo" != "https://github.com/keymanapp/keyman" ]; then - echo "Unknown base repository $prbaserepo." +if [[ "${prbaserepo}" != "https://github.com/keymanapp/keyman" ]]; then + echo "Unknown base repository ${prbaserepo}." exit 1 fi @@ -160,7 +162,7 @@ fi # (Ensure we are within the repository before running git calls) builder_echo grey "# Ensure our local branch is up to date" -pushd "$KEYMAN_ROOT" > /dev/null +pushd "${KEYMAN_ROOT}" > /dev/null git fetch origin > /dev/null # @@ -169,10 +171,10 @@ git fetch origin > /dev/null # on untrusted forks. # -if [ "$prheadrepo" != "https://github.com/keymanapp/keyman" ]; then +if [[ "${prheadrepo}" != "https://github.com/keymanapp/keyman" ]]; then prremote=external-rrtb - git remote add "$prremote" "$prheadrepo" - git fetch "$prremote" > /dev/null + git remote add "${prremote}" "${prheadrepo}" + git fetch "${prremote}" > /dev/null fi # @@ -181,18 +183,18 @@ fi # builder_echo grey "# Get list of changed files in the pull request" -prfiles=`git diff --name-only "origin/$prbase"..."$prremote/$prhead" || ( if [ $? == 128 ]; then echo abort; else exit $?; fi )` -if [ "$prfiles" == "abort" ]; then +prfiles=$(git diff --name-only "origin/${prbase}...${prremote}/${prhead}" || ( if [[ $? == 128 ]]; then echo abort; else exit $?; fi )) +if [[ "${prfiles}" == "abort" ]]; then # Don't trigger any builds, exit with success - if [ "$prremote" != "origin" ]; then - git remote remove "$prremote" + if [[ "${prremote}" != "origin" ]]; then + git remote remove "${prremote}" fi - echo "Remote branch origin/$prhead has gone away; probably an automatic pull request. Skipping build." + echo "Remote branch origin/${prhead} has gone away; probably an automatic pull request. Skipping build." exit 0 fi -if [ "$prremote" != "origin" ]; then - git remote remove "$prremote" +if [[ "${prremote}" != "origin" ]]; then + git remote remove "${prremote}" fi debug_echo "Files found: ${prfiles[*]}" @@ -212,9 +214,9 @@ function find_platform_changes() { while IFS= read -r line; do # for each platform for platform in "${available_platforms[@]}"; do - if [[ ! " ${!build_platforms[@]} " =~ " $platform " ]]; then + if [[ ! " ${!build_platforms[*]} " =~ " ${platform} " ]]; then # Which platform are we watching? - eval watch='$'watch_$platform + eval watch="'$'watch_${platform}" # Add common patterns to the watch list watch="^(${platform}|(oem/[^/]+/${platform})|resources/((?!teamcity)|teamcity/(${platform}|includes))|${watch})" @@ -222,13 +224,13 @@ function find_platform_changes() { # (grep has a --perl-regexp option, but not the version on macOS) if perl -e 'exit($ARGV[0] =~ /$ARGV[1]/ ? 0 : 1)' "${line}" "${watch}"; then # By default, we'll build a 'release' test build for touched platforms - build_platforms[$platform]=$build_level_release + build_platforms[${platform}]=${build_level_release} fi fi done - done <<< "$prfiles" + done <<< "${prfiles}" - builder_echo blue "Default build platforms: ${!build_platforms[@]}" + builder_echo blue "Default build platforms: ${!build_platforms[*]}" } find_platform_changes @@ -238,7 +240,7 @@ find_platform_changes # This will modify the build_platforms array # -if [ "$prremote" == "origin" ]; then +if [[ "${prremote}" == "origin" ]]; then # We only accept Build-bot commands on trusted local origin PRs cd "${KEYMAN_ROOT}/resources/build/ci/github" npm ci @@ -249,9 +251,9 @@ if [ "$prremote" == "origin" ]; then NODE_WRAPPER=node fi # Note: we may move all of this into javascript at some point! - prcommits=$(${NODE_WRAPPER} api-pull-commits.mjs $GITHUB_TOKEN $PRNUM) - test_bot_check_pr_body "$PRNUM" "$prinfo" - build_bot_check_messages $PRNUM "$prinfo" "$prcommits" + prcommits=$(${NODE_WRAPPER} api-pull-commits.mjs "${GITHUB_TOKEN}" "${PRNUM}") + test_bot_check_pr_body "${PRNUM}" "${prinfo}" + build_bot_check_messages "${PRNUM}" "${prinfo}" "${prcommits}" fi # @@ -260,7 +262,7 @@ fi if [[ ! -z "${build_platforms[*]:-}" ]] && (( ${#build_platforms[@]} > 0)); then builder_echo heading "Start test builds" - triggerTestBuilds build_platforms $PRNUM + triggerTestBuilds build_platforms "${PRNUM}" else builder_echo heading "No builds to start" postSkippedBuildsStatusResult From 612cd01743ec5ff86286e1806d5b86a9265cd2ec Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 5 Sep 2025 18:26:08 +0200 Subject: [PATCH 13/52] maint(linux): allow to skip API check This change adds an option to skip the API checks during a build. This can be done by adding the trailer `Keyman-Api-Check: skip` to the PR description. Fixes: #10454 Test-bot: skip Keyman-Api-Check: skip --- .github/workflows/api-verification.yml | 10 +++++-- .github/workflows/deb-packaging.yml | 2 ++ docs/core-api-verification.md | 11 ++++++++ resources/build/ci/trigger-build-bot.inc.sh | 28 +++++++++++++++++++ resources/build/ci/trigger-builds.inc.sh | 5 +++- .../test/build-bot/trigger-build-bot.test.sh | 14 ++++++++++ .../teamcity/triggers/trigger-test-builds.sh | 5 +++- 7 files changed, 70 insertions(+), 5 deletions(-) diff --git a/.github/workflows/api-verification.yml b/.github/workflows/api-verification.yml index e8cc9aa95a..af6bc682dc 100644 --- a/.github/workflows/api-verification.yml +++ b/.github/workflows/api-verification.yml @@ -23,6 +23,7 @@ jobs: GIT_BRANCH: ${{ steps.environment_step.outputs.GIT_BRANCH }} GIT_BASE_BRANCH: ${{ steps.environment_step.outputs.GIT_BASE_BRANCH }} GIT_USER: ${{ steps.environment_step.outputs.GIT_USER }} + SKIP_API_CHECK: ${{ steps.environment_step.outputs.SKIP_API_CHECK }} steps: - name: Restore artifacts @@ -40,7 +41,7 @@ jobs: - name: Set pending status on PR builds id: set_status - if: steps.environment_step.outputs.IS_TEST_BUILD == 'true' + if: steps.environment_step.outputs.IS_TEST_BUILD == 'true' && steps.environment_step.outputs.SKIP_API_CHECK != 'true' shell: bash run: | gh api \ @@ -53,6 +54,7 @@ jobs: -f context="$STATUS_CONTEXT" - name: Checkout + if: steps.environment_step.outputs.SKIP_API_CHECK != 'true' uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 with: ref: '${{ steps.environment_step.outputs.GIT_SHA }}' @@ -61,11 +63,13 @@ jobs: path: ${{ github.workspace }}/keyman/ - name: Install devscripts + if: steps.environment_step.outputs.SKIP_API_CHECK != 'true' uses: ./keyman/.github/actions/apt-install with: packages: devscripts equivs - name: "Verify API for libkeymancore*.so (${{ steps.environment_step.outputs.GIT_BRANCH }}, branch ${{ steps.environment_step.outputs.GIT_BASE_BRANCH }}, by ${{ steps.environment_step.outputs.GIT_USER }})" + if: steps.environment_step.outputs.SKIP_API_CHECK != 'true' run: | echo "Verify API for libkeymancore*.so (${{ steps.environment_step.outputs.GIT_BRANCH }}, branch ${{ steps.environment_step.outputs.GIT_BASE_BRANCH }}, by ${{ steps.environment_step.outputs.GIT_USER }}):" >> $GITHUB_STEP_SUMMARY @@ -79,17 +83,17 @@ jobs: verify 2>> $GITHUB_STEP_SUMMARY - name: Archive .symbols file + if: steps.environment_step.outputs.SKIP_API_CHECK != 'true' && always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: libkeymancore.symbols path: ${{ github.workspace }}/keyman/linux/debian/tmp/DEBIAN/symbols - if: always() set_status: name: Set result status on PR builds needs: [api_verification] runs-on: ubuntu-latest - if: ${{ always() && needs.api_verification.outputs.IS_TEST_BUILD == 'true' }} + if: ${{ always() && needs.api_verification.outputs.IS_TEST_BUILD == 'true' && needs.api_verification.outputs.SKIP_API_CHECK != 'true' }} steps: - name: Set success if: needs.api_verification.result == 'success' diff --git a/.github/workflows/deb-packaging.yml b/.github/workflows/deb-packaging.yml index a40fcb7259..472adf91b0 100644 --- a/.github/workflows/deb-packaging.yml +++ b/.github/workflows/deb-packaging.yml @@ -23,6 +23,7 @@ on: # baseRef: The ref of the previous commit. For a PR the same as `baseBranch`. # user: The user that triggered the build or created the PR # isTestBuild: false for Releases, otherwise true +# skipApiCheck: true to skip the API check, otherwise false env: COLOR_GREEN: "\e[32m" @@ -332,6 +333,7 @@ jobs: echo "GIT_BRANCH=${{ github.event.client_payload.branch }}" >> artifacts/env echo "GIT_BASE_BRANCH=${{ github.event.client_payload.baseBranch }}" >> artifacts/env echo "GIT_USER=${{ github.event.client_payload.user }}" >> artifacts/env + echo "SKIP_API_CHECK=${{ github.event.client_payload.skipApiCheck }}" >> artifacts/env - name: Cache artifacts uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 diff --git a/docs/core-api-verification.md b/docs/core-api-verification.md index fc3e0f629d..0bf266676f 100644 --- a/docs/core-api-verification.md +++ b/docs/core-api-verification.md @@ -99,6 +99,17 @@ the API version number in `CORE_API_VERSION.md`. If the API version numbers in `CORE_API_VERSION.md` and in `libkeymancore.symbols` don't match the API verification will fail. +## Skipping the API check + +Sometimes we have a PR that intentionally does not change the API version +number. To skip the API checks in this case, the trailer + +```text +Keyman-Api-Check: skip +``` + +can be added to the PR description. + ## How this works `.github/workflows/api-verification.yml` contains a `Verify API` step diff --git a/resources/build/ci/trigger-build-bot.inc.sh b/resources/build/ci/trigger-build-bot.inc.sh index f7cdc815f8..fb6ed4c34c 100644 --- a/resources/build/ci/trigger-build-bot.inc.sh +++ b/resources/build/ci/trigger-build-bot.inc.sh @@ -39,6 +39,34 @@ function test_bot_check_pr_body() { fi } +# +# Check the Keyman-Api-Check command whether or not to run API checks. +# Any value other than 'ignore' or no Keyman-Api-Check command will run +# the API checks. +# +# Parameters: +# 1: PR number (not currently used) +# 2: PR JSON data from api.github.com/repos/keymanapp/keyman/pulls/# +# +function is_skip_keyman_api_check() { + local PRNUM=$1 + local prinfo="$2" + local prbody prApiCheck + + set -o noglob + IFS=$'\n' + prbody="$(echo "${prinfo}" | "${JQ}" -r '.body')" + prApiCheck="$(echo "${prbody}" | grep 'Keyman-Api-Check:' | cut -d: -f 2 - | tr -d '[:space:]')" + unset IFS + set +o noglob + + if [[ "${prApiCheck}" == "skip" ]]; then + echo "true" + else + echo "false" + fi +} + # # Check PR commit messages for Build-bot commands. Later commands override # earlier ones. Also checks PR body for any overriding commands. Note, only diff --git a/resources/build/ci/trigger-builds.inc.sh b/resources/build/ci/trigger-builds.inc.sh index c5df3d30a9..d1110a19a1 100644 --- a/resources/build/ci/trigger-builds.inc.sh +++ b/resources/build/ci/trigger-builds.inc.sh @@ -84,12 +84,14 @@ function triggerTeamCityBuild() { # 2: 'skip' - don't run build; 'build' - run build and unit tests; 'test' - run build, unit tests, and deploy # 3: Action name # 4: branch name in git +# 5: 'true' to skip API checks, 'false' to run them (optional, default 'false') # function triggerGitHubActionsBuild() { local IS_TEST_BUILD="$1" local BUILD_LEVEL="$2" local GITHUB_ACTION="$3" local GIT_BRANCH="$4" + local SKIP_API_CHECK="${5:-"false"}" local GIT_BASE_BRANCH="${GIT_BRANCH}" local GIT_USER="keyman-server" local GIT_BUILD_SHA GIT_BASE_REF JSON @@ -127,7 +129,8 @@ function triggerGitHubActionsBuild() { \"baseRef\": \"${GIT_BASE_REF}\", \ \"user\": \"${GIT_USER}\", \ \"isTestBuild\": \"${IS_TEST_BUILD}\", \ - \"buildLevel\": \"${BUILD_LEVEL}\" \ + \"buildLevel\": \"${BUILD_LEVEL}\", \ + \"skipApiCheck\": \"${SKIP_API_CHECK}\" \ }}" echo "GitHub Action Data: ${DATA}" diff --git a/resources/build/test/build-bot/trigger-build-bot.test.sh b/resources/build/test/build-bot/trigger-build-bot.test.sh index a39b8361ae..ce37c718a9 100755 --- a/resources/build/test/build-bot/trigger-build-bot.test.sh +++ b/resources/build/test/build-bot/trigger-build-bot.test.sh @@ -216,7 +216,21 @@ _do_test_test_bot_check_pr_body() { #---------------------------------------------------------------------------------------------------- +test_is_skip_keyman_api_check() { + builder_echo start test_is_skip_keyman_api_check 'START TEST: test_is_skip_keyman_api_check' + result=$(is_skip_keyman_api_check 1 '{ "body": "Keyman-Api-Check: skip" }') + assert-equal "${result}" "true" "PR #1: 'Keyman-Api-Check: skip' should return true" + result=$(is_skip_keyman_api_check 2 '{ "body": "PR message" }') + assert-equal "${result}" "false" "PR #2: omitted 'Keyman-Api-Check' should return false" + result=$(is_skip_keyman_api_check 3 '{ "body": "Keyman-Api-Check: invalid" }') + assert-equal "${result}" "false" "PR #3: invalid data for 'Keyman-Api-Check' should return false" + builder_echo end test_is_skip_keyman_api_check success 'SUCCESS: test_is_skip_keyman_api_check' +} + +#---------------------------------------------------------------------------------------------------- + test_build_bot_verify_platforms test_build_bot_update_commands test_build_bot_check_messages test_test_bot_check_pr_body +test_is_skip_keyman_api_check diff --git a/resources/teamcity/triggers/trigger-test-builds.sh b/resources/teamcity/triggers/trigger-test-builds.sh index ce1af8e3ee..52a9ffc1da 100755 --- a/resources/teamcity/triggers/trigger-test-builds.sh +++ b/resources/teamcity/triggers/trigger-test-builds.sh @@ -85,7 +85,7 @@ function triggerTestBuilds() { builder_echo "DRY RUN: Triggering GitHub action build ${job}/${branch}, level = ${platformBuildLevel}" else builder_echo "Triggering GitHub action build ${job}/${branch}, level = ${platformBuildLevel}" - triggerGitHubActionsBuild true "${platformBuildLevel}" "${job}" "${branch}" + triggerGitHubActionsBuild true "${platformBuildLevel}" "${job}" "${branch}" "${KEYMAN_API_CHECK_SKIP}" fi else if builder_has_option --dry-run; then @@ -240,6 +240,8 @@ find_platform_changes # This will modify the build_platforms array # +KEYMAN_API_CHECK_SKIP="false" + if [[ "${prremote}" == "origin" ]]; then # We only accept Build-bot commands on trusted local origin PRs cd "${KEYMAN_ROOT}/resources/build/ci/github" @@ -254,6 +256,7 @@ if [[ "${prremote}" == "origin" ]]; then prcommits=$(${NODE_WRAPPER} api-pull-commits.mjs "${GITHUB_TOKEN}" "${PRNUM}") test_bot_check_pr_body "${PRNUM}" "${prinfo}" build_bot_check_messages "${PRNUM}" "${prinfo}" "${prcommits}" + KEYMAN_API_CHECK_SKIP="$(is_skip_keyman_api_check "${PRNUM}" "${prinfo}")" fi # From db90fa21a7b88367e3282f193443c6cab266bce1 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 8 Sep 2025 09:44:53 +0200 Subject: [PATCH 14/52] maint(developer): use versioned filenames for PR artifacts Relates-to: #10521 Test-bot: skip Build-bot: skip release:developer --- developer/src/inst/build.sh | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/developer/src/inst/build.sh b/developer/src/inst/build.sh index 8b37048e1e..545626ea31 100755 --- a/developer/src/inst/build.sh +++ b/developer/src/inst/build.sh @@ -12,11 +12,21 @@ builder_describe "Installation files for Keyman Developer" \ @/common/windows/data \ clean configure build test publish +# For local builds, and for PR builds, we will use a filename including the tag +# but for release builds, we omit the tag for now. In the future, we may include +# the tag but that involves changing a number of other websites as dealing with +# the rename there too. +if builder_is_ci_release_build; then + FILENAME_VERSION="${KEYMAN_VERSION}" +else + FILENAME_VERSION="${KEYMAN_VERSION_WITH_TAG}" +fi + # NOTE: not using deps here because we will only do this in the 'publish' phase # after all other builds complete builder_describe_outputs \ - publish /developer/src/inst/keymandeveloper-${KEYMAN_VERSION}.exe + publish /developer/src/inst/keymandeveloper-${FILENAME_VERSION}.exe builder_parse "$@" @@ -78,7 +88,7 @@ function do_publish() { # Copy the installation archive # - copy-kmdev + copy_keyman_developer_to_artifacts builder_if_release_build_level verify-installer-signatures } @@ -87,8 +97,8 @@ function do_test() { verify-node-installer-version } -function copy-kmdev() { - builder_heading copy-kmdev +function copy-keymandeveloper-installer-to-artifacts() { + builder_heading copy-keymandeveloper-installer-to-artifacts mkdir -p "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}" @@ -96,7 +106,7 @@ function copy-kmdev() { make-kmc-install-zip cp -f "$DEVELOPER_ROOT/src/inst/keymandeveloper.msi" "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}/keymandeveloper.msi" - cp -f "$DEVELOPER_ROOT/src/inst/keymandeveloper-${KEYMAN_VERSION}.exe" "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}/keymandeveloper-${KEYMAN_VERSION}.exe" + cp -f "$DEVELOPER_ROOT/src/inst/keymandeveloper-${FILENAME_VERSION}.exe" "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}/keymandeveloper-${FILENAME_VERSION}.exe" } function verify-program-signatures() { @@ -234,8 +244,8 @@ function make-installer() { echo "MSIFileName=keymandeveloper.msi" >> setup.inf echo "Title=Keyman Developer ${KEYMAN_VERSION_WITH_TAG}" >>setup.inf add_zip_files setup.zip keymandeveloper.msi setup.inf - cat "$DEVELOPER_PROGRAM/setup.exe" setup.zip > "keymandeveloper-$KEYMAN_VERSION.exe" - wrap-signcode //d "Keyman Developer" "keymandeveloper-$KEYMAN_VERSION.exe" + cat "$DEVELOPER_PROGRAM/setup.exe" setup.zip > "keymandeveloper-$FILENAME_VERSION.exe" + wrap-signcode //d "Keyman Developer" "keymandeveloper-$FILENAME_VERSION.exe" # # Zip the files we distribute as part of the standalone kmc distro into release\$Version\kmcomp-$Version.zip @@ -251,7 +261,7 @@ function make-kmc-install-zip() { cd "${DEVELOPER_ROOT}/bin" # TODO: rename this to keyman-developer-cli-$Version.zip - local KMCOMP_ZIP="${DEVELOPER_ROOT}/release/${KEYMAN_VERSION}/kmcomp-${KEYMAN_VERSION}.zip" + local KMCOMP_ZIP="${DEVELOPER_ROOT}/release/${KEYMAN_VERSION}/kmcomp-${FILENAME_VERSION}.zip" add_zip_files "${KMCOMP_ZIP}" -q -r \ kmconvert.exe \ From e6d9d8c32abff76cd4f26900b2a0af9e748cdb79 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 8 Sep 2025 10:20:42 +0200 Subject: [PATCH 15/52] maint(windows): use versioned filenames for PR artifacts Relates-to: #10521 Test-bot: skip Build-bot: skip release:windows --- oem/firstvoices/windows/src/inst/build.sh | 14 ++++++++++++-- windows/src/desktop/inst/build.sh | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/oem/firstvoices/windows/src/inst/build.sh b/oem/firstvoices/windows/src/inst/build.sh index ac245b72aa..4fdba0b4b3 100755 --- a/oem/firstvoices/windows/src/inst/build.sh +++ b/oem/firstvoices/windows/src/inst/build.sh @@ -12,11 +12,21 @@ builder_describe "Installation files for FirstVoices Keyboards" \ @/common/windows/data \ clean configure build test publish +# For local builds, and for PR builds, we will use a filename including the tag +# but for release builds, we omit the tag for now. In the future, we may include +# the tag but that involves changing a number of other websites as dealing with +# the rename there too. +if builder_is_ci_release_build; then + FILENAME_VERSION="${KEYMAN_VERSION}" +else + FILENAME_VERSION="${KEYMAN_VERSION_WITH_TAG}" +fi + # NOTE: not using deps here because we will only do this in the 'publish' phase # after all other builds complete builder_if_release_build_level builder_describe_outputs \ - publish /windows/release/${KEYMAN_VERSION}/firstvoices-${KEYMAN_VERSION}.exe + publish /windows/release/${KEYMAN_VERSION}/firstvoices-${FILENAME_VERSION}.exe builder_parse "$@" @@ -103,7 +113,7 @@ function copy-installer() { mkdir -p "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}" cp firstvoices.msi "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/firstvoices.msi" - cp firstvoices.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/firstvoices-${KEYMAN_VERSION}.exe" + cp firstvoices.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/firstvoices-${FILENAME_VERSION}.exe" verify-installer-signatures } diff --git a/windows/src/desktop/inst/build.sh b/windows/src/desktop/inst/build.sh index aa581d5e04..4ae5914e3f 100755 --- a/windows/src/desktop/inst/build.sh +++ b/windows/src/desktop/inst/build.sh @@ -11,11 +11,21 @@ builder_describe "Installation files for Keyman for Windows" \ @/common/windows/data \ clean configure build test publish +# For local builds, and for PR builds, we will use a filename including the tag +# but for release builds, we omit the tag for now. In the future, we may include +# the tag but that involves changing a number of other websites as dealing with +# the rename there too. +if builder_is_ci_release_build; then + FILENAME_VERSION="${KEYMAN_VERSION}" +else + FILENAME_VERSION="${KEYMAN_VERSION_WITH_TAG}" +fi + # NOTE: not using deps here because we will only do this in the 'publish' phase # after all other builds complete builder_if_release_build_level builder_describe_outputs \ - publish /windows/release/${KEYMAN_VERSION}/keyman-${KEYMAN_VERSION}.exe + publish /windows/release/${KEYMAN_VERSION}/keyman-${FILENAME_VERSION}.exe builder_parse "$@" @@ -104,7 +114,7 @@ function copy-installer() { mkdir -p "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}" cp keymandesktop.msi "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/keymandesktop.msi" - cp keymandesktop.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/keyman-${KEYMAN_VERSION}.exe" + cp keymandesktop.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/keyman-${FILENAME_VERSION}.exe" cp "$WINDOWS_PROGRAM_APP/setup.exe" "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/setup.exe" verify-installer-signatures From 9ce0aed2ed4ac88a96a25a8a54aebde00379d278 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 8 Sep 2025 09:44:53 +0200 Subject: [PATCH 16/52] maint(developer): use versioned filenames for PR artifacts Relates-to: #10521 Test-bot: skip Build-bot: skip release:developer --- developer/src/inst/build.sh | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/developer/src/inst/build.sh b/developer/src/inst/build.sh index 8b37048e1e..50f0a5e081 100755 --- a/developer/src/inst/build.sh +++ b/developer/src/inst/build.sh @@ -12,11 +12,21 @@ builder_describe "Installation files for Keyman Developer" \ @/common/windows/data \ clean configure build test publish +# For local builds, and for PR builds, we will use a filename including the tag +# but for release builds, we omit the tag for now. In the future, we may include +# the tag but that involves changing a number of other websites as dealing with +# the rename there too. +if builder_is_ci_release_build; then + FILENAME_VERSION="${KEYMAN_VERSION}" +else + FILENAME_VERSION="${KEYMAN_VERSION_WITH_TAG}" +fi + # NOTE: not using deps here because we will only do this in the 'publish' phase # after all other builds complete builder_describe_outputs \ - publish /developer/src/inst/keymandeveloper-${KEYMAN_VERSION}.exe + publish /developer/src/inst/keymandeveloper-${FILENAME_VERSION}.exe builder_parse "$@" @@ -78,7 +88,7 @@ function do_publish() { # Copy the installation archive # - copy-kmdev + copy-keymandeveloper-installer-to-artifacts builder_if_release_build_level verify-installer-signatures } @@ -87,8 +97,8 @@ function do_test() { verify-node-installer-version } -function copy-kmdev() { - builder_heading copy-kmdev +function copy-keymandeveloper-installer-to-artifacts() { + builder_heading copy-keymandeveloper-installer-to-artifacts mkdir -p "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}" @@ -96,7 +106,7 @@ function copy-kmdev() { make-kmc-install-zip cp -f "$DEVELOPER_ROOT/src/inst/keymandeveloper.msi" "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}/keymandeveloper.msi" - cp -f "$DEVELOPER_ROOT/src/inst/keymandeveloper-${KEYMAN_VERSION}.exe" "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}/keymandeveloper-${KEYMAN_VERSION}.exe" + cp -f "$DEVELOPER_ROOT/src/inst/keymandeveloper-${FILENAME_VERSION}.exe" "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}/keymandeveloper-${FILENAME_VERSION}.exe" } function verify-program-signatures() { @@ -234,8 +244,8 @@ function make-installer() { echo "MSIFileName=keymandeveloper.msi" >> setup.inf echo "Title=Keyman Developer ${KEYMAN_VERSION_WITH_TAG}" >>setup.inf add_zip_files setup.zip keymandeveloper.msi setup.inf - cat "$DEVELOPER_PROGRAM/setup.exe" setup.zip > "keymandeveloper-$KEYMAN_VERSION.exe" - wrap-signcode //d "Keyman Developer" "keymandeveloper-$KEYMAN_VERSION.exe" + cat "$DEVELOPER_PROGRAM/setup.exe" setup.zip > "keymandeveloper-$FILENAME_VERSION.exe" + wrap-signcode //d "Keyman Developer" "keymandeveloper-$FILENAME_VERSION.exe" # # Zip the files we distribute as part of the standalone kmc distro into release\$Version\kmcomp-$Version.zip @@ -251,7 +261,7 @@ function make-kmc-install-zip() { cd "${DEVELOPER_ROOT}/bin" # TODO: rename this to keyman-developer-cli-$Version.zip - local KMCOMP_ZIP="${DEVELOPER_ROOT}/release/${KEYMAN_VERSION}/kmcomp-${KEYMAN_VERSION}.zip" + local KMCOMP_ZIP="${DEVELOPER_ROOT}/release/${KEYMAN_VERSION}/kmcomp-${FILENAME_VERSION}.zip" add_zip_files "${KMCOMP_ZIP}" -q -r \ kmconvert.exe \ From 634fef64d4744d06a5932ac9e1282f68811551e0 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 8 Sep 2025 12:31:11 +0200 Subject: [PATCH 17/52] maint(android): use versioned filenames for PR artifacts Relates-to: #10521 Test-bot: skip Build-bot: skip release:android,developer,windows --- android/build.sh | 11 ++++--- developer/src/inst/build.sh | 20 +++--------- oem/firstvoices/windows/src/inst/build.sh | 14 ++------ resources/build/builder-basic.inc.sh | 40 ++++++++++++++++------- windows/src/desktop/inst/build.sh | 14 ++------ 5 files changed, 44 insertions(+), 55 deletions(-) diff --git a/android/build.sh b/android/build.sh index 9d48effbf1..4702217da3 100755 --- a/android/build.sh +++ b/android/build.sh @@ -62,11 +62,14 @@ function do_test_help() { function archive_artifacts() { local UPLOAD_PATH KEYMAN_ENGINE_ANDROID_ZIP KEYMAN_APK FIRSTVOICES_APK + local KEYMAN_FULLY_VERSIONED_APK FIRSTVOICES_FULLY_VERSIONED_APK UPLOAD_PATH="${KEYMAN_ROOT}/android/upload/${KEYMAN_VERSION}" KEYMAN_ENGINE_ANDROID_ZIP="keyman-engine-android-${KEYMAN_VERSION}.zip" KEYMAN_APK="keyman-${KEYMAN_VERSION}.apk" FIRSTVOICES_APK="firstvoices-${KEYMAN_VERSION}.apk" + KEYMAN_FULLY_VERSIONED_APK="keyman-${KEYMAN_VERSION_FOR_PR_FILENAME}.apk" + FIRSTVOICES_FULLY_VERSIONED_APK="firstvoices-${KEYMAN_VERSION_FOR_PR_FILENAME}.apk" rm -rf "${UPLOAD_PATH}" mkdir -p "${UPLOAD_PATH}" @@ -87,12 +90,12 @@ function archive_artifacts() { ) # Copy release APK - cp "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/release/${KEYMAN_APK}" "${UPLOAD_PATH}" + cp "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/release/${KEYMAN_APK}" "${UPLOAD_PATH}/${KEYMAN_FULLY_VERSIONED_APK}" # FirstVoices app if [[ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]]; then - cp "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/release/${FIRSTVOICES_APK}" "${UPLOAD_PATH}" + cp "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/release/${FIRSTVOICES_APK}" "${UPLOAD_PATH}/${FIRSTVOICES_FULLY_VERSIONED_APK}" fi # @@ -100,10 +103,10 @@ function archive_artifacts() { # write_download_info "${UPLOAD_PATH}" "${KEYMAN_ENGINE_ANDROID_ZIP}" "Keyman Engine for Android" zip android - write_download_info "${UPLOAD_PATH}" "${KEYMAN_APK}" "Keyman for Android" apk android + write_download_info "${UPLOAD_PATH}" "${KEYMAN_FULLY_VERSIONED_APK}" "Keyman for Android" apk android if [[ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]]; then - write_download_info "${UPLOAD_PATH}" "${FIRSTVOICES_APK}" "FirstVoices Keyboards" apk android + write_download_info "${UPLOAD_PATH}" "${FIRSTVOICES_FULLY_VERSIONED_APK}" "FirstVoices Keyboards" apk android fi } diff --git a/developer/src/inst/build.sh b/developer/src/inst/build.sh index 545626ea31..891aa220d3 100755 --- a/developer/src/inst/build.sh +++ b/developer/src/inst/build.sh @@ -12,21 +12,11 @@ builder_describe "Installation files for Keyman Developer" \ @/common/windows/data \ clean configure build test publish -# For local builds, and for PR builds, we will use a filename including the tag -# but for release builds, we omit the tag for now. In the future, we may include -# the tag but that involves changing a number of other websites as dealing with -# the rename there too. -if builder_is_ci_release_build; then - FILENAME_VERSION="${KEYMAN_VERSION}" -else - FILENAME_VERSION="${KEYMAN_VERSION_WITH_TAG}" -fi - # NOTE: not using deps here because we will only do this in the 'publish' phase # after all other builds complete builder_describe_outputs \ - publish /developer/src/inst/keymandeveloper-${FILENAME_VERSION}.exe + publish /developer/src/inst/keymandeveloper-${KEYMAN_VERSION_FOR_PR_FILENAME}.exe builder_parse "$@" @@ -106,7 +96,7 @@ function copy-keymandeveloper-installer-to-artifacts() { make-kmc-install-zip cp -f "$DEVELOPER_ROOT/src/inst/keymandeveloper.msi" "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}/keymandeveloper.msi" - cp -f "$DEVELOPER_ROOT/src/inst/keymandeveloper-${FILENAME_VERSION}.exe" "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}/keymandeveloper-${FILENAME_VERSION}.exe" + cp -f "$DEVELOPER_ROOT/src/inst/keymandeveloper-${KEYMAN_VERSION_FOR_PR_FILENAME}.exe" "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}/keymandeveloper-${KEYMAN_VERSION_FOR_PR_FILENAME}.exe" } function verify-program-signatures() { @@ -244,8 +234,8 @@ function make-installer() { echo "MSIFileName=keymandeveloper.msi" >> setup.inf echo "Title=Keyman Developer ${KEYMAN_VERSION_WITH_TAG}" >>setup.inf add_zip_files setup.zip keymandeveloper.msi setup.inf - cat "$DEVELOPER_PROGRAM/setup.exe" setup.zip > "keymandeveloper-$FILENAME_VERSION.exe" - wrap-signcode //d "Keyman Developer" "keymandeveloper-$FILENAME_VERSION.exe" + cat "$DEVELOPER_PROGRAM/setup.exe" setup.zip > "keymandeveloper-$KEYMAN_VERSION_FOR_PR_FILENAME.exe" + wrap-signcode //d "Keyman Developer" "keymandeveloper-$KEYMAN_VERSION_FOR_PR_FILENAME.exe" # # Zip the files we distribute as part of the standalone kmc distro into release\$Version\kmcomp-$Version.zip @@ -261,7 +251,7 @@ function make-kmc-install-zip() { cd "${DEVELOPER_ROOT}/bin" # TODO: rename this to keyman-developer-cli-$Version.zip - local KMCOMP_ZIP="${DEVELOPER_ROOT}/release/${KEYMAN_VERSION}/kmcomp-${FILENAME_VERSION}.zip" + local KMCOMP_ZIP="${DEVELOPER_ROOT}/release/${KEYMAN_VERSION}/kmcomp-${KEYMAN_VERSION_FOR_PR_FILENAME}.zip" add_zip_files "${KMCOMP_ZIP}" -q -r \ kmconvert.exe \ diff --git a/oem/firstvoices/windows/src/inst/build.sh b/oem/firstvoices/windows/src/inst/build.sh index 4fdba0b4b3..99eadb4e48 100755 --- a/oem/firstvoices/windows/src/inst/build.sh +++ b/oem/firstvoices/windows/src/inst/build.sh @@ -12,21 +12,11 @@ builder_describe "Installation files for FirstVoices Keyboards" \ @/common/windows/data \ clean configure build test publish -# For local builds, and for PR builds, we will use a filename including the tag -# but for release builds, we omit the tag for now. In the future, we may include -# the tag but that involves changing a number of other websites as dealing with -# the rename there too. -if builder_is_ci_release_build; then - FILENAME_VERSION="${KEYMAN_VERSION}" -else - FILENAME_VERSION="${KEYMAN_VERSION_WITH_TAG}" -fi - # NOTE: not using deps here because we will only do this in the 'publish' phase # after all other builds complete builder_if_release_build_level builder_describe_outputs \ - publish /windows/release/${KEYMAN_VERSION}/firstvoices-${FILENAME_VERSION}.exe + publish /windows/release/${KEYMAN_VERSION}/firstvoices-${KEYMAN_VERSION_FOR_PR_FILENAME}.exe builder_parse "$@" @@ -113,7 +103,7 @@ function copy-installer() { mkdir -p "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}" cp firstvoices.msi "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/firstvoices.msi" - cp firstvoices.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/firstvoices-${FILENAME_VERSION}.exe" + cp firstvoices.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/firstvoices-${KEYMAN_VERSION_FOR_PR_FILENAME}.exe" verify-installer-signatures } diff --git a/resources/build/builder-basic.inc.sh b/resources/build/builder-basic.inc.sh index 83f77c3134..a1a268cb2a 100644 --- a/resources/build/builder-basic.inc.sh +++ b/resources/build/builder-basic.inc.sh @@ -13,6 +13,7 @@ # KEYMAN_VERSION_GIT_TAG: Git tag for the release, "release@$KEYMAN_VERSION_WITH_TAG", e.g. "release@14.0.1-alpha-test-1234" # KEYMAN_ROOT: fully resolved root path of Keyman repository # KEYMAN_VERSION_ENVIRONMENT: One of: local, test, alpha, beta, stable +# KEYMAN_VERSION_FOR_PR_FILENAME: KEYMAN_VERSION, for release builds, or KEYMAN_VERSION_WITH_TAG, for PR and local builds; see #10521 # UPLOAD_SENTRY: true - if KEYMAN_VERSION_ENVIRONMENT is one of alpha, beta, stable # false - if local, test. Indicates if debug artifacts should be uploaded to Sentry # BUILDER_OS: win|mac|linux -- current build environment @@ -115,6 +116,16 @@ function _builder_basic_find_version() { KEYMAN_VERSION_WITH_TAG="$KEYMAN_VERSION$KEYMAN_VERSION_TAG" KEYMAN_VERSION_GIT_TAG="release@$KEYMAN_VERSION_WITH_TAG" + # For local builds, and for PR builds, we will use a filename including the tag + # but for release builds, we omit the tag for now. In the future, we may include + # the tag but that involves changing a number of other websites as dealing with + # the rename there too. + if builder_is_ci_release_build; then + KEYMAN_VERSION_FOR_PR_FILENAME="${KEYMAN_VERSION}" + else + KEYMAN_VERSION_FOR_PR_FILENAME="${KEYMAN_VERSION_WITH_TAG}" + fi + readonly KEYMAN_VERSION readonly KEYMAN_VERSION_MAJOR readonly KEYMAN_VERSION_MINOR @@ -125,6 +136,7 @@ function _builder_basic_find_version() { readonly KEYMAN_VERSION_WITH_TAG readonly KEYMAN_VERSION_ENVIRONMENT readonly KEYMAN_VERSION_GIT_TAG + readonly KEYMAN_VERSION_FOR_PR_FILENAME # Export version strings so places like version.gradle can access them export KEYMAN_VERSION @@ -137,6 +149,7 @@ function _builder_basic_find_version() { export KEYMAN_VERSION_WITH_TAG export KEYMAN_VERSION_ENVIRONMENT export KEYMAN_VERSION_GIT_TAG + export KEYMAN_VERSION_FOR_PR_FILENAME } function _builder_basic_find_tier() { @@ -163,18 +176,19 @@ function _builder_basic_print_build_number_for_teamcity() { } function _builder_basic_print_version_utils_debug() { - echo "KEYMAN_ROOT: $KEYMAN_ROOT" - echo "KEYMAN_VERSION: $KEYMAN_VERSION" - echo "KEYMAN_VERSION_WIN: $KEYMAN_VERSION_WIN" - echo "KEYMAN_VERSION_RELEASE: $KEYMAN_VERSION_RELEASE" - echo "KEYMAN_VERSION_MAJOR: $KEYMAN_VERSION_MAJOR" - echo "KEYMAN_VERSION_MINOR: $KEYMAN_VERSION_MINOR" - echo "KEYMAN_VERSION_PATCH: $KEYMAN_VERSION_PATCH" - echo "KEYMAN_TIER: $KEYMAN_TIER" - echo "KEYMAN_VERSION_TAG: $KEYMAN_VERSION_TAG" - echo "KEYMAN_VERSION_WITH_TAG: $KEYMAN_VERSION_WITH_TAG" - echo "KEYMAN_VERSION_GIT_TAG: $KEYMAN_VERSION_GIT_TAG" - echo "KEYMAN_VERSION_ENVIRONMENT: $KEYMAN_VERSION_ENVIRONMENT" + echo "KEYMAN_ROOT: $KEYMAN_ROOT" + echo "KEYMAN_VERSION: $KEYMAN_VERSION" + echo "KEYMAN_VERSION_WIN: $KEYMAN_VERSION_WIN" + echo "KEYMAN_VERSION_RELEASE: $KEYMAN_VERSION_RELEASE" + echo "KEYMAN_VERSION_MAJOR: $KEYMAN_VERSION_MAJOR" + echo "KEYMAN_VERSION_MINOR: $KEYMAN_VERSION_MINOR" + echo "KEYMAN_VERSION_PATCH: $KEYMAN_VERSION_PATCH" + echo "KEYMAN_TIER: $KEYMAN_TIER" + echo "KEYMAN_VERSION_TAG: $KEYMAN_VERSION_TAG" + echo "KEYMAN_VERSION_WITH_TAG: $KEYMAN_VERSION_WITH_TAG" + echo "KEYMAN_VERSION_GIT_TAG: $KEYMAN_VERSION_GIT_TAG" + echo "KEYMAN_VERSION_ENVIRONMENT: $KEYMAN_VERSION_ENVIRONMENT" + echo "KEYMAN_VERSION_FOR_PR_FILENAME: $KEYMAN_VERSION_FOR_PR_FILENAME" } # TODO: consolidate with buildLevel, see #14285 @@ -209,6 +223,7 @@ replaceVersionStrings() { s/\$KEYMAN_VERSION_GIT_TAG/$KEYMAN_VERSION_GIT_TAG/g; s/\$KEYMAN_VERSION_ENVIRONMENT/$KEYMAN_VERSION_ENVIRONMENT/g; s/\$KEYMAN_VERSION/$KEYMAN_VERSION/g; + s/\$KEYMAN_VERSION_FOR_PR_FILENAME/$KEYMAN_VERSION_FOR_PR_FILENAME/g; " "$infile" > "$outfile" } @@ -249,6 +264,7 @@ replaceVersionStrings_Mkver() { s/\$KEYMAN_VERSION_WITH_TAG/$KEYMAN_VERSION_WITH_TAG/g; s/\$KEYMAN_VERSION_GIT_TAG/$KEYMAN_VERSION_GIT_TAG/g; s/\$KEYMAN_VERSION_ENVIRONMENT/$KEYMAN_VERSION_ENVIRONMENT/g; + s/\$KEYMAN_VERSION_FOR_PR_FILENAME/$KEYMAN_VERSION_FOR_PR_FILENAME/g; s/\$KEYMAN_VERSION/$KEYMAN_VERSION_WIN/g; diff --git a/windows/src/desktop/inst/build.sh b/windows/src/desktop/inst/build.sh index 4ae5914e3f..c16dbbbfe5 100755 --- a/windows/src/desktop/inst/build.sh +++ b/windows/src/desktop/inst/build.sh @@ -11,21 +11,11 @@ builder_describe "Installation files for Keyman for Windows" \ @/common/windows/data \ clean configure build test publish -# For local builds, and for PR builds, we will use a filename including the tag -# but for release builds, we omit the tag for now. In the future, we may include -# the tag but that involves changing a number of other websites as dealing with -# the rename there too. -if builder_is_ci_release_build; then - FILENAME_VERSION="${KEYMAN_VERSION}" -else - FILENAME_VERSION="${KEYMAN_VERSION_WITH_TAG}" -fi - # NOTE: not using deps here because we will only do this in the 'publish' phase # after all other builds complete builder_if_release_build_level builder_describe_outputs \ - publish /windows/release/${KEYMAN_VERSION}/keyman-${FILENAME_VERSION}.exe + publish /windows/release/${KEYMAN_VERSION}/keyman-${KEYMAN_VERSION_FOR_PR_FILENAME}.exe builder_parse "$@" @@ -114,7 +104,7 @@ function copy-installer() { mkdir -p "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}" cp keymandesktop.msi "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/keymandesktop.msi" - cp keymandesktop.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/keyman-${FILENAME_VERSION}.exe" + cp keymandesktop.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/keyman-${KEYMAN_VERSION_FOR_PR_FILENAME}.exe" cp "$WINDOWS_PROGRAM_APP/setup.exe" "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/setup.exe" verify-installer-signatures From ac81f49d28c36749449eb399e4b769165fcff2e8 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 8 Sep 2025 12:40:36 +0200 Subject: [PATCH 18/52] maint(ios): use versioned filenames for PR artifacts Relates-to: #10521 Test-bot: skip Build-bot: skip release:ios --- ios/tools/prepRelease.sh | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ios/tools/prepRelease.sh b/ios/tools/prepRelease.sh index 28adfbe1ff..138823d51e 100755 --- a/ios/tools/prepRelease.sh +++ b/ios/tools/prepRelease.sh @@ -29,8 +29,7 @@ fi . $KEYMAN_ROOT/resources/build/history/history-utils.sh #includes the following #. ../resources/build/utils.inc.sh -BUILD_NUMBER=`cat ../VERSION.md` -KEYMAN_TIER=`cat ../TIER.md` +BUILD_NUMBER="${KEYMAN_VERSION}" KEYMAN_CHANGELOG="changelog-${BUILD_NUMBER}-${KEYMAN_TIER}.txt" CHANGELOG_PATH="upload/$BUILD_NUMBER/$KEYMAN_CHANGELOG" @@ -59,7 +58,7 @@ mv -f "$CHANGELOG_PATH.1" "$CHANGELOG_PATH" # Keyman Engine # -KMEI_DST_NAME="keyman-engine-ios-${BUILD_NUMBER}.zip" +KMEI_DST_NAME="keyman-engine-ios-${KEYMAN_VERSION_FOR_PR_FILENAME}.zip" KMEI_DST="${WORK_DIR}/${UPLOAD_DIR}/${KMEI_DST_NAME}" KMEI_FRAMEWORK_BASE="build/Build/Products/Release/" @@ -87,14 +86,14 @@ cd "$WORK_DIR" # KEYMANAPP_IPA="build/Build/Products/Release-iphoneos/Keyman.ipa" -KEYMANAPP_IPA_DST="keyman-ios-${BUILD_NUMBER}.ipa" +KEYMANAPP_IPA_DST="keyman-ios-${KEYMAN_VERSION_FOR_PR_FILENAME}.ipa" echo "Copying Keyman IPA ${KEYMANAPP_IPA} => ${UPLOAD_DIR}/${KEYMANAPP_IPA_DST}..." cp "${KEYMANAPP_IPA}" "${WORK_DIR}/${UPLOAD_DIR}/${KEYMANAPP_IPA_DST}" KEYMANAPP_SIM_FOLDER="build/Build/Products/Release-iphonesimulator" KEYMANAPP_SIM_APP="$KEYMANAPP_SIM_FOLDER/Keyman.app" -KEYMANAPP_SIM_APP_DST="keyman-ios-simulator-${BUILD_NUMBER}.app.zip" +KEYMANAPP_SIM_APP_DST="keyman-ios-simulator-${KEYMAN_VERSION_FOR_PR_FILENAME}.app.zip" echo "Zipping Keyman simulator artifact ${KEYMANAPP_SIM_APP} => ${UPLOAD_DIR}/${KEYMANAPP_SIM_APP_DST}..." cd "${KEYMANAPP_SIM_FOLDER}" @@ -108,14 +107,14 @@ cd "$WORK_DIR" if [ "${RELEASE_OEM_FIRSTVOICES}" = true ]; then FIRSTVOICESAPP_IPA="../oem/firstvoices/ios/build/Build/Products/Release-iphoneos/FirstVoices.ipa" - FIRSTVOICESAPP_IPA_DST="firstvoices-ios-${BUILD_NUMBER}.ipa" + FIRSTVOICESAPP_IPA_DST="firstvoices-ios-${KEYMAN_VERSION_FOR_PR_FILENAME}.ipa" echo "Copying FirstVoices IPA ${FIRSTVOICESAPP_IPA} => ${UPLOAD_DIR}/${FIRSTVOICESAPP_IPA_DST}..." cp "${FIRSTVOICESAPP_IPA}" "${WORK_DIR}/${UPLOAD_DIR}/${FIRSTVOICESAPP_IPA_DST}" FIRSTVOICESAPP_SIM_FOLDER="../oem/firstvoices/ios/build/Build/Products/Release-iphonesimulator/" FIRSTVOICESAPP_SIM_APP="$FIRSTVOICESAPP_SIM_FOLDER/FirstVoices.app" - FIRSTVOICESAPP_SIM_APP_DST="firstvoices-ios-simulator-${BUILD_NUMBER}.app.zip" + FIRSTVOICESAPP_SIM_APP_DST="firstvoices-ios-simulator-${KEYMAN_VERSION_FOR_PR_FILENAME}.app.zip" echo "Zipping FirstVoices simulator artifact ${FIRSTVOICESAPP_SIM_APP} => ${UPLOAD_DIR}/${KEYMANAPP_SIM_APP_DST}..." cd "${FIRSTVOICESAPP_SIM_FOLDER}" From b0619442230bc4200ffc48209c15afec53976754 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 8 Sep 2025 12:44:57 +0200 Subject: [PATCH 19/52] maint(mac): use versioned filenames for PR artifacts Relates-to: #10521 Test-bot: skip Build-bot: skip release:mac --- mac/Keyman4MacIM/make-km-dmg.sh | 2 +- mac/build.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mac/Keyman4MacIM/make-km-dmg.sh b/mac/Keyman4MacIM/make-km-dmg.sh index 0ee1d7395b..87339f754a 100755 --- a/mac/Keyman4MacIM/make-km-dmg.sh +++ b/mac/Keyman4MacIM/make-km-dmg.sh @@ -212,7 +212,7 @@ if (( DETACH_SUCCESS < 999 )); then fi # Step 5 - Convert image to a compressed readonly DMG image -DMG_FILE_PATH="$DEST_DIR/keyman-$KEYMAN_VERSION.dmg" +DMG_FILE_PATH="$DEST_DIR/keyman-$KEYMAN_VERSION_FOR_PR_FILENAME.dmg" builder_echo info "Converting/compressing image to create \"$DMG_FILE_PATH\"" if [[ -e "$DMG_FILE_PATH" ]] ; then if [[ "$VERBOSITY" != "-quiet" ]] ; then diff --git a/mac/build.sh b/mac/build.sh index 875e4b5013..fa331acbe4 100755 --- a/mac/build.sh +++ b/mac/build.sh @@ -293,7 +293,7 @@ do_publish() { "${KM4MIM_BASE_PATH}/make-km-dmg.sh" local UPLOAD_PATH="${KM4MIM_BASE_PATH}/output/upload/${KEYMAN_VERSION}" - write_download_info "${UPLOAD_PATH}" "keyman-${KEYMAN_VERSION}.dmg" "Keyman4MacIM" dmg mac + write_download_info "${UPLOAD_PATH}" "keyman-${KEYMAN_VERSION_FOR_PR_FILENAME}.dmg" "Keyman4MacIM" dmg mac if builder_is_ci_build && builder_is_ci_build_level_release; then do_sentry From 9997ef7fff33c2da9e192f2ecf20eb4ddcf88049 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 8 Sep 2025 13:25:33 +0200 Subject: [PATCH 20/52] maint(android): rework android file versioning --- android/KMAPro/build.sh | 6 ++++-- android/build.sh | 14 ++++++-------- oem/firstvoices/android/build.sh | 4 +++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/android/KMAPro/build.sh b/android/KMAPro/build.sh index 74fed10588..39f1cfddbe 100755 --- a/android/KMAPro/build.sh +++ b/android/KMAPro/build.sh @@ -46,7 +46,7 @@ MODEL_PACKAGE_ID="nrc.en.mtnt" MODELS_TARGET="android/KMAPro/kMAPro/src/main/assets/${MODEL_PACKAGE_ID}.model.kmp" builder_describe_outputs \ configure "/${MODELS_TARGET}" \ - build /android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION}.apk + build /android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION_FOR_PR_FILENAME}.apk #### Build @@ -77,13 +77,15 @@ if builder_start_action build; then # Copy Keyman Engine for Android cp "${KEYMAN_ROOT}/android/KMEA/app/build/outputs/aar/keyman-engine-${CONFIG}.aar" "${KEYMAN_ROOT}/android/KMAPro/kMAPro/libs/keyman-engine.aar" - + # Convert markdown to html for offline help build_help_html android KMAPro/kMAPro/src/main/assets/info echo "BUILD_FLAGS $BUILD_FLAGS" ./gradlew $DAEMON_FLAG clean $BUILD_FLAGS + mv "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION}.apk" "keyman-${KEYMAN_VERSION_FOR_PR_FILENAME}.apk" + builder_finish_action success build fi diff --git a/android/build.sh b/android/build.sh index 4702217da3..56133c01a6 100755 --- a/android/build.sh +++ b/android/build.sh @@ -66,10 +66,8 @@ function archive_artifacts() { UPLOAD_PATH="${KEYMAN_ROOT}/android/upload/${KEYMAN_VERSION}" KEYMAN_ENGINE_ANDROID_ZIP="keyman-engine-android-${KEYMAN_VERSION}.zip" - KEYMAN_APK="keyman-${KEYMAN_VERSION}.apk" - FIRSTVOICES_APK="firstvoices-${KEYMAN_VERSION}.apk" - KEYMAN_FULLY_VERSIONED_APK="keyman-${KEYMAN_VERSION_FOR_PR_FILENAME}.apk" - FIRSTVOICES_FULLY_VERSIONED_APK="firstvoices-${KEYMAN_VERSION_FOR_PR_FILENAME}.apk" + KEYMAN_APK="keyman-${KEYMAN_VERSION_FOR_PR_FILENAME}.apk" + FIRSTVOICES_APK="firstvoices-${KEYMAN_VERSION_FOR_PR_FILENAME}.apk" rm -rf "${UPLOAD_PATH}" mkdir -p "${UPLOAD_PATH}" @@ -90,12 +88,12 @@ function archive_artifacts() { ) # Copy release APK - cp "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/release/${KEYMAN_APK}" "${UPLOAD_PATH}/${KEYMAN_FULLY_VERSIONED_APK}" + cp "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/release/${KEYMAN_APK}" "${UPLOAD_PATH}/${KEYMAN_APK}" # FirstVoices app if [[ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]]; then - cp "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/release/${FIRSTVOICES_APK}" "${UPLOAD_PATH}/${FIRSTVOICES_FULLY_VERSIONED_APK}" + cp "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/release/${FIRSTVOICES_APK}" "${UPLOAD_PATH}/${FIRSTVOICES_APK}" fi # @@ -103,10 +101,10 @@ function archive_artifacts() { # write_download_info "${UPLOAD_PATH}" "${KEYMAN_ENGINE_ANDROID_ZIP}" "Keyman Engine for Android" zip android - write_download_info "${UPLOAD_PATH}" "${KEYMAN_FULLY_VERSIONED_APK}" "Keyman for Android" apk android + write_download_info "${UPLOAD_PATH}" "${KEYMAN_APK}" "Keyman for Android" apk android if [[ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]]; then - write_download_info "${UPLOAD_PATH}" "${FIRSTVOICES_FULLY_VERSIONED_APK}" "FirstVoices Keyboards" apk android + write_download_info "${UPLOAD_PATH}" "${FIRSTVOICES_APK}" "FirstVoices Keyboards" apk android fi } diff --git a/oem/firstvoices/android/build.sh b/oem/firstvoices/android/build.sh index 00de2d98ec..c5b67a954c 100755 --- a/oem/firstvoices/android/build.sh +++ b/oem/firstvoices/android/build.sh @@ -38,7 +38,7 @@ if builder_is_debug_build; then TEST_FLAGS="-x assembleDebug lintDebug testDebug" fi -ARTIFACT="firstvoices-$KEYMAN_VERSION.apk" +ARTIFACT="firstvoices-${KEYMAN_VERSION_FOR_PR_FILENAME}.apk" KEYBOARD_PACKAGE_ID="fv_all" KEYBOARDS_TARGET="oem/firstvoices/android/app/src/main/assets/${KEYBOARD_PACKAGE_ID}.kmp" @@ -76,6 +76,8 @@ if builder_start_action build; then echo "BUILD_FLAGS: $BUILD_FLAGS" ./gradlew $DAEMON_FLAG clean $BUILD_FLAGS + mv "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION}.apk" "${ARTIFACT}" + builder_finish_action success build fi From 18ebb0baf3c52524ec9c55acca82fb93bf1ef6bb Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 8 Sep 2025 13:29:03 +0200 Subject: [PATCH 21/52] maint(ios): use versioned filename for testflight upload Build-bot: skip build:ios --- resources/teamcity/ios/keyman-ios-test.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/resources/teamcity/ios/keyman-ios-test.sh b/resources/teamcity/ios/keyman-ios-test.sh index 1c6f156238..830fdc156b 100755 --- a/resources/teamcity/ios/keyman-ios-test.sh +++ b/resources/teamcity/ios/keyman-ios-test.sh @@ -52,16 +52,14 @@ function __do_upload_to_testflight() { local FASTLANE_ITC_PROVIDER="$1" local FASTLANE_API_KEY_ID="$2" local APP_IPA_DST - local BUILD_NUMBER export FASTLANE_ITC_TEAM_ID="$3" export LC_ALL=en_US.UTF-8 export LANG=en_US.UTF-8 - BUILD_NUMBER=$(cat ../VERSION.md) - APP_IPA_DST="$4-ios-${BUILD_NUMBER}.ipa" + APP_IPA_DST="$4-ios-${KEYMAN_VERSION_FOR_PR_FILENAME}.ipa" - cd "upload/${BUILD_NUMBER}" + cd "upload/${KEYMAN_VERSION}" command -v fastlane # shellcheck disable=SC2154 fastlane pilot upload \ From 4a6755706d8caa3f6ae853980c33fcaf47298781 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 8 Sep 2025 13:37:34 +0200 Subject: [PATCH 22/52] fix(android): correct name of FV artifact --- oem/firstvoices/android/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oem/firstvoices/android/build.sh b/oem/firstvoices/android/build.sh index c5b67a954c..c7863aec97 100755 --- a/oem/firstvoices/android/build.sh +++ b/oem/firstvoices/android/build.sh @@ -76,7 +76,7 @@ if builder_start_action build; then echo "BUILD_FLAGS: $BUILD_FLAGS" ./gradlew $DAEMON_FLAG clean $BUILD_FLAGS - mv "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION}.apk" "${ARTIFACT}" + mv "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/$CONFIG/firstvoices-${KEYMAN_VERSION}.apk" "${ARTIFACT}" builder_finish_action success build fi From f8f6626f550e2f68e29a046651f500112129ab5f Mon Sep 17 00:00:00 2001 From: Markus Greiner Date: Tue, 2 Sep 2025 20:40:01 +0200 Subject: [PATCH 23/52] main(resources): #13954 adding GitHub actions workflow for Docker builds Test-bot: skip Build-bot: skip --- .../workflows/build-test-publish-docker.yml | 78 +++++++++++++++++++ resources/docker-images/README.md | 6 ++ resources/docker-images/act.sh | 28 +++++++ .../teamcity/triggers/trigger-test-builds.sh | 2 +- 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-test-publish-docker.yml create mode 100755 resources/docker-images/act.sh diff --git a/.github/workflows/build-test-publish-docker.yml b/.github/workflows/build-test-publish-docker.yml new file mode 100644 index 0000000000..8ea47649c4 --- /dev/null +++ b/.github/workflows/build-test-publish-docker.yml @@ -0,0 +1,78 @@ +name: build-test-publish-docker +run-name: "Build, test, publish Docker images (branch ${{ github.ref_name }} on ${{ github.event_name }}/${{ github.event.action }} by ${{ github.actor }})" + +# This workflow runs the build, test and publish steps for the +# Keyman build Docker images. +# It is triggered by a schedule and repository_dispatch event. + +on: + schedule: + #run every Fri, 3 pm UTC + - cron: '0 15 * * 5' # + + repository_dispatch: + types: ['build-test-publish-docker:*'] + +env: + # Use GitHub registry, if empty + REGISTRY: ghcr.io + USERNAME: "${{ github.actor }}" + PASSWORD: "${{ github.token }}" + + +jobs: + + prepare: + runs-on: ubuntu-latest + build: + runs-on: ubuntu-latest + steps: + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Build Docker images + run: | + if [ -n "${{ env.USERNAME }}" ]; then + resources/docker-images/build.sh build: --registry ${{ env.REGISTRY }} + else + echo ${{ env.PASSWORD }} | resources/docker-images/build.sh build: --registry ${{ env.REGISTRY }} --username ${{ env.USERNAME }} + fi + + + test: + runs-on: ubuntu-latest + + needs: build + + steps: + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test Docker images + run: | + if [ -n "${{ env.USERNAME }}" ]; then + resources/docker-images/build.sh test: --registry ${{ env.REGISTRY }} + else + echo ${{ env.PASSWORD }} | resources/docker-images/build.sh test: --registry ${{ env.REGISTRY }} --username ${{ env.USERNAME }} + fi + + + publish: + runs-on: ubuntu-latest + + needs: test + steps: + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Publish Docker images + run: | + if [ -n "${{ env.USERNAME }}" ]; then + resources/docker-images/build.sh publish: --registry ${{ env.REGISTRY }} + else + echo ${{ env.PASSWORD }} | resources/docker-images/build.sh publish: --registry ${{ env.REGISTRY }} --username ${{ env.USERNAME }} + fi + diff --git a/resources/docker-images/README.md b/resources/docker-images/README.md index cc1b9f1b38..cefae5020f 100644 --- a/resources/docker-images/README.md +++ b/resources/docker-images/README.md @@ -107,3 +107,9 @@ Running tests locally with the `run.sh` looks like: resources/docker-images/run.sh :web --registry 'myregistry:5678' --username 'user' --password 'password' -- web/build.sh test ``` +## Running GitHub Actions locally + +There exist [GitHub actions for building the Docker images](../../.github/workflows/build-test-publish-docker.yml). +Mainly for test resaons The workflow can be started locally. +As a prerequisite [act](https://nektosact.com/) must be installed. +There is [an example script](./act.sh) for running the workflow with act. diff --git a/resources/docker-images/act.sh b/resources/docker-images/act.sh new file mode 100755 index 0000000000..d50df56b66 --- /dev/null +++ b/resources/docker-images/act.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# runs GitHub workflow locally for building, testing and publishing Docker images +# requires act (https://nektosact.com/) +# requires a GitHub token with repo permissions (e.g. GITHUB_TOKEN from a personal access token) +# requires Docker with buildx support + +tempfile=/tmp/event.json + +trap "rm -f \"${tempfile}\"" EXIT + +cat > ${tempfile} < \ + -s GITHUB_TOKEN= \ + --container-options "--user runner:$(getent group docker | cut -d: -f3)" + +rm -f ${tempfile} diff --git a/resources/teamcity/triggers/trigger-test-builds.sh b/resources/teamcity/triggers/trigger-test-builds.sh index ce1af8e3ee..2561c0c07f 100755 --- a/resources/teamcity/triggers/trigger-test-builds.sh +++ b/resources/teamcity/triggers/trigger-test-builds.sh @@ -219,7 +219,7 @@ function find_platform_changes() { eval watch="'$'watch_${platform}" # Add common patterns to the watch list - watch="^(${platform}|(oem/[^/]+/${platform})|resources/((?!teamcity)|teamcity/(${platform}|includes))|${watch})" + watch="^(${platform}|(oem/[^/]+/${platform})|resources/((?!teamcity|docker-images)|teamcity/(${platform}|includes)|docker-images/(${platform}|base))|${watch})" # Since bash doesn't support negative look-aheads we use perl to test # (grep has a --perl-regexp option, but not the version on macOS) if perl -e 'exit($ARGV[0] =~ /$ARGV[1]/ ? 0 : 1)' "${line}" "${watch}"; then From 3e7e9e2664e7b379ce840973af070c0aeec04ecd Mon Sep 17 00:00:00 2001 From: Markus Greiner Date: Tue, 2 Sep 2025 21:56:43 +0200 Subject: [PATCH 24/52] #13954 cleang-up workfow file --- .github/workflows/build-test-publish-docker.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/build-test-publish-docker.yml b/.github/workflows/build-test-publish-docker.yml index 8ea47649c4..50ca258cdb 100644 --- a/.github/workflows/build-test-publish-docker.yml +++ b/.github/workflows/build-test-publish-docker.yml @@ -21,9 +21,6 @@ env: jobs: - - prepare: - runs-on: ubuntu-latest build: runs-on: ubuntu-latest steps: @@ -42,11 +39,9 @@ jobs: test: runs-on: ubuntu-latest - needs: build steps: - - name: Checkout repository uses: actions/checkout@v4 @@ -61,8 +56,8 @@ jobs: publish: runs-on: ubuntu-latest - needs: test + steps: - name: Checkout repository From cd10869579d520caa8e145a2df25afb3526e6b62 Mon Sep 17 00:00:00 2001 From: Markus Greiner Date: Tue, 2 Sep 2025 22:22:24 +0200 Subject: [PATCH 25/52] fix(resources) #19354 correcting if clause --- .github/workflows/build-test-publish-docker.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-test-publish-docker.yml b/.github/workflows/build-test-publish-docker.yml index 50ca258cdb..47336967e3 100644 --- a/.github/workflows/build-test-publish-docker.yml +++ b/.github/workflows/build-test-publish-docker.yml @@ -30,7 +30,8 @@ jobs: - name: Build Docker images run: | - if [ -n "${{ env.USERNAME }}" ]; then + echo ${{ github.event.action }} + if [ -z "${{ env.USERNAME }}" ]; then resources/docker-images/build.sh build: --registry ${{ env.REGISTRY }} else echo ${{ env.PASSWORD }} | resources/docker-images/build.sh build: --registry ${{ env.REGISTRY }} --username ${{ env.USERNAME }} @@ -47,7 +48,7 @@ jobs: - name: Test Docker images run: | - if [ -n "${{ env.USERNAME }}" ]; then + if [ -z "${{ env.USERNAME }}" ]; then resources/docker-images/build.sh test: --registry ${{ env.REGISTRY }} else echo ${{ env.PASSWORD }} | resources/docker-images/build.sh test: --registry ${{ env.REGISTRY }} --username ${{ env.USERNAME }} @@ -65,7 +66,7 @@ jobs: - name: Publish Docker images run: | - if [ -n "${{ env.USERNAME }}" ]; then + if [ -z "${{ env.USERNAME }}" ]; then resources/docker-images/build.sh publish: --registry ${{ env.REGISTRY }} else echo ${{ env.PASSWORD }} | resources/docker-images/build.sh publish: --registry ${{ env.REGISTRY }} --username ${{ env.USERNAME }} From 5a4635c88321e36ef4990afe69e3f6d6551fde4d Mon Sep 17 00:00:00 2001 From: Markus Greiner <90342882+Markus-SWAG@users.noreply.github.com> Date: Mon, 8 Sep 2025 12:12:48 +0200 Subject: [PATCH 26/52] Update resources/docker-images/README.md Co-authored-by: Eberhard Beilharz --- resources/docker-images/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/docker-images/README.md b/resources/docker-images/README.md index cefae5020f..d875c473b6 100644 --- a/resources/docker-images/README.md +++ b/resources/docker-images/README.md @@ -110,6 +110,6 @@ resources/docker-images/run.sh :web --registry 'myregistry:5678' --username 'use ## Running GitHub Actions locally There exist [GitHub actions for building the Docker images](../../.github/workflows/build-test-publish-docker.yml). -Mainly for test resaons The workflow can be started locally. +For testing and developing the workflow can be started locally. As a prerequisite [act](https://nektosact.com/) must be installed. There is [an example script](./act.sh) for running the workflow with act. From 0cdcbd7905dfd36f2730ea0a92d836c1fc08608b Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 8 Sep 2025 15:28:12 +0200 Subject: [PATCH 27/52] maint(android): tweak output path for android apks Build-bot: skip build:android,developer --- android/KMAPro/build.sh | 2 +- oem/firstvoices/android/build.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/KMAPro/build.sh b/android/KMAPro/build.sh index 39f1cfddbe..e4151355da 100755 --- a/android/KMAPro/build.sh +++ b/android/KMAPro/build.sh @@ -84,7 +84,7 @@ if builder_start_action build; then echo "BUILD_FLAGS $BUILD_FLAGS" ./gradlew $DAEMON_FLAG clean $BUILD_FLAGS - mv "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION}.apk" "keyman-${KEYMAN_VERSION_FOR_PR_FILENAME}.apk" + mv "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION}.apk" "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION_FOR_PR_FILENAME}.apk" builder_finish_action success build fi diff --git a/oem/firstvoices/android/build.sh b/oem/firstvoices/android/build.sh index c7863aec97..5fa54578c1 100755 --- a/oem/firstvoices/android/build.sh +++ b/oem/firstvoices/android/build.sh @@ -76,7 +76,7 @@ if builder_start_action build; then echo "BUILD_FLAGS: $BUILD_FLAGS" ./gradlew $DAEMON_FLAG clean $BUILD_FLAGS - mv "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/$CONFIG/firstvoices-${KEYMAN_VERSION}.apk" "${ARTIFACT}" + mv "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/$CONFIG/firstvoices-${KEYMAN_VERSION}.apk" "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/$CONFIG/${ARTIFACT}" builder_finish_action success build fi From ab33d638f57eb6938006fcaf40a14e178b692167 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 8 Sep 2025 16:52:56 +0200 Subject: [PATCH 28/52] maint(linux): check that command is at beginning of line Addresses code review comments. Co-Authored-By: Marc Durdin --- resources/build/ci/trigger-build-bot.inc.sh | 4 ++-- resources/build/test/build-bot/trigger-build-bot.test.sh | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/resources/build/ci/trigger-build-bot.inc.sh b/resources/build/ci/trigger-build-bot.inc.sh index fb6ed4c34c..d9fd38294f 100644 --- a/resources/build/ci/trigger-build-bot.inc.sh +++ b/resources/build/ci/trigger-build-bot.inc.sh @@ -41,7 +41,7 @@ function test_bot_check_pr_body() { # # Check the Keyman-Api-Check command whether or not to run API checks. -# Any value other than 'ignore' or no Keyman-Api-Check command will run +# Any value other than 'skip' or no Keyman-Api-Check command will run # the API checks. # # Parameters: @@ -56,7 +56,7 @@ function is_skip_keyman_api_check() { set -o noglob IFS=$'\n' prbody="$(echo "${prinfo}" | "${JQ}" -r '.body')" - prApiCheck="$(echo "${prbody}" | grep 'Keyman-Api-Check:' | cut -d: -f 2 - | tr -d '[:space:]')" + prApiCheck="$(echo -e "${prbody}" | grep '^Keyman-Api-Check:' | cut -d: -f 2 - | tr -d '[:space:]')" unset IFS set +o noglob diff --git a/resources/build/test/build-bot/trigger-build-bot.test.sh b/resources/build/test/build-bot/trigger-build-bot.test.sh index ce37c718a9..f7ca8f91bd 100755 --- a/resources/build/test/build-bot/trigger-build-bot.test.sh +++ b/resources/build/test/build-bot/trigger-build-bot.test.sh @@ -224,6 +224,10 @@ test_is_skip_keyman_api_check() { assert-equal "${result}" "false" "PR #2: omitted 'Keyman-Api-Check' should return false" result=$(is_skip_keyman_api_check 3 '{ "body": "Keyman-Api-Check: invalid" }') assert-equal "${result}" "false" "PR #3: invalid data for 'Keyman-Api-Check' should return false" + result=$(is_skip_keyman_api_check 4 '{ "body": "Some message\nKeyman-Api-Check: skip" }') + assert-equal "${result}" "true" "PR #4: 'Keyman-Api-Check: skip' at start of line should return true" + result=$(is_skip_keyman_api_check 5 '{ "body": "Here Keyman-Api-Check: skip\nis in the middle of the line" }') + assert-equal "${result}" "false" "PR #5: 'Keyman-Api-Check: skip' in the middle of the line should return false" builder_echo end test_is_skip_keyman_api_check success 'SUCCESS: test_is_skip_keyman_api_check' } From 535f7aa153ec1d4d682ec33e5343585bbf219f01 Mon Sep 17 00:00:00 2001 From: Markus Greiner Date: Mon, 8 Sep 2025 17:28:11 +0200 Subject: [PATCH 29/52] maint(resources): added trigger for Docker images build, pubish, test moving example script to readme file adding delete job for container images in ghcr Part-of: #13594 Test-bot: skip Build-bot: skip --- .../workflows/build-test-publish-docker.yml | 60 ++++++++++--------- .github/workflows/delete-build-containers.yml | 19 ++++++ resources/build/ci/trigger-definitions.inc.sh | 2 +- resources/docker-images/README.md | 31 +++++++++- resources/docker-images/act.sh | 28 --------- resources/docker-images/build.sh | 2 +- resources/docker-images/docker-build.inc.sh | 13 ++-- 7 files changed, 86 insertions(+), 69 deletions(-) create mode 100644 .github/workflows/delete-build-containers.yml delete mode 100755 resources/docker-images/act.sh diff --git a/.github/workflows/build-test-publish-docker.yml b/.github/workflows/build-test-publish-docker.yml index 47336967e3..1a9bf06615 100644 --- a/.github/workflows/build-test-publish-docker.yml +++ b/.github/workflows/build-test-publish-docker.yml @@ -1,5 +1,5 @@ -name: build-test-publish-docker -run-name: "Build, test, publish Docker images (branch ${{ github.ref_name }} on ${{ github.event_name }}/${{ github.event.action }} by ${{ github.actor }})" +name: "Build, test, and publish Docker images" +run-name: "Build, test, and publish Docker images (branch ${{ github.ref_name }} on ${{ github.event_name }}/${{ github.event.action }} by ${{ github.actor }})" # This workflow runs the build, test and publish steps for the # Keyman build Docker images. @@ -14,33 +14,54 @@ on: types: ['build-test-publish-docker:*'] env: - # Use GitHub registry, if empty REGISTRY: ghcr.io USERNAME: "${{ github.actor }}" PASSWORD: "${{ github.token }}" - jobs: - build: - runs-on: ubuntu-latest - steps: + message: + runs-on: ubuntu-latest + if: ${{ github.event.act }} + + steps: + - name: Print message + run: | + echo "Build, test, and publish Docker images (branch ${{ github.ref_name }} on ${{ github.event_name }}/${{ github.event.action }} by ${{ github.actor }})" + + build: + runs-on: ubuntu-latest + + steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Build Docker images run: | - echo ${{ github.event.action }} if [ -z "${{ env.USERNAME }}" ]; then resources/docker-images/build.sh build: --registry ${{ env.REGISTRY }} else echo ${{ env.PASSWORD }} | resources/docker-images/build.sh build: --registry ${{ env.REGISTRY }} --username ${{ env.USERNAME }} fi + publish: + runs-on: ubuntu-latest + needs: build + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Publish Docker images + run: | + if [ -z "${{ env.USERNAME }}" ]; then + resources/docker-images/build.sh publish: --registry ${{ env.REGISTRY }} + else + echo ${{ env.PASSWORD }} | resources/docker-images/build.sh publish: --registry ${{ env.REGISTRY }} --username ${{ env.USERNAME }} + fi test: runs-on: ubuntu-latest - needs: build + needs: publish steps: - name: Checkout repository @@ -53,22 +74,3 @@ jobs: else echo ${{ env.PASSWORD }} | resources/docker-images/build.sh test: --registry ${{ env.REGISTRY }} --username ${{ env.USERNAME }} fi - - - publish: - runs-on: ubuntu-latest - needs: test - - steps: - - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Publish Docker images - run: | - if [ -z "${{ env.USERNAME }}" ]; then - resources/docker-images/build.sh publish: --registry ${{ env.REGISTRY }} - else - echo ${{ env.PASSWORD }} | resources/docker-images/build.sh publish: --registry ${{ env.REGISTRY }} --username ${{ env.USERNAME }} - fi - diff --git a/.github/workflows/delete-build-containers.yml b/.github/workflows/delete-build-containers.yml new file mode 100644 index 0000000000..335e844de7 --- /dev/null +++ b/.github/workflows/delete-build-containers.yml @@ -0,0 +1,19 @@ + + +on: + schedule: + #run every Fri, 6 pm UTC + - cron: '0 18 * * 5' # + +jobs: + cleanup-images: + name: ghcr.io cleanup action + runs-on: ubuntu-latest + + steps: + - uses: dataaxiom/ghcr-cleanup-action@v1 + with: + dry-run: true + expand-packages: true + packages: keyman-*-ci + delete-untagged: true diff --git a/resources/build/ci/trigger-definitions.inc.sh b/resources/build/ci/trigger-definitions.inc.sh index 74380e7311..0666e05487 100644 --- a/resources/build/ci/trigger-definitions.inc.sh +++ b/resources/build/ci/trigger-definitions.inc.sh @@ -112,7 +112,7 @@ vcs_test=HttpsGithubComKeymanappKeymanPRs bc_master_android=(KeymanAndroid_Build) bc_master_ios=(Keyman_iOS_Master) -bc_master_linux=(KeymanLinux_Master deb-release-packaging_GitHub) +bc_master_linux=(KeymanLinux_Master deb-release-packaging_GitHub build-test-publish-docker_GitHub) bc_master_mac=(KeymanMac_Master) bc_master_windows=(Keyman_Build) bc_master_web=(Keymanweb_Build) diff --git a/resources/docker-images/README.md b/resources/docker-images/README.md index d875c473b6..6c962b407c 100644 --- a/resources/docker-images/README.md +++ b/resources/docker-images/README.md @@ -112,4 +112,33 @@ resources/docker-images/run.sh :web --registry 'myregistry:5678' --username 'use There exist [GitHub actions for building the Docker images](../../.github/workflows/build-test-publish-docker.yml). For testing and developing the workflow can be started locally. As a prerequisite [act](https://nektosact.com/) must be installed. -There is [an example script](./act.sh) for running the workflow with act. + +This is an exmaple for running the workflow with act: + +```bash +#!/bin/bash +# runs GitHub workflow locally for building, testing and publishing Docker images +# requires act (https://nektosact.com/) +# requires a GitHub token with repo permissions (e.g. GITHUB_TOKEN from a personal access token) +# requires Docker with buildx support + +tempfile=/tmp/event.json + +cat > ${tempfile} < ${tempfile} < \ - -s GITHUB_TOKEN= \ - --container-options "--user runner:$(getent group docker | cut -d: -f3)" - -rm -f ${tempfile} diff --git a/resources/docker-images/build.sh b/resources/docker-images/build.sh index 49735071a5..bd2323332c 100755 --- a/resources/docker-images/build.sh +++ b/resources/docker-images/build.sh @@ -69,7 +69,7 @@ publish_action() { local platform=$1 local version_list=("${build_version}") - builder_echo debug "publishing image for ${platform}" + builder_echo debug "publishing image for ${platform}." if is_default_values; then version_list+=("default") diff --git a/resources/docker-images/docker-build.inc.sh b/resources/docker-images/docker-build.inc.sh index 711d6bdb53..b020559b45 100644 --- a/resources/docker-images/docker-build.inc.sh +++ b/resources/docker-images/docker-build.inc.sh @@ -96,16 +96,11 @@ setup_container_registry() { local password=${REGISTRY_PASSWORD:-} registry_slash='' - builder_echo debug "Setting up container registry." + builder_echo debug "Setting up container registry '${registry}'." - if [[ "${registry:-}" == "docker.io" ]]; then - builder_echo debug "Container registry is 'docker.io'." - registry= - else - registry_slash="${registry}/" - registry_parameters="--registry ${registry}" - build_args+=("--build-arg=REGISTRY=${registry_slash}") - fi + registry_slash="${registry}/" + registry_parameters="--registry ${registry}" + build_args+=("--build-arg=REGISTRY=${registry_slash}") if [[ -n ${username:-} ]] ; then if [[ -z ${password:-} ]] ; then From 189ee50e2462c4c47646235aaae1157ea273397b Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 8 Sep 2025 18:06:36 +0200 Subject: [PATCH 30/52] fix(linux): don't crash if `kmp.json` is missing `keyboards` section Some keyboards seem to be missing the `keyboards` section in the `kmp.json` file. This change prevents a crash and instead returns `None` and thus we don't create a .ldml file and so onboard will show the default keyboard instead. The alternative would be to create an empty .ldml file, but that causes onboard to show a keyboard with all key caps being empty. Fixes: #14707 Fixes: KEYMAN-LINUX-8P Test-bot: skip --- .../keyman_config/install_kmp.py | 6 +- linux/keyman-config/keyman_config/kvk2ldml.py | 7 +- linux/keyman-config/tests/kvk2ldml_tests.py | 66 ++++++++++++------- 3 files changed, 51 insertions(+), 28 deletions(-) diff --git a/linux/keyman-config/keyman_config/install_kmp.py b/linux/keyman-config/keyman_config/install_kmp.py index cb69d596af..8b47ed7857 100755 --- a/linux/keyman-config/keyman_config/install_kmp.py +++ b/linux/keyman-config/keyman_config/install_kmp.py @@ -178,9 +178,9 @@ class InstallKmp(): logging.info("Converting %s to LDML and installing both as as keyman file", f['name']) name, ext = os.path.splitext(f['name']) - ldml = convert_kvk_to_ldml(name, fpath) - ldmlfile = os.path.join(self.packageDir, f"{name}.ldml") - output_ldml(ldmlfile, ldml) + if ldml := convert_kvk_to_ldml(name, fpath): + ldmlfile = os.path.join(self.packageDir, f"{name}.ldml") + output_ldml(ldmlfile, ldml) elif ftype == KMFileTypes.KM_ICON: # Special handling of icon to convert to PNG logging.info("Converting %s to PNG and installing both as keyman files", diff --git a/linux/keyman-config/keyman_config/kvk2ldml.py b/linux/keyman-config/keyman_config/kvk2ldml.py index ddef89241a..35f5bcd908 100755 --- a/linux/keyman-config/keyman_config/kvk2ldml.py +++ b/linux/keyman-config/keyman_config/kvk2ldml.py @@ -366,14 +366,17 @@ def convert_ldml(keyboardName, kvkData, kmpJsonFilename): info, system, options, keyboards, files = parsemetadata(kmpJsonFilename) + if not keyboards: + return None + ldml = etree.Element("keyboard", locale="zzz-keyman") + for keyboard in keyboards: if keyboard['id'] != keyboardName: continue if 'oskFont' in keyboard: fontFile = os.path.join(os.path.dirname(kmpJsonFilename), keyboard['oskFont']) - font = _fontFacename(fontFile) - if font: + if font := _fontFacename(fontFile): ldml.set('keymanFacename', font) break diff --git a/linux/keyman-config/tests/kvk2ldml_tests.py b/linux/keyman-config/tests/kvk2ldml_tests.py index fc8a34c28b..4bb9c337c0 100644 --- a/linux/keyman-config/tests/kvk2ldml_tests.py +++ b/linux/keyman-config/tests/kvk2ldml_tests.py @@ -8,31 +8,34 @@ import unittest from keyman_config.kvk2ldml import KVKData, NFont, convert_ldml class Kvk2LdmlTests(unittest.TestCase): - def _createKmpJson(self, packagedir): + def _createKmpJson(self, packagedir, add_keyboards=True): kmpJsonFilename = os.path.join(packagedir, 'kmp.json') + system = '''"system": { + "keymanDeveloperVersion": "18.0", + "fileVersion": "7.0" + }''' + files = '''"files": [ { + "name": "khmer_angkor.kmx", + "description": "Keyboard Khmer Angkor" + }, { + "name": "kmp.json", + "description": "Package information (JSON)" + } ]''' + if add_keyboards: + keyboards = '''"keyboards": [ { + "name": "Khmer Angkor", + "id": "khmer_angkor", + "version": "1.5", + "oskFont": "keymanweb-osk.ttf", + "languages": [ { + "name": "Central Khmer (Khmer, Cambodia)", + "id": "km" + } ]} + ]''' + else: + keyboards = '' with open(kmpJsonFilename, 'w') as file: - file.write('''{ - "system": { - "keymanDeveloperVersion": "18.0", - "fileVersion": "7.0" - }, - "files": [ { - "name": "khmer_angkor.kmx", - "description": "Keyboard Khmer Angkor" - }, { - "name": "kmp.json", - "description": "Package information (JSON)" - } ], - "keyboards": [ { - "name": "Khmer Angkor", - "id": "khmer_angkor", - "version": "1.5", - "oskFont": "keymanweb-osk.ttf", - "languages": [ { - "name": "Central Khmer (Khmer, Cambodia)", - "id": "km" - } ]} - ]}''') + file.write(f'{{ {system}, {files}, {keyboards} }}') return kmpJsonFilename def test_convert_ldml__adds_keymanFacename(self): @@ -56,3 +59,20 @@ class Kvk2LdmlTests(unittest.TestCase): self.assertEqual(ldml.get('keymanFacename'), 'SymChar') workdir.cleanup() + + def test_convert_ldml__no_crash_without_keyboards(self): + # Setup + keyboardName = 'khmer_angkor' + kvkData = KVKData() + kvkData.AssociatedKeyboard = keyboardName + + workdir = tempfile.TemporaryDirectory() + kmpJsonFilename = self._createKmpJson(workdir.name, add_keyboards=False) + + # Execute + ldml = convert_ldml(keyboardName, kvkData, kmpJsonFilename) + + # Verify + self.assertIsNone(ldml) + + workdir.cleanup() From 0f8ea18c20c0e76057c570cc17fbb9dc0fb34c4a Mon Sep 17 00:00:00 2001 From: Markus Greiner Date: Mon, 8 Sep 2025 18:10:51 +0200 Subject: [PATCH 31/52] fixing typo Part-of: #13594 Test-bot: skip Build-bot: skip --- .github/workflows/build-test-publish-docker.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-test-publish-docker.yml b/.github/workflows/build-test-publish-docker.yml index 1a9bf06615..41fec89bfd 100644 --- a/.github/workflows/build-test-publish-docker.yml +++ b/.github/workflows/build-test-publish-docker.yml @@ -8,7 +8,7 @@ run-name: "Build, test, and publish Docker images (branch ${{ github.ref_name }} on: schedule: #run every Fri, 3 pm UTC - - cron: '0 15 * * 5' # + - cron: '0 15 * * 5' repository_dispatch: types: ['build-test-publish-docker:*'] @@ -19,7 +19,6 @@ env: PASSWORD: "${{ github.token }}" jobs: - message: runs-on: ubuntu-latest if: ${{ github.event.act }} @@ -28,8 +27,7 @@ jobs: - name: Print message run: | echo "Build, test, and publish Docker images (branch ${{ github.ref_name }} on ${{ github.event_name }}/${{ github.event.action }} by ${{ github.actor }})" - - build: + build: runs-on: ubuntu-latest steps: From e22e46db369daa29118f88fc561b3b2352179cb1 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 8 Sep 2025 14:04:31 -0400 Subject: [PATCH 32/52] auto: increment master version to 19.0.115 Test-bot: skip Build-bot: skip --- HISTORY.md | 8 ++++++++ VERSION.md | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 769d8ddd22..2320b87bf6 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,13 @@ # Keyman Version History +## 19.0.114 alpha 2025-09-08 + +* docs(web): Improve empty What's New page for Web (#14660) +* maint(linux): remove obsolete Makefile (#14694) +* maint(resources): honor configure comment character in commit message (#14695) +* fix(web): fix error trying to calculate style scaling effects (#14684) +* refactor(resources): remove shellcheck warnings (#14698) + ## 19.0.113 alpha 2025-09-04 * fix(android): Update KMSample1 layout when changing orientation (#14678) diff --git a/VERSION.md b/VERSION.md index 668434973c..feeb83c0d3 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.114 \ No newline at end of file +19.0.115 \ No newline at end of file From 4d38b254e558b64aef274238659fb1eb3fef4ace Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 9 Sep 2025 12:04:16 +1000 Subject: [PATCH 33/52] fix(windows): fix confusing code comments --- .../main/Keyman.System.UpdateStateMachine.pas | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 98b0080842..c7a99d2d24 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -89,8 +89,9 @@ type function CurrentStateName: string; (** * Checks if Keyman is the WaitingRestartState, the cache is valid - * and that Keyman has not run in this Windows session. - * The purpose is for the calling code to produce + * and that Keyman has not run in this Windows session. It also confirms + * that automatic updates are still enabled #14295. + * The purpose is for the calling code can choose to produce * a UI to confirm the user wants to install an update, * if everything is ready to start a update. * @@ -570,13 +571,9 @@ begin if not (CurrentState is WaitingRestartState) then Exit(False); - // Validate the cache otherwise we could - // prompt the caller for update but then fail. - // Also combined #14295 moved check here from initprog.pas - // The setting for automatic updates may have been set to disable - // after updates were checked and downloaded. The user should - // not be prompted for an update in this case and the update should - // abort. + // Verify conditions since entering WaitingRestartState: + // If the cache is not valid or automatic updates have + // been disabled clear the cache and return to the idle state. if not (TUpdateCheckStorage.CheckMetaDataForUpdate and Self.GetAutomaticUpdates) then begin HandleAbort; From 33846f608671d49e1129eb6cda9b0112796e41ea Mon Sep 17 00:00:00 2001 From: Markus Greiner Date: Tue, 9 Sep 2025 10:24:07 +0200 Subject: [PATCH 34/52] maint(resources): adding names to jobs and ids to steps, setting bash as default shell Part-of: #13594 Test-bot: skip Build-bot: skip --- .../workflows/build-test-publish-docker.yml | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-test-publish-docker.yml b/.github/workflows/build-test-publish-docker.yml index 41fec89bfd..7919d9ff81 100644 --- a/.github/workflows/build-test-publish-docker.yml +++ b/.github/workflows/build-test-publish-docker.yml @@ -1,10 +1,13 @@ -name: "Build, test, and publish Docker images" -run-name: "Build, test, and publish Docker images (branch ${{ github.ref_name }} on ${{ github.event_name }}/${{ github.event.action }} by ${{ github.actor }})" - # This workflow runs the build, test and publish steps for the # Keyman build Docker images. # It is triggered by a schedule and repository_dispatch event. +name: "Build, test, and publish Docker images" +run-name: "Build, test, and publish Docker images (branch ${{ github.ref_name }} on ${{ github.event_name }}/${{ github.event.action }} by ${{ github.actor }})" +defaults: + run: + shell: bash + on: schedule: #run every Fri, 3 pm UTC @@ -19,21 +22,28 @@ env: PASSWORD: "${{ github.token }}" jobs: + message: + name: Print message runs-on: ubuntu-latest if: ${{ github.event.act }} steps: - name: Print message + id: print-message run: | echo "Build, test, and publish Docker images (branch ${{ github.ref_name }} on ${{ github.event_name }}/${{ github.event.action }} by ${{ github.actor }})" + build: + name: Build Docker images runs-on: ubuntu-latest steps: - name: Checkout repository + id: checkout uses: actions/checkout@v4 - name: Build Docker images + id: build run: | if [ -z "${{ env.USERNAME }}" ]; then resources/docker-images/build.sh build: --registry ${{ env.REGISTRY }} @@ -42,14 +52,17 @@ jobs: fi publish: + name: Publish Docker images runs-on: ubuntu-latest needs: build steps: - name: Checkout repository + id: checkout uses: actions/checkout@v4 - name: Publish Docker images + id: publish run: | if [ -z "${{ env.USERNAME }}" ]; then resources/docker-images/build.sh publish: --registry ${{ env.REGISTRY }} @@ -58,14 +71,17 @@ jobs: fi test: + name: Test Docker images runs-on: ubuntu-latest needs: publish steps: - name: Checkout repository + id: checkout uses: actions/checkout@v4 - name: Test Docker images + id: test run: | if [ -z "${{ env.USERNAME }}" ]; then resources/docker-images/build.sh test: --registry ${{ env.REGISTRY }} From 96507a433bd01be1e02abf778f5855938171eac2 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 9 Sep 2025 21:00:49 +1000 Subject: [PATCH 35/52] update(windows): review comment Co-authored-by: Marc Durdin --- .../desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index c7a99d2d24..d98e2c05f0 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -574,7 +574,7 @@ begin // Verify conditions since entering WaitingRestartState: // If the cache is not valid or automatic updates have // been disabled clear the cache and return to the idle state. - if not (TUpdateCheckStorage.CheckMetaDataForUpdate and Self.GetAutomaticUpdates) then + if not TUpdateCheckStorage.CheckMetaDataForUpdate or not Self.GetAutomaticUpdates then begin HandleAbort; Exit(False); From 1a5f5dfefaa7bd7f1d48d5f9ff023fc503c03348 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Tue, 9 Sep 2025 14:01:27 -0400 Subject: [PATCH 36/52] auto: increment master version to 19.0.116 Test-bot: skip Build-bot: skip --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 2320b87bf6..793e0d8b86 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 19.0.115 alpha 2025-09-09 + +* fix(linux): don't crash if `kmp.json` is missing `keyboards` section (#14708) + ## 19.0.114 alpha 2025-09-08 * docs(web): Improve empty What's New page for Web (#14660) diff --git a/VERSION.md b/VERSION.md index feeb83c0d3..0ecff9843f 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.115 \ No newline at end of file +19.0.116 \ No newline at end of file From fa830f0261be27937d4b2988106332271d1c5b12 Mon Sep 17 00:00:00 2001 From: Markus Greiner <90342882+Markus-SWAG@users.noreply.github.com> Date: Tue, 9 Sep 2025 21:25:59 +0200 Subject: [PATCH 37/52] Update .github/workflows/delete-build-containers.yml Co-authored-by: Eberhard Beilharz --- .github/workflows/delete-build-containers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/delete-build-containers.yml b/.github/workflows/delete-build-containers.yml index 335e844de7..d869831366 100644 --- a/.github/workflows/delete-build-containers.yml +++ b/.github/workflows/delete-build-containers.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: dataaxiom/ghcr-cleanup-action@v1 + - uses: dataaxiom/ghcr-cleanup-action@cd0cdb900b5dbf3a6f2cc869f0dbb0b8211f50c4 # v1.0.16 with: dry-run: true expand-packages: true From 377b1056ec36fbc7fb0950df6fee4d3240209b0c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 10 Sep 2025 11:17:41 +0200 Subject: [PATCH 38/52] maint(resources): rename KEYMAN_VERSION_FOR_PR_FILENAME Address review comment from #14703. Test-bot: skip Build-bot: skip Relates-to: #14703 Relates-to: #10521 --- android/KMAPro/build.sh | 4 ++-- android/build.sh | 4 ++-- developer/src/inst/build.sh | 10 +++++----- ios/tools/prepRelease.sh | 10 +++++----- mac/Keyman4MacIM/make-km-dmg.sh | 2 +- mac/build.sh | 2 +- oem/firstvoices/android/build.sh | 2 +- oem/firstvoices/windows/src/inst/build.sh | 4 ++-- resources/build/builder-basic.inc.sh | 16 ++++++++-------- resources/teamcity/ios/keyman-ios-test.sh | 2 +- windows/src/desktop/inst/build.sh | 4 ++-- 11 files changed, 30 insertions(+), 30 deletions(-) diff --git a/android/KMAPro/build.sh b/android/KMAPro/build.sh index e4151355da..06370c06d2 100755 --- a/android/KMAPro/build.sh +++ b/android/KMAPro/build.sh @@ -46,7 +46,7 @@ MODEL_PACKAGE_ID="nrc.en.mtnt" MODELS_TARGET="android/KMAPro/kMAPro/src/main/assets/${MODEL_PACKAGE_ID}.model.kmp" builder_describe_outputs \ configure "/${MODELS_TARGET}" \ - build /android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION_FOR_PR_FILENAME}.apk + build /android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION_FOR_FILENAME}.apk #### Build @@ -84,7 +84,7 @@ if builder_start_action build; then echo "BUILD_FLAGS $BUILD_FLAGS" ./gradlew $DAEMON_FLAG clean $BUILD_FLAGS - mv "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION}.apk" "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION_FOR_PR_FILENAME}.apk" + mv "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION}.apk" "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION_FOR_FILENAME}.apk" builder_finish_action success build fi diff --git a/android/build.sh b/android/build.sh index 56133c01a6..54e61dd9a5 100755 --- a/android/build.sh +++ b/android/build.sh @@ -66,8 +66,8 @@ function archive_artifacts() { UPLOAD_PATH="${KEYMAN_ROOT}/android/upload/${KEYMAN_VERSION}" KEYMAN_ENGINE_ANDROID_ZIP="keyman-engine-android-${KEYMAN_VERSION}.zip" - KEYMAN_APK="keyman-${KEYMAN_VERSION_FOR_PR_FILENAME}.apk" - FIRSTVOICES_APK="firstvoices-${KEYMAN_VERSION_FOR_PR_FILENAME}.apk" + KEYMAN_APK="keyman-${KEYMAN_VERSION_FOR_FILENAME}.apk" + FIRSTVOICES_APK="firstvoices-${KEYMAN_VERSION_FOR_FILENAME}.apk" rm -rf "${UPLOAD_PATH}" mkdir -p "${UPLOAD_PATH}" diff --git a/developer/src/inst/build.sh b/developer/src/inst/build.sh index e24d75b16c..5d44f228eb 100755 --- a/developer/src/inst/build.sh +++ b/developer/src/inst/build.sh @@ -16,7 +16,7 @@ builder_describe "Installation files for Keyman Developer" \ # after all other builds complete builder_describe_outputs \ - publish /developer/src/inst/keymandeveloper-${KEYMAN_VERSION_FOR_PR_FILENAME}.exe + publish /developer/src/inst/keymandeveloper-${KEYMAN_VERSION_FOR_FILENAME}.exe builder_parse "$@" @@ -96,7 +96,7 @@ function copy-keymandeveloper-installer-to-artifacts() { make-kmc-install-zip cp -f "$DEVELOPER_ROOT/src/inst/keymandeveloper.msi" "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}/keymandeveloper.msi" - cp -f "$DEVELOPER_ROOT/src/inst/keymandeveloper-${KEYMAN_VERSION_FOR_PR_FILENAME}.exe" "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}/keymandeveloper-${KEYMAN_VERSION_FOR_PR_FILENAME}.exe" + cp -f "$DEVELOPER_ROOT/src/inst/keymandeveloper-${KEYMAN_VERSION_FOR_FILENAME}.exe" "$DEVELOPER_ROOT/release/${KEYMAN_VERSION}/keymandeveloper-${KEYMAN_VERSION_FOR_FILENAME}.exe" } function verify-program-signatures() { @@ -234,8 +234,8 @@ function make-installer() { echo "MSIFileName=keymandeveloper.msi" >> setup.inf echo "Title=Keyman Developer ${KEYMAN_VERSION_WITH_TAG}" >>setup.inf add_zip_files setup.zip keymandeveloper.msi setup.inf - cat "$DEVELOPER_PROGRAM/setup.exe" setup.zip > "keymandeveloper-$KEYMAN_VERSION_FOR_PR_FILENAME.exe" - wrap-signcode //d "Keyman Developer" "keymandeveloper-$KEYMAN_VERSION_FOR_PR_FILENAME.exe" + cat "$DEVELOPER_PROGRAM/setup.exe" setup.zip > "keymandeveloper-$KEYMAN_VERSION_FOR_FILENAME.exe" + wrap-signcode //d "Keyman Developer" "keymandeveloper-$KEYMAN_VERSION_FOR_FILENAME.exe" # # Zip the files we distribute as part of the standalone kmc distro into release\$Version\kmcomp-$Version.zip @@ -251,7 +251,7 @@ function make-kmc-install-zip() { cd "${DEVELOPER_ROOT}/bin" # TODO: rename this to keyman-developer-cli-$Version.zip - local KMCOMP_ZIP="${DEVELOPER_ROOT}/release/${KEYMAN_VERSION}/kmcomp-${KEYMAN_VERSION_FOR_PR_FILENAME}.zip" + local KMCOMP_ZIP="${DEVELOPER_ROOT}/release/${KEYMAN_VERSION}/kmcomp-${KEYMAN_VERSION_FOR_FILENAME}.zip" add_zip_files "${KMCOMP_ZIP}" -q -r \ kmconvert.exe \ diff --git a/ios/tools/prepRelease.sh b/ios/tools/prepRelease.sh index 138823d51e..8065aad2f8 100755 --- a/ios/tools/prepRelease.sh +++ b/ios/tools/prepRelease.sh @@ -58,7 +58,7 @@ mv -f "$CHANGELOG_PATH.1" "$CHANGELOG_PATH" # Keyman Engine # -KMEI_DST_NAME="keyman-engine-ios-${KEYMAN_VERSION_FOR_PR_FILENAME}.zip" +KMEI_DST_NAME="keyman-engine-ios-${KEYMAN_VERSION_FOR_FILENAME}.zip" KMEI_DST="${WORK_DIR}/${UPLOAD_DIR}/${KMEI_DST_NAME}" KMEI_FRAMEWORK_BASE="build/Build/Products/Release/" @@ -86,14 +86,14 @@ cd "$WORK_DIR" # KEYMANAPP_IPA="build/Build/Products/Release-iphoneos/Keyman.ipa" -KEYMANAPP_IPA_DST="keyman-ios-${KEYMAN_VERSION_FOR_PR_FILENAME}.ipa" +KEYMANAPP_IPA_DST="keyman-ios-${KEYMAN_VERSION_FOR_FILENAME}.ipa" echo "Copying Keyman IPA ${KEYMANAPP_IPA} => ${UPLOAD_DIR}/${KEYMANAPP_IPA_DST}..." cp "${KEYMANAPP_IPA}" "${WORK_DIR}/${UPLOAD_DIR}/${KEYMANAPP_IPA_DST}" KEYMANAPP_SIM_FOLDER="build/Build/Products/Release-iphonesimulator" KEYMANAPP_SIM_APP="$KEYMANAPP_SIM_FOLDER/Keyman.app" -KEYMANAPP_SIM_APP_DST="keyman-ios-simulator-${KEYMAN_VERSION_FOR_PR_FILENAME}.app.zip" +KEYMANAPP_SIM_APP_DST="keyman-ios-simulator-${KEYMAN_VERSION_FOR_FILENAME}.app.zip" echo "Zipping Keyman simulator artifact ${KEYMANAPP_SIM_APP} => ${UPLOAD_DIR}/${KEYMANAPP_SIM_APP_DST}..." cd "${KEYMANAPP_SIM_FOLDER}" @@ -107,14 +107,14 @@ cd "$WORK_DIR" if [ "${RELEASE_OEM_FIRSTVOICES}" = true ]; then FIRSTVOICESAPP_IPA="../oem/firstvoices/ios/build/Build/Products/Release-iphoneos/FirstVoices.ipa" - FIRSTVOICESAPP_IPA_DST="firstvoices-ios-${KEYMAN_VERSION_FOR_PR_FILENAME}.ipa" + FIRSTVOICESAPP_IPA_DST="firstvoices-ios-${KEYMAN_VERSION_FOR_FILENAME}.ipa" echo "Copying FirstVoices IPA ${FIRSTVOICESAPP_IPA} => ${UPLOAD_DIR}/${FIRSTVOICESAPP_IPA_DST}..." cp "${FIRSTVOICESAPP_IPA}" "${WORK_DIR}/${UPLOAD_DIR}/${FIRSTVOICESAPP_IPA_DST}" FIRSTVOICESAPP_SIM_FOLDER="../oem/firstvoices/ios/build/Build/Products/Release-iphonesimulator/" FIRSTVOICESAPP_SIM_APP="$FIRSTVOICESAPP_SIM_FOLDER/FirstVoices.app" - FIRSTVOICESAPP_SIM_APP_DST="firstvoices-ios-simulator-${KEYMAN_VERSION_FOR_PR_FILENAME}.app.zip" + FIRSTVOICESAPP_SIM_APP_DST="firstvoices-ios-simulator-${KEYMAN_VERSION_FOR_FILENAME}.app.zip" echo "Zipping FirstVoices simulator artifact ${FIRSTVOICESAPP_SIM_APP} => ${UPLOAD_DIR}/${KEYMANAPP_SIM_APP_DST}..." cd "${FIRSTVOICESAPP_SIM_FOLDER}" diff --git a/mac/Keyman4MacIM/make-km-dmg.sh b/mac/Keyman4MacIM/make-km-dmg.sh index 87339f754a..d949a96af3 100755 --- a/mac/Keyman4MacIM/make-km-dmg.sh +++ b/mac/Keyman4MacIM/make-km-dmg.sh @@ -212,7 +212,7 @@ if (( DETACH_SUCCESS < 999 )); then fi # Step 5 - Convert image to a compressed readonly DMG image -DMG_FILE_PATH="$DEST_DIR/keyman-$KEYMAN_VERSION_FOR_PR_FILENAME.dmg" +DMG_FILE_PATH="$DEST_DIR/keyman-$KEYMAN_VERSION_FOR_FILENAME.dmg" builder_echo info "Converting/compressing image to create \"$DMG_FILE_PATH\"" if [[ -e "$DMG_FILE_PATH" ]] ; then if [[ "$VERBOSITY" != "-quiet" ]] ; then diff --git a/mac/build.sh b/mac/build.sh index fa331acbe4..e032db998a 100755 --- a/mac/build.sh +++ b/mac/build.sh @@ -293,7 +293,7 @@ do_publish() { "${KM4MIM_BASE_PATH}/make-km-dmg.sh" local UPLOAD_PATH="${KM4MIM_BASE_PATH}/output/upload/${KEYMAN_VERSION}" - write_download_info "${UPLOAD_PATH}" "keyman-${KEYMAN_VERSION_FOR_PR_FILENAME}.dmg" "Keyman4MacIM" dmg mac + write_download_info "${UPLOAD_PATH}" "keyman-${KEYMAN_VERSION_FOR_FILENAME}.dmg" "Keyman4MacIM" dmg mac if builder_is_ci_build && builder_is_ci_build_level_release; then do_sentry diff --git a/oem/firstvoices/android/build.sh b/oem/firstvoices/android/build.sh index 5fa54578c1..2d8d30141f 100755 --- a/oem/firstvoices/android/build.sh +++ b/oem/firstvoices/android/build.sh @@ -38,7 +38,7 @@ if builder_is_debug_build; then TEST_FLAGS="-x assembleDebug lintDebug testDebug" fi -ARTIFACT="firstvoices-${KEYMAN_VERSION_FOR_PR_FILENAME}.apk" +ARTIFACT="firstvoices-${KEYMAN_VERSION_FOR_FILENAME}.apk" KEYBOARD_PACKAGE_ID="fv_all" KEYBOARDS_TARGET="oem/firstvoices/android/app/src/main/assets/${KEYBOARD_PACKAGE_ID}.kmp" diff --git a/oem/firstvoices/windows/src/inst/build.sh b/oem/firstvoices/windows/src/inst/build.sh index 99eadb4e48..cad414d39b 100755 --- a/oem/firstvoices/windows/src/inst/build.sh +++ b/oem/firstvoices/windows/src/inst/build.sh @@ -16,7 +16,7 @@ builder_describe "Installation files for FirstVoices Keyboards" \ # after all other builds complete builder_if_release_build_level builder_describe_outputs \ - publish /windows/release/${KEYMAN_VERSION}/firstvoices-${KEYMAN_VERSION_FOR_PR_FILENAME}.exe + publish /windows/release/${KEYMAN_VERSION}/firstvoices-${KEYMAN_VERSION_FOR_FILENAME}.exe builder_parse "$@" @@ -103,7 +103,7 @@ function copy-installer() { mkdir -p "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}" cp firstvoices.msi "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/firstvoices.msi" - cp firstvoices.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/firstvoices-${KEYMAN_VERSION_FOR_PR_FILENAME}.exe" + cp firstvoices.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/firstvoices-${KEYMAN_VERSION_FOR_FILENAME}.exe" verify-installer-signatures } diff --git a/resources/build/builder-basic.inc.sh b/resources/build/builder-basic.inc.sh index a1a268cb2a..d5d590de45 100644 --- a/resources/build/builder-basic.inc.sh +++ b/resources/build/builder-basic.inc.sh @@ -13,7 +13,7 @@ # KEYMAN_VERSION_GIT_TAG: Git tag for the release, "release@$KEYMAN_VERSION_WITH_TAG", e.g. "release@14.0.1-alpha-test-1234" # KEYMAN_ROOT: fully resolved root path of Keyman repository # KEYMAN_VERSION_ENVIRONMENT: One of: local, test, alpha, beta, stable -# KEYMAN_VERSION_FOR_PR_FILENAME: KEYMAN_VERSION, for release builds, or KEYMAN_VERSION_WITH_TAG, for PR and local builds; see #10521 +# KEYMAN_VERSION_FOR_FILENAME: KEYMAN_VERSION, for release builds, or KEYMAN_VERSION_WITH_TAG, for PR and local builds; see #10521 # UPLOAD_SENTRY: true - if KEYMAN_VERSION_ENVIRONMENT is one of alpha, beta, stable # false - if local, test. Indicates if debug artifacts should be uploaded to Sentry # BUILDER_OS: win|mac|linux -- current build environment @@ -121,9 +121,9 @@ function _builder_basic_find_version() { # the tag but that involves changing a number of other websites as dealing with # the rename there too. if builder_is_ci_release_build; then - KEYMAN_VERSION_FOR_PR_FILENAME="${KEYMAN_VERSION}" + KEYMAN_VERSION_FOR_FILENAME="${KEYMAN_VERSION}" else - KEYMAN_VERSION_FOR_PR_FILENAME="${KEYMAN_VERSION_WITH_TAG}" + KEYMAN_VERSION_FOR_FILENAME="${KEYMAN_VERSION_WITH_TAG}" fi readonly KEYMAN_VERSION @@ -136,7 +136,7 @@ function _builder_basic_find_version() { readonly KEYMAN_VERSION_WITH_TAG readonly KEYMAN_VERSION_ENVIRONMENT readonly KEYMAN_VERSION_GIT_TAG - readonly KEYMAN_VERSION_FOR_PR_FILENAME + readonly KEYMAN_VERSION_FOR_FILENAME # Export version strings so places like version.gradle can access them export KEYMAN_VERSION @@ -149,7 +149,7 @@ function _builder_basic_find_version() { export KEYMAN_VERSION_WITH_TAG export KEYMAN_VERSION_ENVIRONMENT export KEYMAN_VERSION_GIT_TAG - export KEYMAN_VERSION_FOR_PR_FILENAME + export KEYMAN_VERSION_FOR_FILENAME } function _builder_basic_find_tier() { @@ -188,7 +188,7 @@ function _builder_basic_print_version_utils_debug() { echo "KEYMAN_VERSION_WITH_TAG: $KEYMAN_VERSION_WITH_TAG" echo "KEYMAN_VERSION_GIT_TAG: $KEYMAN_VERSION_GIT_TAG" echo "KEYMAN_VERSION_ENVIRONMENT: $KEYMAN_VERSION_ENVIRONMENT" - echo "KEYMAN_VERSION_FOR_PR_FILENAME: $KEYMAN_VERSION_FOR_PR_FILENAME" + echo "KEYMAN_VERSION_FOR_FILENAME: $KEYMAN_VERSION_FOR_FILENAME" } # TODO: consolidate with buildLevel, see #14285 @@ -223,7 +223,7 @@ replaceVersionStrings() { s/\$KEYMAN_VERSION_GIT_TAG/$KEYMAN_VERSION_GIT_TAG/g; s/\$KEYMAN_VERSION_ENVIRONMENT/$KEYMAN_VERSION_ENVIRONMENT/g; s/\$KEYMAN_VERSION/$KEYMAN_VERSION/g; - s/\$KEYMAN_VERSION_FOR_PR_FILENAME/$KEYMAN_VERSION_FOR_PR_FILENAME/g; + s/\$KEYMAN_VERSION_FOR_FILENAME/$KEYMAN_VERSION_FOR_FILENAME/g; " "$infile" > "$outfile" } @@ -264,7 +264,7 @@ replaceVersionStrings_Mkver() { s/\$KEYMAN_VERSION_WITH_TAG/$KEYMAN_VERSION_WITH_TAG/g; s/\$KEYMAN_VERSION_GIT_TAG/$KEYMAN_VERSION_GIT_TAG/g; s/\$KEYMAN_VERSION_ENVIRONMENT/$KEYMAN_VERSION_ENVIRONMENT/g; - s/\$KEYMAN_VERSION_FOR_PR_FILENAME/$KEYMAN_VERSION_FOR_PR_FILENAME/g; + s/\$KEYMAN_VERSION_FOR_FILENAME/$KEYMAN_VERSION_FOR_FILENAME/g; s/\$KEYMAN_VERSION/$KEYMAN_VERSION_WIN/g; diff --git a/resources/teamcity/ios/keyman-ios-test.sh b/resources/teamcity/ios/keyman-ios-test.sh index 830fdc156b..63c48cc7dc 100755 --- a/resources/teamcity/ios/keyman-ios-test.sh +++ b/resources/teamcity/ios/keyman-ios-test.sh @@ -57,7 +57,7 @@ function __do_upload_to_testflight() { export LC_ALL=en_US.UTF-8 export LANG=en_US.UTF-8 - APP_IPA_DST="$4-ios-${KEYMAN_VERSION_FOR_PR_FILENAME}.ipa" + APP_IPA_DST="$4-ios-${KEYMAN_VERSION_FOR_FILENAME}.ipa" cd "upload/${KEYMAN_VERSION}" command -v fastlane diff --git a/windows/src/desktop/inst/build.sh b/windows/src/desktop/inst/build.sh index c16dbbbfe5..8e64342a22 100755 --- a/windows/src/desktop/inst/build.sh +++ b/windows/src/desktop/inst/build.sh @@ -15,7 +15,7 @@ builder_describe "Installation files for Keyman for Windows" \ # after all other builds complete builder_if_release_build_level builder_describe_outputs \ - publish /windows/release/${KEYMAN_VERSION}/keyman-${KEYMAN_VERSION_FOR_PR_FILENAME}.exe + publish /windows/release/${KEYMAN_VERSION}/keyman-${KEYMAN_VERSION_FOR_FILENAME}.exe builder_parse "$@" @@ -104,7 +104,7 @@ function copy-installer() { mkdir -p "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}" cp keymandesktop.msi "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/keymandesktop.msi" - cp keymandesktop.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/keyman-${KEYMAN_VERSION_FOR_PR_FILENAME}.exe" + cp keymandesktop.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/keyman-${KEYMAN_VERSION_FOR_FILENAME}.exe" cp "$WINDOWS_PROGRAM_APP/setup.exe" "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/setup.exe" verify-installer-signatures From d9622ff4b1fb7b4dbdff954b9b995e1f1064491b Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 10 Sep 2025 15:07:09 +0200 Subject: [PATCH 39/52] chore(developer): whitespace in builder-basic.inc.sh Build-bot: skip Co-authored-by: Eberhard Beilharz --- resources/build/builder-basic.inc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/build/builder-basic.inc.sh b/resources/build/builder-basic.inc.sh index d5d590de45..94441617b2 100644 --- a/resources/build/builder-basic.inc.sh +++ b/resources/build/builder-basic.inc.sh @@ -188,7 +188,7 @@ function _builder_basic_print_version_utils_debug() { echo "KEYMAN_VERSION_WITH_TAG: $KEYMAN_VERSION_WITH_TAG" echo "KEYMAN_VERSION_GIT_TAG: $KEYMAN_VERSION_GIT_TAG" echo "KEYMAN_VERSION_ENVIRONMENT: $KEYMAN_VERSION_ENVIRONMENT" - echo "KEYMAN_VERSION_FOR_FILENAME: $KEYMAN_VERSION_FOR_FILENAME" + echo "KEYMAN_VERSION_FOR_FILENAME: $KEYMAN_VERSION_FOR_FILENAME" } # TODO: consolidate with buildLevel, see #14285 From 416df255f875ccce5daf6e8b5d4ded3ed6eafbbd Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Wed, 10 Sep 2025 14:04:52 -0400 Subject: [PATCH 40/52] auto: increment master version to 19.0.117 Test-bot: skip Build-bot: skip --- HISTORY.md | 12 ++++++++++++ VERSION.md | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 793e0d8b86..692db6bb10 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,17 @@ # Keyman Version History +## 19.0.116 alpha 2025-09-10 + +* fix(windows): kmshell continues when sm is in waitingrestart and returns to idle (#14644) +* maint(linux): allow to skip API check (#14699) +* maint(resources): adding GitHub actions workflow for Docker builds (#14674) +* maint(developer): use versioned filenames for PR artifacts (#14701) +* maint(windows): use versioned filenames for PR artifacts (#14702) +* maint(android): use versioned filenames for PR artifacts (#14703) +* maint(ios): use versioned filenames for PR artifacts (#14704) +* maint(mac): use versioned filenames for PR artifacts (#14705) +* maint(resources): rename KEYMAN_VERSION_FOR_PR_FILENAME (#14720) + ## 19.0.115 alpha 2025-09-09 * fix(linux): don't crash if `kmp.json` is missing `keyboards` section (#14708) diff --git a/VERSION.md b/VERSION.md index 0ecff9843f..67eaf27978 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.116 \ No newline at end of file +19.0.117 \ No newline at end of file From 8321294e1e1e9e892587bc46c00105fb86e0d523 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 9 Sep 2025 10:34:55 +0700 Subject: [PATCH 41/52] chore(common): Update to Unicode 17.0 --- docs/minimum-versions.md | 2 +- resources/build/minimum-versions.inc.sh | 4 +- resources/standards-data/readme.md | 8 +- .../unicode-character-database/Blocks.txt | 14 +- .../UnicodeData.txt | 489 +++++++++++++++++- .../WordBreakProperty.txt | 61 ++- .../unicode-character-database/emoji-data.txt | 148 ++---- 7 files changed, 581 insertions(+), 145 deletions(-) diff --git a/docs/minimum-versions.md b/docs/minimum-versions.md index 96b6e820a5..19e0bf4420 100644 --- a/docs/minimum-versions.md +++ b/docs/minimum-versions.md @@ -73,6 +73,6 @@ https://help.keyman.com/developer/engine/android/latest-version/ | KEYMAN_VERSION_JAVA | 21 | | KEYMAN_VERSION_LANGTAGS | 2025-02-18 | | KEYMAN_VERSION_LANGUAGE_SUBTAG_REGISTRY | 2025-03-10 | -| KEYMAN_VERSION_UNICODE | 16.0.0 | +| KEYMAN_VERSION_UNICODE | 17.0.0 | > ### This file is auto-generated by publish-minimum-versions.sh. Do not modify diff --git a/resources/build/minimum-versions.inc.sh b/resources/build/minimum-versions.inc.sh index 5bcbf3cc12..8320c80954 100644 --- a/resources/build/minimum-versions.inc.sh +++ b/resources/build/minimum-versions.inc.sh @@ -3,7 +3,7 @@ # This is a list of minimum, maximum, and specific versions of any external # components or dependencies found in Keyman. # -# Original (outdated) reference +# Original (outdated) reference # https://docs.google.com/document/d/1Uy3U2YXeA4rCEbUbT7O6QzUGJDZBeViFecL8fjUOLkE/edit?usp=sharing # NOTE: After changing any values, you should run publish-minimum-versions.sh. @@ -50,4 +50,4 @@ KEYMAN_VERSION_CLDR=46 # LDML Keyboards version KEYMAN_VERSION_ISO639_3=2024-05-22 # Date of last import KEYMAN_VERSION_LANGTAGS=2025-02-18 # _version value KEYMAN_VERSION_LANGUAGE_SUBTAG_REGISTRY=2025-03-10 # Date from first line of language-subtag-registry -KEYMAN_VERSION_UNICODE=16.0.0 # UCD + related data +KEYMAN_VERSION_UNICODE=17.0.0 # UCD + related data diff --git a/resources/standards-data/readme.md b/resources/standards-data/readme.md index 51d2079929..caeff56d19 100644 --- a/resources/standards-data/readme.md +++ b/resources/standards-data/readme.md @@ -57,13 +57,9 @@ See also [ldml-keyboards/readme.md](ldml-keyboards/readme.md). ### unicode-character-database -Data from : +Data from : -```bash -UNICODE_VERSION=15.1.0 -wget https://www.unicode.org/Public/${UNICODE_VERSION}/ucd/Blocks.txt -O unicode-character-database/Blocks.txt -wget https://www.unicode.org/Public/${UNICODE_VERSION}/ucd/UnicodeData.txt -O unicode-character-database/UnicodeData.txt -``` +See [download.sh](./unicode-character-database/download.sh) ### windows-lcid-to-bcp-47 diff --git a/resources/standards-data/unicode-character-database/Blocks.txt b/resources/standards-data/unicode-character-database/Blocks.txt index 1517dde0ce..5c24ab60cb 100644 --- a/resources/standards-data/unicode-character-database/Blocks.txt +++ b/resources/standards-data/unicode-character-database/Blocks.txt @@ -1,6 +1,6 @@ -# Blocks-16.0.0.txt -# Date: 2024-02-02 -# © 2024 Unicode®, Inc. +# Blocks-17.0.0.txt +# Date: 2025-08-01 +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -228,6 +228,7 @@ FFF0..FFFF; Specials 108E0..108FF; Hatran 10900..1091F; Phoenician 10920..1093F; Lydian +10940..1095F; Sidetic 10980..1099F; Meroitic Hieroglyphs 109A0..109FF; Meroitic Cursive 10A00..10A5F; Kharoshthi @@ -279,11 +280,13 @@ FFF0..FFFF; Specials 11AB0..11ABF; Unified Canadian Aboriginal Syllabics Extended-A 11AC0..11AFF; Pau Cin Hau 11B00..11B5F; Devanagari Extended-A +11B60..11B7F; Sharada Supplement 11BC0..11BFF; Sunuwar 11C00..11C6F; Bhaiksuki 11C70..11CBF; Marchen 11D00..11D5F; Masaram Gondi 11D60..11DAF; Gunjala Gondi +11DB0..11DEF; Tolong Siki 11EE0..11EFF; Makasar 11F00..11F5F; Kawi 11FB0..11FBF; Lisu Supplement @@ -304,12 +307,14 @@ FFF0..FFFF; Specials 16B00..16B8F; Pahawh Hmong 16D40..16D7F; Kirat Rai 16E40..16E9F; Medefaidrin +16EA0..16EDF; Beria Erfe 16F00..16F9F; Miao 16FE0..16FFF; Ideographic Symbols and Punctuation 17000..187FF; Tangut 18800..18AFF; Tangut Components 18B00..18CFF; Khitan Small Script 18D00..18D7F; Tangut Supplement +18D80..18DFF; Tangut Components Supplement 1AFF0..1AFFF; Kana Extended-B 1B000..1B0FF; Kana Supplement 1B100..1B12F; Kana Extended-A @@ -318,6 +323,7 @@ FFF0..FFFF; Specials 1BC00..1BC9F; Duployan 1BCA0..1BCAF; Shorthand Format Controls 1CC00..1CEBF; Symbols for Legacy Computing Supplement +1CEC0..1CEFF; Miscellaneous Symbols Supplement 1CF00..1CFCF; Znamenny Musical Notation 1D000..1D0FF; Byzantine Musical Symbols 1D100..1D1FF; Musical Symbols @@ -336,6 +342,7 @@ FFF0..FFFF; Specials 1E2C0..1E2FF; Wancho 1E4D0..1E4FF; Nag Mundari 1E5D0..1E5FF; Ol Onal +1E6C0..1E6FF; Tai Yo 1E7E0..1E7FF; Ethiopic Extended-B 1E800..1E8DF; Mende Kikakui 1E900..1E95F; Adlam @@ -367,6 +374,7 @@ FFF0..FFFF; Specials 2F800..2FA1F; CJK Compatibility Ideographs Supplement 30000..3134F; CJK Unified Ideographs Extension G 31350..323AF; CJK Unified Ideographs Extension H +323B0..3347F; CJK Unified Ideographs Extension J E0000..E007F; Tags E0100..E01EF; Variation Selectors Supplement F0000..FFFFF; Supplementary Private Use Area-A diff --git a/resources/standards-data/unicode-character-database/UnicodeData.txt b/resources/standards-data/unicode-character-database/UnicodeData.txt index 64258a3739..fca68e3e15 100644 --- a/resources/standards-data/unicode-character-database/UnicodeData.txt +++ b/resources/standards-data/unicode-character-database/UnicodeData.txt @@ -659,7 +659,7 @@ 0292;LATIN SMALL LETTER EZH;Ll;0;L;;;;;N;LATIN SMALL LETTER YOGH;;01B7;;01B7 0293;LATIN SMALL LETTER EZH WITH CURL;Ll;0;L;;;;;N;LATIN SMALL LETTER YOGH CURL;;;; 0294;LATIN LETTER GLOTTAL STOP;Lo;0;L;;;;;N;;;;; -0295;LATIN LETTER PHARYNGEAL VOICED FRICATIVE;Ll;0;L;;;;;N;LATIN LETTER REVERSED GLOTTAL STOP;;;; +0295;LATIN LETTER PHARYNGEAL VOICED FRICATIVE;Lo;0;L;;;;;N;LATIN LETTER REVERSED GLOTTAL STOP;;;; 0296;LATIN LETTER INVERTED GLOTTAL STOP;Ll;0;L;;;;;N;;;;; 0297;LATIN LETTER STRETCHED C;Ll;0;L;;;;;N;;;;; 0298;LATIN LETTER BILABIAL CLICK;Ll;0;L;;;;;N;LATIN LETTER BULLSEYE;;;; @@ -2121,6 +2121,7 @@ 088C;ARABIC LETTER TAH WITH THREE DOTS BELOW;Lo;0;AL;;;;;N;;;;; 088D;ARABIC LETTER KEHEH WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; 088E;ARABIC VERTICAL TAIL;Lo;0;AL;;;;;N;;;;; +088F;ARABIC LETTER NOON WITH RING ABOVE;Lo;0;AL;;;;;N;;;;; 0890;ARABIC POUND MARK ABOVE;Cf;0;AN;;;;;N;;;;; 0891;ARABIC PIASTRE MARK ABOVE;Cf;0;AN;;;;;N;;;;; 0897;ARABIC PEPET;Mn;230;NSM;;;;;N;;;;; @@ -2862,6 +2863,7 @@ 0C58;TELUGU LETTER TSA;Lo;0;L;;;;;N;;;;; 0C59;TELUGU LETTER DZA;Lo;0;L;;;;;N;;;;; 0C5A;TELUGU LETTER RRRA;Lo;0;L;;;;;N;;;;; +0C5C;TELUGU ARCHAIC SHRII;Lo;0;L;;;;;N;;;;; 0C5D;TELUGU LETTER NAKAARA POLLU;Lo;0;L;;;;;N;;;;; 0C60;TELUGU LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; 0C61;TELUGU LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; @@ -2958,6 +2960,7 @@ 0CCD;KANNADA SIGN VIRAMA;Mn;9;NSM;;;;;N;;;;; 0CD5;KANNADA LENGTH MARK;Mc;0;L;;;;;N;;;;; 0CD6;KANNADA AI LENGTH MARK;Mc;0;L;;;;;N;;;;; +0CDC;KANNADA ARCHAIC SHRII;Lo;0;L;;;;;N;;;;; 0CDD;KANNADA LETTER NAKAARA POLLU;Lo;0;L;;;;;N;;;;; 0CDE;KANNADA LETTER FA;Lo;0;L;;;;;N;;;;; 0CE0;KANNADA LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; @@ -6137,6 +6140,33 @@ 1ACC;COMBINING LATIN SMALL LETTER INSULAR G;Mn;230;NSM;;;;;N;;;;; 1ACD;COMBINING LATIN SMALL LETTER INSULAR R;Mn;230;NSM;;;;;N;;;;; 1ACE;COMBINING LATIN SMALL LETTER INSULAR T;Mn;230;NSM;;;;;N;;;;; +1ACF;COMBINING DOUBLE CARON;Mn;230;NSM;;;;;N;;;;; +1AD0;COMBINING VERTICAL-LINE-ACUTE;Mn;230;NSM;;;;;N;;;;; +1AD1;COMBINING GRAVE-VERTICAL-LINE;Mn;230;NSM;;;;;N;;;;; +1AD2;COMBINING VERTICAL-LINE-GRAVE;Mn;230;NSM;;;;;N;;;;; +1AD3;COMBINING ACUTE-VERTICAL-LINE;Mn;230;NSM;;;;;N;;;;; +1AD4;COMBINING VERTICAL-LINE-MACRON;Mn;230;NSM;;;;;N;;;;; +1AD5;COMBINING MACRON-VERTICAL-LINE;Mn;230;NSM;;;;;N;;;;; +1AD6;COMBINING VERTICAL-LINE-ACUTE-GRAVE;Mn;230;NSM;;;;;N;;;;; +1AD7;COMBINING VERTICAL-LINE-GRAVE-ACUTE;Mn;230;NSM;;;;;N;;;;; +1AD8;COMBINING MACRON-ACUTE-GRAVE;Mn;230;NSM;;;;;N;;;;; +1AD9;COMBINING SHARP SIGN;Mn;230;NSM;;;;;N;;;;; +1ADA;COMBINING FLAT SIGN;Mn;230;NSM;;;;;N;;;;; +1ADB;COMBINING DOWN TACK ABOVE;Mn;230;NSM;;;;;N;;;;; +1ADC;COMBINING DIAERESIS WITH RAISED LEFT DOT;Mn;230;NSM;;;;;N;;;;; +1ADD;COMBINING DOT-AND-RING BELOW;Mn;220;NSM;;;;;N;;;;; +1AE0;COMBINING LEFT TACK ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE1;COMBINING RIGHT TACK ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE2;COMBINING MINUS SIGN ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE3;COMBINING INVERTED BRIDGE ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE4;COMBINING SQUARE ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE5;COMBINING SEAGULL ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE6;COMBINING DOUBLE ARCH BELOW;Mn;220;NSM;;;;;N;;;;; +1AE7;COMBINING DOUBLE ARCH ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE8;COMBINING EQUALS SIGN ABOVE;Mn;230;NSM;;;;;N;;;;; +1AE9;COMBINING LEFT ANGLE CENTRED ABOVE;Mn;230;NSM;;;;;N;;;;; +1AEA;COMBINING UPWARDS ARROW ABOVE;Mn;230;NSM;;;;;N;;;;; +1AEB;COMBINING DOUBLE RIGHTWARDS ARROW ABOVE;Mn;234;NSM;;;;;N;;;;; 1B00;BALINESE SIGN ULU RICEM;Mn;0;NSM;;;;;N;;;;; 1B01;BALINESE SIGN ULU CANDRA;Mn;0;NSM;;;;;N;;;;; 1B02;BALINESE SIGN CECEK;Mn;0;NSM;;;;;N;;;;; @@ -7545,6 +7575,7 @@ 20BE;LARI SIGN;Sc;0;ET;;;;;N;;;;; 20BF;BITCOIN SIGN;Sc;0;ET;;;;;N;;;;; 20C0;SOM SIGN;Sc;0;ET;;;;;N;;;;; +20C1;SAUDI RIYAL SIGN;Sc;0;ET;;;;;N;;;;; 20D0;COMBINING LEFT HARPOON ABOVE;Mn;230;NSM;;;;;N;NON-SPACING LEFT HARPOON ABOVE;;;; 20D1;COMBINING RIGHT HARPOON ABOVE;Mn;230;NSM;;;;;N;NON-SPACING RIGHT HARPOON ABOVE;;;; 20D2;COMBINING LONG VERTICAL LINE OVERLAY;Mn;1;NSM;;;;;N;NON-SPACING LONG VERTICAL BAR OVERLAY;;;; @@ -10239,6 +10270,7 @@ 2B93;NEWLINE RIGHT;So;0;ON;;;;;N;;;;; 2B94;FOUR CORNER ARROWS CIRCLING ANTICLOCKWISE;So;0;ON;;;;;N;;;;; 2B95;RIGHTWARDS BLACK ARROW;So;0;ON;;;;;N;;;;; +2B96;EQUALS SIGN WITH INFINITY ABOVE;So;0;ON;;;;;N;;;;; 2B97;SYMBOL FOR TYPE A ELECTRONICS;So;0;ON;;;;;N;;;;; 2B98;THREE-D TOP-LIGHTED LEFTWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; 2B99;THREE-D RIGHT-LIGHTED UPWARDS EQUILATERAL ARROWHEAD;So;0;ON;;;;;N;;;;; @@ -14274,10 +14306,14 @@ A7CA;LATIN SMALL LETTER S WITH SHORT STROKE OVERLAY;Ll;0;L;;;;;N;;;A7C9;;A7C9 A7CB;LATIN CAPITAL LETTER RAMS HORN;Lu;0;L;;;;;N;;;;0264; A7CC;LATIN CAPITAL LETTER S WITH DIAGONAL STROKE;Lu;0;L;;;;;N;;;;A7CD; A7CD;LATIN SMALL LETTER S WITH DIAGONAL STROKE;Ll;0;L;;;;;N;;;A7CC;;A7CC +A7CE;LATIN CAPITAL LETTER PHARYNGEAL VOICED FRICATIVE;Lu;0;L;;;;;N;;;;A7CF; +A7CF;LATIN SMALL LETTER PHARYNGEAL VOICED FRICATIVE;Ll;0;L;;;;;N;;;A7CE;;A7CE A7D0;LATIN CAPITAL LETTER CLOSED INSULAR G;Lu;0;L;;;;;N;;;;A7D1; A7D1;LATIN SMALL LETTER CLOSED INSULAR G;Ll;0;L;;;;;N;;;A7D0;;A7D0 -A7D3;LATIN SMALL LETTER DOUBLE THORN;Ll;0;L;;;;;N;;;;; -A7D5;LATIN SMALL LETTER DOUBLE WYNN;Ll;0;L;;;;;N;;;;; +A7D2;LATIN CAPITAL LETTER DOUBLE THORN;Lu;0;L;;;;;N;;;;A7D3; +A7D3;LATIN SMALL LETTER DOUBLE THORN;Ll;0;L;;;;;N;;;A7D2;;A7D2 +A7D4;LATIN CAPITAL LETTER DOUBLE WYNN;Lu;0;L;;;;;N;;;;A7D5; +A7D5;LATIN SMALL LETTER DOUBLE WYNN;Ll;0;L;;;;;N;;;A7D4;;A7D4 A7D6;LATIN CAPITAL LETTER MIDDLE SCOTS S;Lu;0;L;;;;;N;;;;A7D7; A7D7;LATIN SMALL LETTER MIDDLE SCOTS S;Ll;0;L;;;;;N;;;A7D6;;A7D6 A7D8;LATIN CAPITAL LETTER SIGMOID S;Lu;0;L;;;;;N;;;;A7D9; @@ -14285,6 +14321,7 @@ A7D9;LATIN SMALL LETTER SIGMOID S;Ll;0;L;;;;;N;;;A7D8;;A7D8 A7DA;LATIN CAPITAL LETTER LAMBDA;Lu;0;L;;;;;N;;;;A7DB; A7DB;LATIN SMALL LETTER LAMBDA;Ll;0;L;;;;;N;;;A7DA;;A7DA A7DC;LATIN CAPITAL LETTER LAMBDA WITH STROKE;Lu;0;L;;;;;N;;;;019B; +A7F1;MODIFIER LETTER CAPITAL S;Lm;0;L; 0053;;;;N;;;;; A7F2;MODIFIER LETTER CAPITAL C;Lm;0;L; 0043;;;;N;;;;; A7F3;MODIFIER LETTER CAPITAL F;Lm;0;L; 0046;;;;N;;;;; A7F4;MODIFIER LETTER CAPITAL Q;Lm;0;L; 0051;;;;N;;;;; @@ -15925,6 +15962,22 @@ FBBF;ARABIC SYMBOL RING;Sk;0;AL;;;;;N;;;;; FBC0;ARABIC SYMBOL SMALL TAH ABOVE;Sk;0;AL;;;;;N;;;;; FBC1;ARABIC SYMBOL SMALL TAH BELOW;Sk;0;AL;;;;;N;;;;; FBC2;ARABIC SYMBOL WASLA ABOVE;Sk;0;AL;;;;;N;;;;; +FBC3;ARABIC LIGATURE JALLA WA-ALAA;So;0;ON;;;;;N;;;;; +FBC4;ARABIC LIGATURE DAAMAT BARAKAATUHUM;So;0;ON;;;;;N;;;;; +FBC5;ARABIC LIGATURE RAHMATU ALLAAHI TAAALAA ALAYH;So;0;ON;;;;;N;;;;; +FBC6;ARABIC LIGATURE RAHMATU ALLAAHI ALAYHIM;So;0;ON;;;;;N;;;;; +FBC7;ARABIC LIGATURE RAHMATU ALLAAHI ALAYHIMAA;So;0;ON;;;;;N;;;;; +FBC8;ARABIC LIGATURE RAHIMAHUM ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBC9;ARABIC LIGATURE RAHIMAHUMAA ALLAAH;So;0;ON;;;;;N;;;;; +FBCA;ARABIC LIGATURE RAHIMAHUMAA ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBCB;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANHUM;So;0;ON;;;;;N;;;;; +FBCC;ARABIC LIGATURE HAFIZAHU ALLAAH;So;0;ON;;;;;N;;;;; +FBCD;ARABIC LIGATURE HAFIZAHU ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBCE;ARABIC LIGATURE HAFIZAHUM ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBCF;ARABIC LIGATURE HAFIZAHUMAA ALLAAHU TAAALAA;So;0;ON;;;;;N;;;;; +FBD0;ARABIC LIGATURE SALLALLAAHU TAAALAA ALAYHI WA-SALLAM;So;0;ON;;;;;N;;;;; +FBD1;ARABIC LIGATURE AJJAL ALLAAHU FARAJAHU ASH-SHAREEF;So;0;ON;;;;;N;;;;; +FBD2;ARABIC LIGATURE ALAYHI AR-RAHMAH;So;0;ON;;;;;N;;;;; FBD3;ARABIC LETTER NG ISOLATED FORM;Lo;0;AL; 06AD;;;;N;;;;; FBD4;ARABIC LETTER NG FINAL FORM;Lo;0;AL; 06AD;;;;N;;;;; FBD5;ARABIC LETTER NG INITIAL FORM;Lo;0;AL; 06AD;;;;N;;;;; @@ -16370,6 +16423,8 @@ FD8C;ARABIC LIGATURE MEEM WITH JEEM WITH HAH INITIAL FORM;Lo;0;AL; 0645 FD8D;ARABIC LIGATURE MEEM WITH JEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0645 062C 0645;;;;N;;;;; FD8E;ARABIC LIGATURE MEEM WITH KHAH WITH JEEM INITIAL FORM;Lo;0;AL; 0645 062E 062C;;;;N;;;;; FD8F;ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM;Lo;0;AL; 0645 062E 0645;;;;N;;;;; +FD90;ARABIC LIGATURE RAHMATU ALLAAHI ALAYH;So;0;ON;;;;;N;;;;; +FD91;ARABIC LIGATURE RAHMATU ALLAAHI ALAYHAA;So;0;ON;;;;;N;;;;; FD92;ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM;Lo;0;AL; 0645 062C 062E;;;;N;;;;; FD93;ARABIC LIGATURE HEH WITH MEEM WITH JEEM INITIAL FORM;Lo;0;AL; 0647 0645 062C;;;;N;;;;; FD94;ARABIC LIGATURE HEH WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0647 0645 0645;;;;N;;;;; @@ -16424,6 +16479,13 @@ FDC4;ARABIC LIGATURE AIN WITH JEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0639 FDC5;ARABIC LIGATURE SAD WITH MEEM WITH MEEM INITIAL FORM;Lo;0;AL; 0635 0645 0645;;;;N;;;;; FDC6;ARABIC LIGATURE SEEN WITH KHAH WITH YEH FINAL FORM;Lo;0;AL; 0633 062E 064A;;;;N;;;;; FDC7;ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM;Lo;0;AL; 0646 062C 064A;;;;N;;;;; +FDC8;ARABIC LIGATURE RAHIMAHU ALLAAH TAAALAA;So;0;ON;;;;;N;;;;; +FDC9;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANH;So;0;ON;;;;;N;;;;; +FDCA;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANHAA;So;0;ON;;;;;N;;;;; +FDCB;ARABIC LIGATURE RADI ALLAAHU TAAALAA ANHUMAA;So;0;ON;;;;;N;;;;; +FDCC;ARABIC LIGATURE SALLALLAHU ALAYHI WA-ALAA AALIHEE WA-SALLAM;So;0;ON;;;;;N;;;;; +FDCD;ARABIC LIGATURE AJJAL ALLAAHU TAAALAA FARAJAHU ASH-SHAREEF;So;0;ON;;;;;N;;;;; +FDCE;ARABIC LIGATURE KARRAMA ALLAAHU WAJHAH;So;0;ON;;;;;N;;;;; FDCF;ARABIC LIGATURE SALAAMUHU ALAYNAA;So;0;ON;;;;;N;;;;; FDF0;ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM;Lo;0;AL; 0635 0644 06D2;;;;N;;;;; FDF1;ARABIC LIGATURE QALA USED AS KORANIC STOP SIGN ISOLATED FORM;Lo;0;AL; 0642 0644 06D2;;;;N;;;;; @@ -18708,6 +18770,32 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 10938;LYDIAN LETTER NN;Lo;0;R;;;;;N;;;;; 10939;LYDIAN LETTER C;Lo;0;R;;;;;N;;;;; 1093F;LYDIAN TRIANGULAR MARK;Po;0;R;;;;;N;;;;; +10940;SIDETIC LETTER N01;Lo;0;R;;;;;N;;;;; +10941;SIDETIC LETTER N02;Lo;0;R;;;;;N;;;;; +10942;SIDETIC LETTER N03;Lo;0;R;;;;;N;;;;; +10943;SIDETIC LETTER N04;Lo;0;R;;;;;N;;;;; +10944;SIDETIC LETTER N05;Lo;0;R;;;;;N;;;;; +10945;SIDETIC LETTER N06;Lo;0;R;;;;;N;;;;; +10946;SIDETIC LETTER N07;Lo;0;R;;;;;N;;;;; +10947;SIDETIC LETTER N08;Lo;0;R;;;;;N;;;;; +10948;SIDETIC LETTER N09;Lo;0;R;;;;;N;;;;; +10949;SIDETIC LETTER N10;Lo;0;R;;;;;N;;;;; +1094A;SIDETIC LETTER N11;Lo;0;R;;;;;N;;;;; +1094B;SIDETIC LETTER N12;Lo;0;R;;;;;N;;;;; +1094C;SIDETIC LETTER N13;Lo;0;R;;;;;N;;;;; +1094D;SIDETIC LETTER N14;Lo;0;R;;;;;N;;;;; +1094E;SIDETIC LETTER N15;Lo;0;R;;;;;N;;;;; +1094F;SIDETIC LETTER N16;Lo;0;R;;;;;N;;;;; +10950;SIDETIC LETTER N17;Lo;0;R;;;;;N;;;;; +10951;SIDETIC LETTER N18;Lo;0;R;;;;;N;;;;; +10952;SIDETIC LETTER N19;Lo;0;R;;;;;N;;;;; +10953;SIDETIC LETTER N20;Lo;0;R;;;;;N;;;;; +10954;SIDETIC LETTER N21;Lo;0;R;;;;;N;;;;; +10955;SIDETIC LETTER N22;Lo;0;R;;;;;N;;;;; +10956;SIDETIC LETTER N23;Lo;0;R;;;;;N;;;;; +10957;SIDETIC LETTER N24;Lo;0;R;;;;;N;;;;; +10958;SIDETIC LETTER N25;Lo;0;R;;;;;N;;;;; +10959;SIDETIC LETTER N26;Lo;0;R;;;;;N;;;;; 10980;MEROITIC HIEROGLYPHIC LETTER A;Lo;0;R;;;;;N;;;;; 10981;MEROITIC HIEROGLYPHIC LETTER E;Lo;0;R;;;;;N;;;;; 10982;MEROITIC HIEROGLYPHIC LETTER I;Lo;0;R;;;;;N;;;;; @@ -19541,6 +19629,20 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 10EC2;ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; 10EC3;ARABIC LETTER TAH WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; 10EC4;ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW;Lo;0;AL;;;;;N;;;;; +10EC5;ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW;Lm;0;AL;;;;;N;;;;; +10EC6;ARABIC LETTER THIN NOON;Lo;0;AL;;;;;N;;;;; +10EC7;ARABIC LETTER YEH WITH FOUR DOTS BELOW;Lo;0;AL;;;;;N;;;;; +10ED0;ARABIC BIBLICAL END OF VERSE;Po;0;ON;;;;;N;;;;; +10ED1;ARABIC LIGATURE ALAYHAA AS-SALAATU WAS-SALAAM;So;0;ON;;;;;N;;;;; +10ED2;ARABIC LIGATURE ALAYHIM AS-SALAATU WAS-SALAAM;So;0;ON;;;;;N;;;;; +10ED3;ARABIC LIGATURE ALAYHIMAA AS-SALAATU WAS-SALAAM;So;0;ON;;;;;N;;;;; +10ED4;ARABIC LIGATURE QADDASA ALLAAHU SIRRAH;So;0;ON;;;;;N;;;;; +10ED5;ARABIC LIGATURE QUDDISA SIRRUHUM;So;0;ON;;;;;N;;;;; +10ED6;ARABIC LIGATURE QUDDISA SIRRUHUMAA;So;0;ON;;;;;N;;;;; +10ED7;ARABIC LIGATURE QUDDISAT ASRAARUHUM;So;0;ON;;;;;N;;;;; +10ED8;ARABIC LIGATURE NAWWARA ALLAAHU MARQADAH;So;0;ON;;;;;N;;;;; +10EFA;ARABIC DOUBLE VERTICAL BAR BELOW;Mn;220;NSM;;;;;N;;;;; +10EFB;ARABIC SMALL LOW NOON;Mn;220;NSM;;;;;N;;;;; 10EFC;ARABIC COMBINING ALEF OVERLAY;Mn;0;NSM;;;;;N;;;;; 10EFD;ARABIC SMALL LOW WORD SAKTA;Mn;220;NSM;;;;;N;;;;; 10EFE;ARABIC SMALL LOW WORD QASR;Mn;220;NSM;;;;;N;;;;; @@ -21521,6 +21623,14 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 11B07;DEVANAGARI SIGN WESTERN NINE-LIKE BHALE;Po;0;L;;;;;N;;;;; 11B08;DEVANAGARI SIGN REVERSED NINE-LIKE BHALE;Po;0;L;;;;;N;;;;; 11B09;DEVANAGARI SIGN MINDU;Po;0;L;;;;;N;;;;; +11B60;SHARADA VOWEL SIGN OE;Mn;0;NSM;;;;;N;;;;; +11B61;SHARADA VOWEL SIGN OOE;Mc;0;L;;;;;N;;;;; +11B62;SHARADA VOWEL SIGN UE;Mn;0;NSM;;;;;N;;;;; +11B63;SHARADA VOWEL SIGN UUE;Mn;0;NSM;;;;;N;;;;; +11B64;SHARADA VOWEL SIGN SHORT E;Mn;0;NSM;;;;;N;;;;; +11B65;SHARADA VOWEL SIGN SHORT O;Mc;0;L;;;;;N;;;;; +11B66;SHARADA VOWEL SIGN CANDRA E;Mn;0;NSM;;;;;N;;;;; +11B67;SHARADA VOWEL SIGN CANDRA O;Mc;0;L;;;;;N;;;;; 11BC0;SUNUWAR LETTER DEVI;Lo;0;L;;;;;N;;;;; 11BC1;SUNUWAR LETTER TASLA;Lo;0;L;;;;;N;;;;; 11BC2;SUNUWAR LETTER EKO;Lo;0;L;;;;;N;;;;; @@ -21868,6 +21978,60 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 11DA7;GUNJALA GONDI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; 11DA8;GUNJALA GONDI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 11DA9;GUNJALA GONDI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; +11DB0;TOLONG SIKI LETTER I;Lo;0;L;;;;;N;;;;; +11DB1;TOLONG SIKI LETTER E;Lo;0;L;;;;;N;;;;; +11DB2;TOLONG SIKI LETTER U;Lo;0;L;;;;;N;;;;; +11DB3;TOLONG SIKI LETTER O;Lo;0;L;;;;;N;;;;; +11DB4;TOLONG SIKI LETTER A;Lo;0;L;;;;;N;;;;; +11DB5;TOLONG SIKI LETTER AA;Lo;0;L;;;;;N;;;;; +11DB6;TOLONG SIKI LETTER P;Lo;0;L;;;;;N;;;;; +11DB7;TOLONG SIKI LETTER PH;Lo;0;L;;;;;N;;;;; +11DB8;TOLONG SIKI LETTER B;Lo;0;L;;;;;N;;;;; +11DB9;TOLONG SIKI LETTER BH;Lo;0;L;;;;;N;;;;; +11DBA;TOLONG SIKI LETTER M;Lo;0;L;;;;;N;;;;; +11DBB;TOLONG SIKI LETTER T;Lo;0;L;;;;;N;;;;; +11DBC;TOLONG SIKI LETTER TH;Lo;0;L;;;;;N;;;;; +11DBD;TOLONG SIKI LETTER D;Lo;0;L;;;;;N;;;;; +11DBE;TOLONG SIKI LETTER DH;Lo;0;L;;;;;N;;;;; +11DBF;TOLONG SIKI LETTER N;Lo;0;L;;;;;N;;;;; +11DC0;TOLONG SIKI LETTER TT;Lo;0;L;;;;;N;;;;; +11DC1;TOLONG SIKI LETTER TTH;Lo;0;L;;;;;N;;;;; +11DC2;TOLONG SIKI LETTER DD;Lo;0;L;;;;;N;;;;; +11DC3;TOLONG SIKI LETTER DDH;Lo;0;L;;;;;N;;;;; +11DC4;TOLONG SIKI LETTER NN;Lo;0;L;;;;;N;;;;; +11DC5;TOLONG SIKI LETTER C;Lo;0;L;;;;;N;;;;; +11DC6;TOLONG SIKI LETTER CH;Lo;0;L;;;;;N;;;;; +11DC7;TOLONG SIKI LETTER J;Lo;0;L;;;;;N;;;;; +11DC8;TOLONG SIKI LETTER JH;Lo;0;L;;;;;N;;;;; +11DC9;TOLONG SIKI LETTER NY;Lo;0;L;;;;;N;;;;; +11DCA;TOLONG SIKI LETTER K;Lo;0;L;;;;;N;;;;; +11DCB;TOLONG SIKI LETTER KH;Lo;0;L;;;;;N;;;;; +11DCC;TOLONG SIKI LETTER G;Lo;0;L;;;;;N;;;;; +11DCD;TOLONG SIKI LETTER GH;Lo;0;L;;;;;N;;;;; +11DCE;TOLONG SIKI LETTER NG;Lo;0;L;;;;;N;;;;; +11DCF;TOLONG SIKI LETTER Y;Lo;0;L;;;;;N;;;;; +11DD0;TOLONG SIKI LETTER R;Lo;0;L;;;;;N;;;;; +11DD1;TOLONG SIKI LETTER L;Lo;0;L;;;;;N;;;;; +11DD2;TOLONG SIKI LETTER V;Lo;0;L;;;;;N;;;;; +11DD3;TOLONG SIKI LETTER NNY;Lo;0;L;;;;;N;;;;; +11DD4;TOLONG SIKI LETTER S;Lo;0;L;;;;;N;;;;; +11DD5;TOLONG SIKI LETTER H;Lo;0;L;;;;;N;;;;; +11DD6;TOLONG SIKI LETTER X;Lo;0;L;;;;;N;;;;; +11DD7;TOLONG SIKI LETTER RR;Lo;0;L;;;;;N;;;;; +11DD8;TOLONG SIKI LETTER RRH;Lo;0;L;;;;;N;;;;; +11DD9;TOLONG SIKI SIGN SELA;Lm;0;L;;;;;N;;;;; +11DDA;TOLONG SIKI SIGN HECAKA;Lo;0;L;;;;;N;;;;; +11DDB;TOLONG SIKI UNGGA;Lo;0;L;;;;;N;;;;; +11DE0;TOLONG SIKI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; +11DE1;TOLONG SIKI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; +11DE2;TOLONG SIKI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; +11DE3;TOLONG SIKI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; +11DE4;TOLONG SIKI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; +11DE5;TOLONG SIKI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; +11DE6;TOLONG SIKI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; +11DE7;TOLONG SIKI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; +11DE8;TOLONG SIKI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; +11DE9;TOLONG SIKI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 11EE0;MAKASAR LETTER KA;Lo;0;L;;;;;N;;;;; 11EE1;MAKASAR LETTER GA;Lo;0;L;;;;;N;;;;; 11EE2;MAKASAR LETTER NGA;Lo;0;L;;;;;N;;;;; @@ -22088,8 +22252,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12035;CUNEIFORM SIGN ARAD TIMES KUR;Lo;0;L;;;;;N;;;;; 12036;CUNEIFORM SIGN ARKAB;Lo;0;L;;;;;N;;;;; 12037;CUNEIFORM SIGN ASAL2;Lo;0;L;;;;;N;;;;; -12038;CUNEIFORM SIGN ASH;Lo;0;L;;;;;N;;;;; -12039;CUNEIFORM SIGN ASH ZIDA TENU;Lo;0;L;;;;;N;;;;; +12038;CUNEIFORM SIGN ASH;Lo;0;L;;;;1;N;;;;; +12039;CUNEIFORM SIGN ASH ZIDA TENU;Lo;0;L;;;;1;N;;;;; 1203A;CUNEIFORM SIGN ASH KABA TENU;Lo;0;L;;;;;N;;;;; 1203B;CUNEIFORM SIGN ASH OVER ASH TUG2 OVER TUG2 TUG2 OVER TUG2 PAP;Lo;0;L;;;;;N;;;;; 1203C;CUNEIFORM SIGN ASH OVER ASH OVER ASH;Lo;0;L;;;;;N;;;;; @@ -22153,7 +22317,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12076;CUNEIFORM SIGN DIM2;Lo;0;L;;;;;N;;;;; 12077;CUNEIFORM SIGN DIN;Lo;0;L;;;;;N;;;;; 12078;CUNEIFORM SIGN DIN KASKAL U GUNU DISH;Lo;0;L;;;;;N;;;;; -12079;CUNEIFORM SIGN DISH;Lo;0;L;;;;;N;;;;; +12079;CUNEIFORM SIGN DISH;Lo;0;L;;;;1;N;;;;; 1207A;CUNEIFORM SIGN DU;Lo;0;L;;;;;N;;;;; 1207B;CUNEIFORM SIGN DU OVER DU;Lo;0;L;;;;;N;;;;; 1207C;CUNEIFORM SIGN DU GUNU;Lo;0;L;;;;;N;;;;; @@ -22582,12 +22746,12 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12223;CUNEIFORM SIGN MA2;Lo;0;L;;;;;N;;;;; 12224;CUNEIFORM SIGN MAH;Lo;0;L;;;;;N;;;;; 12225;CUNEIFORM SIGN MAR;Lo;0;L;;;;;N;;;;; -12226;CUNEIFORM SIGN MASH;Lo;0;L;;;;;N;;;;; +12226;CUNEIFORM SIGN MASH;Lo;0;L;;;;1/2;N;;;;; 12227;CUNEIFORM SIGN MASH2;Lo;0;L;;;;;N;;;;; 12228;CUNEIFORM SIGN ME;Lo;0;L;;;;;N;;;;; 12229;CUNEIFORM SIGN MES;Lo;0;L;;;;;N;;;;; 1222A;CUNEIFORM SIGN MI;Lo;0;L;;;;;N;;;;; -1222B;CUNEIFORM SIGN MIN;Lo;0;L;;;;;N;;;;; +1222B;CUNEIFORM SIGN MIN;Lo;0;L;;;;2;N;;;;; 1222C;CUNEIFORM SIGN MU;Lo;0;L;;;;;N;;;;; 1222D;CUNEIFORM SIGN MU OVER MU;Lo;0;L;;;;;N;;;;; 1222E;CUNEIFORM SIGN MUG;Lo;0;L;;;;;N;;;;; @@ -22811,9 +22975,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12308;CUNEIFORM SIGN TUM;Lo;0;L;;;;;N;;;;; 12309;CUNEIFORM SIGN TUR;Lo;0;L;;;;;N;;;;; 1230A;CUNEIFORM SIGN TUR OVER TUR ZA OVER ZA;Lo;0;L;;;;;N;;;;; -1230B;CUNEIFORM SIGN U;Lo;0;L;;;;;N;;;;; +1230B;CUNEIFORM SIGN U;Lo;0;L;;;;1;N;;;;; 1230C;CUNEIFORM SIGN U GUD;Lo;0;L;;;;;N;;;;; -1230D;CUNEIFORM SIGN U U U;Lo;0;L;;;;;N;;;;; +1230D;CUNEIFORM SIGN U U U;Lo;0;L;;;;3;N;;;;; 1230E;CUNEIFORM SIGN U OVER U PA OVER PA GAR OVER GAR;Lo;0;L;;;;;N;;;;; 1230F;CUNEIFORM SIGN U OVER U SUR OVER SUR;Lo;0;L;;;;;N;;;;; 12310;CUNEIFORM SIGN U OVER U U REVERSED OVER U REVERSED;Lo;0;L;;;;;N;;;;; @@ -22953,7 +23117,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 12396;CUNEIFORM SIGN SAG TIMES IGI GUNU;Lo;0;L;;;;;N;;;;; 12397;CUNEIFORM SIGN TI2;Lo;0;L;;;;;N;;;;; 12398;CUNEIFORM SIGN UM TIMES ME;Lo;0;L;;;;;N;;;;; -12399;CUNEIFORM SIGN U U;Lo;0;L;;;;;N;;;;; +12399;CUNEIFORM SIGN U U;Lo;0;L;;;;2;N;;;;; 12400;CUNEIFORM NUMERIC SIGN TWO ASH;Nl;0;L;;;;2;N;;;;; 12401;CUNEIFORM NUMERIC SIGN THREE ASH;Nl;0;L;;;;3;N;;;;; 12402;CUNEIFORM NUMERIC SIGN FOUR ASH;Nl;0;L;;;;4;N;;;;; @@ -30124,6 +30288,56 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 16E98;MEDEFAIDRIN FULL STOP;Po;0;L;;;;;N;;;;; 16E99;MEDEFAIDRIN SYMBOL AIVA;Po;0;L;;;;;N;;;;; 16E9A;MEDEFAIDRIN EXCLAMATION OH;Po;0;L;;;;;N;;;;; +16EA0;BERIA ERFE CAPITAL LETTER ARKAB;Lu;0;L;;;;;N;;;;16EBB; +16EA1;BERIA ERFE CAPITAL LETTER BASIGNA;Lu;0;L;;;;;N;;;;16EBC; +16EA2;BERIA ERFE CAPITAL LETTER DARBAI;Lu;0;L;;;;;N;;;;16EBD; +16EA3;BERIA ERFE CAPITAL LETTER EH;Lu;0;L;;;;;N;;;;16EBE; +16EA4;BERIA ERFE CAPITAL LETTER FITKO;Lu;0;L;;;;;N;;;;16EBF; +16EA5;BERIA ERFE CAPITAL LETTER GOWAY;Lu;0;L;;;;;N;;;;16EC0; +16EA6;BERIA ERFE CAPITAL LETTER HIRDEABO;Lu;0;L;;;;;N;;;;16EC1; +16EA7;BERIA ERFE CAPITAL LETTER I;Lu;0;L;;;;;N;;;;16EC2; +16EA8;BERIA ERFE CAPITAL LETTER DJAI;Lu;0;L;;;;;N;;;;16EC3; +16EA9;BERIA ERFE CAPITAL LETTER KOBO;Lu;0;L;;;;;N;;;;16EC4; +16EAA;BERIA ERFE CAPITAL LETTER LAKKO;Lu;0;L;;;;;N;;;;16EC5; +16EAB;BERIA ERFE CAPITAL LETTER MERI;Lu;0;L;;;;;N;;;;16EC6; +16EAC;BERIA ERFE CAPITAL LETTER NINI;Lu;0;L;;;;;N;;;;16EC7; +16EAD;BERIA ERFE CAPITAL LETTER GNA;Lu;0;L;;;;;N;;;;16EC8; +16EAE;BERIA ERFE CAPITAL LETTER NGAY;Lu;0;L;;;;;N;;;;16EC9; +16EAF;BERIA ERFE CAPITAL LETTER OI;Lu;0;L;;;;;N;;;;16ECA; +16EB0;BERIA ERFE CAPITAL LETTER PI;Lu;0;L;;;;;N;;;;16ECB; +16EB1;BERIA ERFE CAPITAL LETTER ERIGO;Lu;0;L;;;;;N;;;;16ECC; +16EB2;BERIA ERFE CAPITAL LETTER ERIGO TAMURA;Lu;0;L;;;;;N;;;;16ECD; +16EB3;BERIA ERFE CAPITAL LETTER SERI;Lu;0;L;;;;;N;;;;16ECE; +16EB4;BERIA ERFE CAPITAL LETTER SHEP;Lu;0;L;;;;;N;;;;16ECF; +16EB5;BERIA ERFE CAPITAL LETTER TATASOUE;Lu;0;L;;;;;N;;;;16ED0; +16EB6;BERIA ERFE CAPITAL LETTER UI;Lu;0;L;;;;;N;;;;16ED1; +16EB7;BERIA ERFE CAPITAL LETTER WASSE;Lu;0;L;;;;;N;;;;16ED2; +16EB8;BERIA ERFE CAPITAL LETTER AY;Lu;0;L;;;;;N;;;;16ED3; +16EBB;BERIA ERFE SMALL LETTER ARKAB;Ll;0;L;;;;;N;;;16EA0;;16EA0 +16EBC;BERIA ERFE SMALL LETTER BASIGNA;Ll;0;L;;;;;N;;;16EA1;;16EA1 +16EBD;BERIA ERFE SMALL LETTER DARBAI;Ll;0;L;;;;;N;;;16EA2;;16EA2 +16EBE;BERIA ERFE SMALL LETTER EH;Ll;0;L;;;;;N;;;16EA3;;16EA3 +16EBF;BERIA ERFE SMALL LETTER FITKO;Ll;0;L;;;;;N;;;16EA4;;16EA4 +16EC0;BERIA ERFE SMALL LETTER GOWAY;Ll;0;L;;;;;N;;;16EA5;;16EA5 +16EC1;BERIA ERFE SMALL LETTER HIRDEABO;Ll;0;L;;;;;N;;;16EA6;;16EA6 +16EC2;BERIA ERFE SMALL LETTER I;Ll;0;L;;;;;N;;;16EA7;;16EA7 +16EC3;BERIA ERFE SMALL LETTER DJAI;Ll;0;L;;;;;N;;;16EA8;;16EA8 +16EC4;BERIA ERFE SMALL LETTER KOBO;Ll;0;L;;;;;N;;;16EA9;;16EA9 +16EC5;BERIA ERFE SMALL LETTER LAKKO;Ll;0;L;;;;;N;;;16EAA;;16EAA +16EC6;BERIA ERFE SMALL LETTER MERI;Ll;0;L;;;;;N;;;16EAB;;16EAB +16EC7;BERIA ERFE SMALL LETTER NINI;Ll;0;L;;;;;N;;;16EAC;;16EAC +16EC8;BERIA ERFE SMALL LETTER GNA;Ll;0;L;;;;;N;;;16EAD;;16EAD +16EC9;BERIA ERFE SMALL LETTER NGAY;Ll;0;L;;;;;N;;;16EAE;;16EAE +16ECA;BERIA ERFE SMALL LETTER OI;Ll;0;L;;;;;N;;;16EAF;;16EAF +16ECB;BERIA ERFE SMALL LETTER PI;Ll;0;L;;;;;N;;;16EB0;;16EB0 +16ECC;BERIA ERFE SMALL LETTER ERIGO;Ll;0;L;;;;;N;;;16EB1;;16EB1 +16ECD;BERIA ERFE SMALL LETTER ERIGO TAMURA;Ll;0;L;;;;;N;;;16EB2;;16EB2 +16ECE;BERIA ERFE SMALL LETTER SERI;Ll;0;L;;;;;N;;;16EB3;;16EB3 +16ECF;BERIA ERFE SMALL LETTER SHEP;Ll;0;L;;;;;N;;;16EB4;;16EB4 +16ED0;BERIA ERFE SMALL LETTER TATASOUE;Ll;0;L;;;;;N;;;16EB5;;16EB5 +16ED1;BERIA ERFE SMALL LETTER UI;Ll;0;L;;;;;N;;;16EB6;;16EB6 +16ED2;BERIA ERFE SMALL LETTER WASSE;Ll;0;L;;;;;N;;;16EB7;;16EB7 +16ED3;BERIA ERFE SMALL LETTER AY;Ll;0;L;;;;;N;;;16EB8;;16EB8 16F00;MIAO LETTER PA;Lo;0;L;;;;;N;;;;; 16F01;MIAO LETTER BA;Lo;0;L;;;;;N;;;;; 16F02;MIAO LETTER YI PA;Lo;0;L;;;;;N;;;;; @@ -30280,8 +30494,13 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 16FE4;KHITAN SMALL SCRIPT FILLER;Mn;0;NSM;;;;;N;;;;; 16FF0;VIETNAMESE ALTERNATE READING MARK CA;Mc;6;L;;;;;N;;;;; 16FF1;VIETNAMESE ALTERNATE READING MARK NHAY;Mc;6;L;;;;;N;;;;; +16FF2;CHINESE SMALL SIMPLIFIED ER;Lm;0;L;;;;;N;;;;; +16FF3;CHINESE SMALL TRADITIONAL ER;Lm;0;L;;;;;N;;;;; +16FF4;YANGQIN SIGN SLOW ONE BEAT;Nl;0;L;;;;1;N;;;;; +16FF5;YANGQIN SIGN SLOW THREE HALF BEATS;Nl;0;L;;;;3/2;N;;;;; +16FF6;YANGQIN SIGN SLOW TWO BEATS;Nl;0;L;;;;2;N;;;;; 17000;;Lo;0;L;;;;;N;;;;; -187F7;;Lo;0;L;;;;;N;;;;; +187FF;;Lo;0;L;;;;;N;;;;; 18800;TANGUT COMPONENT-001;Lo;0;L;;;;;N;;;;; 18801;TANGUT COMPONENT-002;Lo;0;L;;;;;N;;;;; 18802;TANGUT COMPONENT-003;Lo;0;L;;;;;N;;;;; @@ -31522,7 +31741,122 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 18CD5;KHITAN SMALL SCRIPT CHARACTER-18CD5;Lo;0;L;;;;;N;;;;; 18CFF;KHITAN SMALL SCRIPT CHARACTER-18CFF;Lo;0;L;;;;;N;;;;; 18D00;;Lo;0;L;;;;;N;;;;; -18D08;;Lo;0;L;;;;;N;;;;; +18D1E;;Lo;0;L;;;;;N;;;;; +18D80;TANGUT COMPONENT-769;Lo;0;L;;;;;N;;;;; +18D81;TANGUT COMPONENT-770;Lo;0;L;;;;;N;;;;; +18D82;TANGUT COMPONENT-771;Lo;0;L;;;;;N;;;;; +18D83;TANGUT COMPONENT-772;Lo;0;L;;;;;N;;;;; +18D84;TANGUT COMPONENT-773;Lo;0;L;;;;;N;;;;; +18D85;TANGUT COMPONENT-774;Lo;0;L;;;;;N;;;;; +18D86;TANGUT COMPONENT-775;Lo;0;L;;;;;N;;;;; +18D87;TANGUT COMPONENT-776;Lo;0;L;;;;;N;;;;; +18D88;TANGUT COMPONENT-777;Lo;0;L;;;;;N;;;;; +18D89;TANGUT COMPONENT-778;Lo;0;L;;;;;N;;;;; +18D8A;TANGUT COMPONENT-779;Lo;0;L;;;;;N;;;;; +18D8B;TANGUT COMPONENT-780;Lo;0;L;;;;;N;;;;; +18D8C;TANGUT COMPONENT-781;Lo;0;L;;;;;N;;;;; +18D8D;TANGUT COMPONENT-782;Lo;0;L;;;;;N;;;;; +18D8E;TANGUT COMPONENT-783;Lo;0;L;;;;;N;;;;; +18D8F;TANGUT COMPONENT-784;Lo;0;L;;;;;N;;;;; +18D90;TANGUT COMPONENT-785;Lo;0;L;;;;;N;;;;; +18D91;TANGUT COMPONENT-786;Lo;0;L;;;;;N;;;;; +18D92;TANGUT COMPONENT-787;Lo;0;L;;;;;N;;;;; +18D93;TANGUT COMPONENT-788;Lo;0;L;;;;;N;;;;; +18D94;TANGUT COMPONENT-789;Lo;0;L;;;;;N;;;;; +18D95;TANGUT COMPONENT-790;Lo;0;L;;;;;N;;;;; +18D96;TANGUT COMPONENT-791;Lo;0;L;;;;;N;;;;; +18D97;TANGUT COMPONENT-792;Lo;0;L;;;;;N;;;;; +18D98;TANGUT COMPONENT-793;Lo;0;L;;;;;N;;;;; +18D99;TANGUT COMPONENT-794;Lo;0;L;;;;;N;;;;; +18D9A;TANGUT COMPONENT-795;Lo;0;L;;;;;N;;;;; +18D9B;TANGUT COMPONENT-796;Lo;0;L;;;;;N;;;;; +18D9C;TANGUT COMPONENT-797;Lo;0;L;;;;;N;;;;; +18D9D;TANGUT COMPONENT-798;Lo;0;L;;;;;N;;;;; +18D9E;TANGUT COMPONENT-799;Lo;0;L;;;;;N;;;;; +18D9F;TANGUT COMPONENT-800;Lo;0;L;;;;;N;;;;; +18DA0;TANGUT COMPONENT-801;Lo;0;L;;;;;N;;;;; +18DA1;TANGUT COMPONENT-802;Lo;0;L;;;;;N;;;;; +18DA2;TANGUT COMPONENT-803;Lo;0;L;;;;;N;;;;; +18DA3;TANGUT COMPONENT-804;Lo;0;L;;;;;N;;;;; +18DA4;TANGUT COMPONENT-805;Lo;0;L;;;;;N;;;;; +18DA5;TANGUT COMPONENT-806;Lo;0;L;;;;;N;;;;; +18DA6;TANGUT COMPONENT-807;Lo;0;L;;;;;N;;;;; +18DA7;TANGUT COMPONENT-808;Lo;0;L;;;;;N;;;;; +18DA8;TANGUT COMPONENT-809;Lo;0;L;;;;;N;;;;; +18DA9;TANGUT COMPONENT-810;Lo;0;L;;;;;N;;;;; +18DAA;TANGUT COMPONENT-811;Lo;0;L;;;;;N;;;;; +18DAB;TANGUT COMPONENT-812;Lo;0;L;;;;;N;;;;; +18DAC;TANGUT COMPONENT-813;Lo;0;L;;;;;N;;;;; +18DAD;TANGUT COMPONENT-814;Lo;0;L;;;;;N;;;;; +18DAE;TANGUT COMPONENT-815;Lo;0;L;;;;;N;;;;; +18DAF;TANGUT COMPONENT-816;Lo;0;L;;;;;N;;;;; +18DB0;TANGUT COMPONENT-817;Lo;0;L;;;;;N;;;;; +18DB1;TANGUT COMPONENT-818;Lo;0;L;;;;;N;;;;; +18DB2;TANGUT COMPONENT-819;Lo;0;L;;;;;N;;;;; +18DB3;TANGUT COMPONENT-820;Lo;0;L;;;;;N;;;;; +18DB4;TANGUT COMPONENT-821;Lo;0;L;;;;;N;;;;; +18DB5;TANGUT COMPONENT-822;Lo;0;L;;;;;N;;;;; +18DB6;TANGUT COMPONENT-823;Lo;0;L;;;;;N;;;;; +18DB7;TANGUT COMPONENT-824;Lo;0;L;;;;;N;;;;; +18DB8;TANGUT COMPONENT-825;Lo;0;L;;;;;N;;;;; +18DB9;TANGUT COMPONENT-826;Lo;0;L;;;;;N;;;;; +18DBA;TANGUT COMPONENT-827;Lo;0;L;;;;;N;;;;; +18DBB;TANGUT COMPONENT-828;Lo;0;L;;;;;N;;;;; +18DBC;TANGUT COMPONENT-829;Lo;0;L;;;;;N;;;;; +18DBD;TANGUT COMPONENT-830;Lo;0;L;;;;;N;;;;; +18DBE;TANGUT COMPONENT-831;Lo;0;L;;;;;N;;;;; +18DBF;TANGUT COMPONENT-832;Lo;0;L;;;;;N;;;;; +18DC0;TANGUT COMPONENT-833;Lo;0;L;;;;;N;;;;; +18DC1;TANGUT COMPONENT-834;Lo;0;L;;;;;N;;;;; +18DC2;TANGUT COMPONENT-835;Lo;0;L;;;;;N;;;;; +18DC3;TANGUT COMPONENT-836;Lo;0;L;;;;;N;;;;; +18DC4;TANGUT COMPONENT-837;Lo;0;L;;;;;N;;;;; +18DC5;TANGUT COMPONENT-838;Lo;0;L;;;;;N;;;;; +18DC6;TANGUT COMPONENT-839;Lo;0;L;;;;;N;;;;; +18DC7;TANGUT COMPONENT-840;Lo;0;L;;;;;N;;;;; +18DC8;TANGUT COMPONENT-841;Lo;0;L;;;;;N;;;;; +18DC9;TANGUT COMPONENT-842;Lo;0;L;;;;;N;;;;; +18DCA;TANGUT COMPONENT-843;Lo;0;L;;;;;N;;;;; +18DCB;TANGUT COMPONENT-844;Lo;0;L;;;;;N;;;;; +18DCC;TANGUT COMPONENT-845;Lo;0;L;;;;;N;;;;; +18DCD;TANGUT COMPONENT-846;Lo;0;L;;;;;N;;;;; +18DCE;TANGUT COMPONENT-847;Lo;0;L;;;;;N;;;;; +18DCF;TANGUT COMPONENT-848;Lo;0;L;;;;;N;;;;; +18DD0;TANGUT COMPONENT-849;Lo;0;L;;;;;N;;;;; +18DD1;TANGUT COMPONENT-850;Lo;0;L;;;;;N;;;;; +18DD2;TANGUT COMPONENT-851;Lo;0;L;;;;;N;;;;; +18DD3;TANGUT COMPONENT-852;Lo;0;L;;;;;N;;;;; +18DD4;TANGUT COMPONENT-853;Lo;0;L;;;;;N;;;;; +18DD5;TANGUT COMPONENT-854;Lo;0;L;;;;;N;;;;; +18DD6;TANGUT COMPONENT-855;Lo;0;L;;;;;N;;;;; +18DD7;TANGUT COMPONENT-856;Lo;0;L;;;;;N;;;;; +18DD8;TANGUT COMPONENT-857;Lo;0;L;;;;;N;;;;; +18DD9;TANGUT COMPONENT-858;Lo;0;L;;;;;N;;;;; +18DDA;TANGUT COMPONENT-859;Lo;0;L;;;;;N;;;;; +18DDB;TANGUT COMPONENT-860;Lo;0;L;;;;;N;;;;; +18DDC;TANGUT COMPONENT-861;Lo;0;L;;;;;N;;;;; +18DDD;TANGUT COMPONENT-862;Lo;0;L;;;;;N;;;;; +18DDE;TANGUT COMPONENT-863;Lo;0;L;;;;;N;;;;; +18DDF;TANGUT COMPONENT-864;Lo;0;L;;;;;N;;;;; +18DE0;TANGUT COMPONENT-865;Lo;0;L;;;;;N;;;;; +18DE1;TANGUT COMPONENT-866;Lo;0;L;;;;;N;;;;; +18DE2;TANGUT COMPONENT-867;Lo;0;L;;;;;N;;;;; +18DE3;TANGUT COMPONENT-868;Lo;0;L;;;;;N;;;;; +18DE4;TANGUT COMPONENT-869;Lo;0;L;;;;;N;;;;; +18DE5;TANGUT COMPONENT-870;Lo;0;L;;;;;N;;;;; +18DE6;TANGUT COMPONENT-871;Lo;0;L;;;;;N;;;;; +18DE7;TANGUT COMPONENT-872;Lo;0;L;;;;;N;;;;; +18DE8;TANGUT COMPONENT-873;Lo;0;L;;;;;N;;;;; +18DE9;TANGUT COMPONENT-874;Lo;0;L;;;;;N;;;;; +18DEA;TANGUT COMPONENT-875;Lo;0;L;;;;;N;;;;; +18DEB;TANGUT COMPONENT-876;Lo;0;L;;;;;N;;;;; +18DEC;TANGUT COMPONENT-877;Lo;0;L;;;;;N;;;;; +18DED;TANGUT COMPONENT-878;Lo;0;L;;;;;N;;;;; +18DEE;TANGUT COMPONENT-879;Lo;0;L;;;;;N;;;;; +18DEF;TANGUT COMPONENT-880;Lo;0;L;;;;;N;;;;; +18DF0;TANGUT COMPONENT-881;Lo;0;L;;;;;N;;;;; +18DF1;TANGUT COMPONENT-882;Lo;0;L;;;;;N;;;;; +18DF2;TANGUT COMPONENT-883;Lo;0;L;;;;;N;;;;; 1AFF0;KATAKANA LETTER MINNAN TONE-2;Lm;0;L;;;;;N;;;;; 1AFF1;KATAKANA LETTER MINNAN TONE-3;Lm;0;L;;;;;N;;;;; 1AFF2;KATAKANA LETTER MINNAN TONE-4;Lm;0;L;;;;;N;;;;; @@ -32629,6 +32963,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1CCF7;OUTLINED DIGIT SEVEN;Nd;0;EN; 0037;7;7;7;N;;;;; 1CCF8;OUTLINED DIGIT EIGHT;Nd;0;EN; 0038;8;8;8;N;;;;; 1CCF9;OUTLINED DIGIT NINE;Nd;0;EN; 0039;9;9;9;N;;;;; +1CCFA;SNAKE SYMBOL;So;0;ON;;;;;N;;;;; +1CCFB;FLYING SAUCER SYMBOL;So;0;ON;;;;;N;;;;; +1CCFC;NOSE SYMBOL;So;0;ON;;;;;N;;;;; 1CD00;BLOCK OCTANT-3;So;0;ON;;;;;N;;;;; 1CD01;BLOCK OCTANT-23;So;0;ON;;;;;N;;;;; 1CD02;BLOCK OCTANT-123;So;0;ON;;;;;N;;;;; @@ -33065,6 +33402,46 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1CEB1;KEYHOLE;So;0;ON;;;;;N;;;;; 1CEB2;OLD PERSONAL COMPUTER WITH MONITOR IN PORTRAIT ORIENTATION;So;0;ON;;;;;N;;;;; 1CEB3;BLACK RIGHT TRIANGLE CARET;So;0;ON;;;;;N;;;;; +1CEBA;FRAGILE SYMBOL;So;0;ON;;;;;N;;;;; +1CEBB;OFFICE BUILDING SYMBOL;So;0;ON;;;;;N;;;;; +1CEBC;TREE SYMBOL;So;0;ON;;;;;N;;;;; +1CEBD;APPLE SYMBOL;So;0;ON;;;;;N;;;;; +1CEBE;CHERRY SYMBOL;So;0;ON;;;;;N;;;;; +1CEBF;STRAWBERRY SYMBOL;So;0;ON;;;;;N;;;;; +1CEC0;HEBE;So;0;ON;;;;;N;;;;; +1CEC1;IRIS;So;0;ON;;;;;N;;;;; +1CEC2;FLORA;So;0;ON;;;;;N;;;;; +1CEC3;METIS;So;0;ON;;;;;N;;;;; +1CEC4;PARTHENOPE;So;0;ON;;;;;N;;;;; +1CEC5;VICTORIA;So;0;ON;;;;;N;;;;; +1CEC6;EGERIA;So;0;ON;;;;;N;;;;; +1CEC7;IRENE;So;0;ON;;;;;N;;;;; +1CEC8;EUNOMIA;So;0;ON;;;;;N;;;;; +1CEC9;PSYCHE;So;0;ON;;;;;N;;;;; +1CECA;THETIS;So;0;ON;;;;;N;;;;; +1CECB;MELPOMENE;So;0;ON;;;;;N;;;;; +1CECC;FORTUNA;So;0;ON;;;;;N;;;;; +1CECD;ASTRONOMICAL SYMBOL FOR ASTEROID PROSERPINA;So;0;ON;;;;;N;;;;; +1CECE;BELLONA;So;0;ON;;;;;N;;;;; +1CECF;AMPHITRITE;So;0;ON;;;;;N;;;;; +1CED0;LEUKOTHEA;So;0;ON;;;;;N;;;;; +1CEE0;GEOMANTIC FIGURE POPULUS;So;0;ON;;;;;N;;;;; +1CEE1;GEOMANTIC FIGURE TRISTITIA;So;0;ON;;;;;N;;;;; +1CEE2;GEOMANTIC FIGURE ALBUS;So;0;ON;;;;;N;;;;; +1CEE3;GEOMANTIC FIGURE FORTUNA MAJOR;So;0;ON;;;;;N;;;;; +1CEE4;GEOMANTIC FIGURE RUBEUS;So;0;ON;;;;;N;;;;; +1CEE5;GEOMANTIC FIGURE ACQUISITIO;So;0;ON;;;;;N;;;;; +1CEE6;GEOMANTIC FIGURE CONJUNCTIO;So;0;ON;;;;;N;;;;; +1CEE7;GEOMANTIC FIGURE CAPUT DRACONIS;So;0;ON;;;;;N;;;;; +1CEE8;GEOMANTIC FIGURE LAETITIA;So;0;ON;;;;;N;;;;; +1CEE9;GEOMANTIC FIGURE CARCER;So;0;ON;;;;;N;;;;; +1CEEA;GEOMANTIC FIGURE AMISSIO;So;0;ON;;;;;N;;;;; +1CEEB;GEOMANTIC FIGURE PUELLA;So;0;ON;;;;;N;;;;; +1CEEC;GEOMANTIC FIGURE FORTUNA MINOR;So;0;ON;;;;;N;;;;; +1CEED;GEOMANTIC FIGURE PUER;So;0;ON;;;;;N;;;;; +1CEEE;GEOMANTIC FIGURE CAUDA DRACONIS;So;0;ON;;;;;N;;;;; +1CEEF;GEOMANTIC FIGURE VIA;So;0;ON;;;;;N;;;;; +1CEF0;MEDIUM SMALL WHITE CIRCLE WITH HORIZONTAL BAR;Sm;0;ON;;;;;N;;;;; 1CF00;ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT;Mn;0;NSM;;;;;N;;;;; 1CF01;ZNAMENNY COMBINING MARK NIZKO S KRYZHEM ON LEFT;Mn;0;NSM;;;;;N;;;;; 1CF02;ZNAMENNY COMBINING MARK TSATA ON LEFT;Mn;0;NSM;;;;;N;;;;; @@ -36004,6 +36381,61 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1E5F9;OL ONAL DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1E5FA;OL ONAL DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1E5FF;OL ONAL ABBREVIATION SIGN;Po;0;L;;;;;N;;;;; +1E6C0;TAI YO LETTER LOW KO;Lo;0;L;;;;;N;;;;; +1E6C1;TAI YO LETTER HIGH KO;Lo;0;L;;;;;N;;;;; +1E6C2;TAI YO LETTER LOW KHO;Lo;0;L;;;;;N;;;;; +1E6C3;TAI YO LETTER HIGH KHO;Lo;0;L;;;;;N;;;;; +1E6C4;TAI YO LETTER GO;Lo;0;L;;;;;N;;;;; +1E6C5;TAI YO LETTER NGO;Lo;0;L;;;;;N;;;;; +1E6C6;TAI YO LETTER CO;Lo;0;L;;;;;N;;;;; +1E6C7;TAI YO LETTER LOW XO;Lo;0;L;;;;;N;;;;; +1E6C8;TAI YO LETTER HIGH XO;Lo;0;L;;;;;N;;;;; +1E6C9;TAI YO LETTER LOW NYO;Lo;0;L;;;;;N;;;;; +1E6CA;TAI YO LETTER HIGH NYO;Lo;0;L;;;;;N;;;;; +1E6CB;TAI YO LETTER DO;Lo;0;L;;;;;N;;;;; +1E6CC;TAI YO LETTER LOW TO;Lo;0;L;;;;;N;;;;; +1E6CD;TAI YO LETTER HIGH TO;Lo;0;L;;;;;N;;;;; +1E6CE;TAI YO LETTER THO;Lo;0;L;;;;;N;;;;; +1E6CF;TAI YO LETTER NO;Lo;0;L;;;;;N;;;;; +1E6D0;TAI YO LETTER BO;Lo;0;L;;;;;N;;;;; +1E6D1;TAI YO LETTER LOW PO;Lo;0;L;;;;;N;;;;; +1E6D2;TAI YO LETTER HIGH PO;Lo;0;L;;;;;N;;;;; +1E6D3;TAI YO LETTER PHO;Lo;0;L;;;;;N;;;;; +1E6D4;TAI YO LETTER LOW FO;Lo;0;L;;;;;N;;;;; +1E6D5;TAI YO LETTER HIGH FO;Lo;0;L;;;;;N;;;;; +1E6D6;TAI YO LETTER MO;Lo;0;L;;;;;N;;;;; +1E6D7;TAI YO LETTER YO;Lo;0;L;;;;;N;;;;; +1E6D8;TAI YO LETTER LO;Lo;0;L;;;;;N;;;;; +1E6D9;TAI YO LETTER VO;Lo;0;L;;;;;N;;;;; +1E6DA;TAI YO LETTER LOW HO;Lo;0;L;;;;;N;;;;; +1E6DB;TAI YO LETTER HIGH HO;Lo;0;L;;;;;N;;;;; +1E6DC;TAI YO LETTER QO;Lo;0;L;;;;;N;;;;; +1E6DD;TAI YO LETTER LOW KVO;Lo;0;L;;;;;N;;;;; +1E6DE;TAI YO LETTER HIGH KVO;Lo;0;L;;;;;N;;;;; +1E6E0;TAI YO LETTER AA;Lo;0;L;;;;;N;;;;; +1E6E1;TAI YO LETTER I;Lo;0;L;;;;;N;;;;; +1E6E2;TAI YO LETTER UE;Lo;0;L;;;;;N;;;;; +1E6E3;TAI YO SIGN UE;Mn;230;NSM;;;;;N;;;;; +1E6E4;TAI YO LETTER U;Lo;0;L;;;;;N;;;;; +1E6E5;TAI YO LETTER AE;Lo;0;L;;;;;N;;;;; +1E6E6;TAI YO SIGN AU;Mn;230;NSM;;;;;N;;;;; +1E6E7;TAI YO LETTER O;Lo;0;L;;;;;N;;;;; +1E6E8;TAI YO LETTER E;Lo;0;L;;;;;N;;;;; +1E6E9;TAI YO LETTER IA;Lo;0;L;;;;;N;;;;; +1E6EA;TAI YO LETTER UEA;Lo;0;L;;;;;N;;;;; +1E6EB;TAI YO LETTER UA;Lo;0;L;;;;;N;;;;; +1E6EC;TAI YO LETTER OO;Lo;0;L;;;;;N;;;;; +1E6ED;TAI YO LETTER AUE;Lo;0;L;;;;;N;;;;; +1E6EE;TAI YO SIGN AY;Mn;230;NSM;;;;;N;;;;; +1E6EF;TAI YO SIGN ANG;Mn;230;NSM;;;;;N;;;;; +1E6F0;TAI YO LETTER AN;Lo;0;L;;;;;N;;;;; +1E6F1;TAI YO LETTER AM;Lo;0;L;;;;;N;;;;; +1E6F2;TAI YO LETTER AK;Lo;0;L;;;;;N;;;;; +1E6F3;TAI YO LETTER AT;Lo;0;L;;;;;N;;;;; +1E6F4;TAI YO LETTER AP;Lo;0;L;;;;;N;;;;; +1E6F5;TAI YO SIGN OM;Mn;230;NSM;;;;;N;;;;; +1E6FE;TAI YO SYMBOL MUEANG;Lo;0;L;;;;;N;;;;; +1E6FF;TAI YO XAM LAI;Lm;0;L;;;;;N;;;;; 1E7E0;ETHIOPIC SYLLABLE HHYA;Lo;0;L;;;;;N;;;;; 1E7E1;ETHIOPIC SYLLABLE HHYU;Lo;0;L;;;;;N;;;;; 1E7E2;ETHIOPIC SYLLABLE HHYI;Lo;0;L;;;;;N;;;;; @@ -38079,6 +38511,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F6D5;HINDU TEMPLE;So;0;ON;;;;;N;;;;; 1F6D6;HUT;So;0;ON;;;;;N;;;;; 1F6D7;ELEVATOR;So;0;ON;;;;;N;;;;; +1F6D8;LANDSLIDE;So;0;ON;;;;;N;;;;; 1F6DC;WIRELESS;So;0;ON;;;;;N;;;;; 1F6DD;PLAYGROUND SLIDE;So;0;ON;;;;;N;;;;; 1F6DE;WHEEL;So;0;ON;;;;;N;;;;; @@ -38228,6 +38661,10 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F774;LOT OF FORTUNE;So;0;ON;;;;;N;;;;; 1F775;OCCULTATION;So;0;ON;;;;;N;;;;; 1F776;LUNAR ECLIPSE;So;0;ON;;;;;N;;;;; +1F777;VESTA FORM TWO;So;0;ON;;;;;N;;;;; +1F778;ASTRAEA FORM TWO;So;0;ON;;;;;N;;;;; +1F779;HYGIEA FORM TWO;So;0;ON;;;;;N;;;;; +1F77A;PARTHENOPE FORM TWO;So;0;ON;;;;;N;;;;; 1F77B;HAUMEA;So;0;ON;;;;;N;;;;; 1F77C;MAKEMAKE;So;0;ON;;;;;N;;;;; 1F77D;GONGGONG;So;0;ON;;;;;N;;;;; @@ -38498,6 +38935,15 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F8BB;SOUTH WEST ARROW FROM BAR;So;0;ON;;;;;N;;;;; 1F8C0;LEFTWARDS ARROW FROM DOWNWARDS ARROW;So;0;ON;;;;;N;;;;; 1F8C1;RIGHTWARDS ARROW FROM DOWNWARDS ARROW;So;0;ON;;;;;N;;;;; +1F8D0;LONG RIGHTWARDS ARROW OVER LONG LEFTWARDS ARROW;Sm;0;ON;;;;;N;;;;; +1F8D1;LONG RIGHTWARDS HARPOON OVER LONG LEFTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D2;LONG RIGHTWARDS HARPOON ABOVE SHORT LEFTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D3;SHORT RIGHTWARDS HARPOON ABOVE LONG LEFTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D4;LONG LEFTWARDS HARPOON ABOVE SHORT RIGHTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D5;SHORT LEFTWARDS HARPOON ABOVE LONG RIGHTWARDS HARPOON;Sm;0;ON;;;;;N;;;;; +1F8D6;LONG RIGHTWARDS ARROW THROUGH X;Sm;0;ON;;;;;N;;;;; +1F8D7;LONG RIGHTWARDS ARROW WITH DOUBLE SLASH;Sm;0;ON;;;;;N;;;;; +1F8D8;LONG LEFT RIGHT ARROW WITH DEPENDENT LOBE;Sm;0;ON;;;;;N;;;;; 1F900;CIRCLED CROSS FORMEE WITH FOUR DOTS;So;0;ON;;;;;N;;;;; 1F901;CIRCLED CROSS FORMEE WITH TWO DOTS;So;0;ON;;;;;N;;;;; 1F902;CIRCLED CROSS FORMEE;So;0;ON;;;;;N;;;;; @@ -38838,6 +39284,10 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FA51;BLACK CHESS KNIGHT-QUEEN;So;0;ON;;;;;N;;;;; 1FA52;BLACK CHESS KNIGHT-ROOK;So;0;ON;;;;;N;;;;; 1FA53;BLACK CHESS KNIGHT-BISHOP;So;0;ON;;;;;N;;;;; +1FA54;WHITE CHESS FERZ;So;0;ON;;;;;N;;;;; +1FA55;WHITE CHESS ALFIL;So;0;ON;;;;;N;;;;; +1FA56;BLACK CHESS FERZ;So;0;ON;;;;;N;;;;; +1FA57;BLACK CHESS ALFIL;So;0;ON;;;;;N;;;;; 1FA60;XIANGQI RED GENERAL;So;0;ON;;;;;N;;;;; 1FA61;XIANGQI RED MANDARIN;So;0;ON;;;;;N;;;;; 1FA62;XIANGQI RED ELEPHANT;So;0;ON;;;;;N;;;;; @@ -38875,6 +39325,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FA87;MARACAS;So;0;ON;;;;;N;;;;; 1FA88;FLUTE;So;0;ON;;;;;N;;;;; 1FA89;HARP;So;0;ON;;;;;N;;;;; +1FA8A;TROMBONE;So;0;ON;;;;;N;;;;; +1FA8E;TREASURE CHEST;So;0;ON;;;;;N;;;;; 1FA8F;SHOVEL;So;0;ON;;;;;N;;;;; 1FA90;RINGED PLANET;So;0;ON;;;;;N;;;;; 1FA91;CHAIR;So;0;ON;;;;;N;;;;; @@ -38931,6 +39383,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FAC4;PREGNANT PERSON;So;0;ON;;;;;N;;;;; 1FAC5;PERSON WITH CROWN;So;0;ON;;;;;N;;;;; 1FAC6;FINGERPRINT;So;0;ON;;;;;N;;;;; +1FAC8;HAIRY CREATURE;So;0;ON;;;;;N;;;;; +1FACD;ORCA;So;0;ON;;;;;N;;;;; 1FACE;MOOSE;So;0;ON;;;;;N;;;;; 1FACF;DONKEY;So;0;ON;;;;;N;;;;; 1FAD0;BLUEBERRIES;So;0;ON;;;;;N;;;;; @@ -38957,6 +39411,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FAE7;BUBBLES;So;0;ON;;;;;N;;;;; 1FAE8;SHAKING FACE;So;0;ON;;;;;N;;;;; 1FAE9;FACE WITH BAGS UNDER EYES;So;0;ON;;;;;N;;;;; +1FAEA;DISTORTED FACE;So;0;ON;;;;;N;;;;; +1FAEF;FIGHT CLOUD;So;0;ON;;;;;N;;;;; 1FAF0;HAND WITH INDEX FINGER AND THUMB CROSSED;So;0;ON;;;;;N;;;;; 1FAF1;RIGHTWARDS HAND;So;0;ON;;;;;N;;;;; 1FAF2;LEFTWARDS HAND;So;0;ON;;;;;N;;;;; @@ -39215,14 +39671,15 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FBF7;SEGMENTED DIGIT SEVEN;Nd;0;EN; 0037;7;7;7;N;;;;; 1FBF8;SEGMENTED DIGIT EIGHT;Nd;0;EN; 0038;8;8;8;N;;;;; 1FBF9;SEGMENTED DIGIT NINE;Nd;0;EN; 0039;9;9;9;N;;;;; +1FBFA;ALARM BELL SYMBOL;So;0;ON;;;;;N;;;;; 20000;;Lo;0;L;;;;;N;;;;; 2A6DF;;Lo;0;L;;;;;N;;;;; 2A700;;Lo;0;L;;;;;N;;;;; -2B739;;Lo;0;L;;;;;N;;;;; +2B73F;;Lo;0;L;;;;;N;;;;; 2B740;;Lo;0;L;;;;;N;;;;; 2B81D;;Lo;0;L;;;;;N;;;;; 2B820;;Lo;0;L;;;;;N;;;;; -2CEA1;;Lo;0;L;;;;;N;;;;; +2CEAD;;Lo;0;L;;;;;N;;;;; 2CEB0;;Lo;0;L;;;;;N;;;;; 2EBE0;;Lo;0;L;;;;;N;;;;; 2EBF0;;Lo;0;L;;;;;N;;;;; @@ -39773,6 +40230,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 3134A;;Lo;0;L;;;;;N;;;;; 31350;;Lo;0;L;;;;;N;;;;; 323AF;;Lo;0;L;;;;;N;;;;; +323B0;;Lo;0;L;;;;;N;;;;; +33479;;Lo;0;L;;;;;N;;;;; E0001;LANGUAGE TAG;Cf;0;BN;;;;;N;;;;; E0020;TAG SPACE;Cf;0;BN;;;;;N;;;;; E0021;TAG EXCLAMATION MARK;Cf;0;BN;;;;;N;;;;; diff --git a/resources/standards-data/unicode-character-database/WordBreakProperty.txt b/resources/standards-data/unicode-character-database/WordBreakProperty.txt index e557c3d0de..20fa24e375 100644 --- a/resources/standards-data/unicode-character-database/WordBreakProperty.txt +++ b/resources/standards-data/unicode-character-database/WordBreakProperty.txt @@ -1,6 +1,6 @@ -# WordBreakProperty-16.0.0.txt -# Date: 2024-07-29, 16:27:36 GMT -# © 2024 Unicode®, Inc. +# WordBreakProperty-17.0.0.txt +# Date: 2025-06-30, 06:20:49 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # @@ -283,7 +283,8 @@ FB46..FB4F ; Hebrew_Letter # Lo [10] HEBREW LETTER TSADI WITH DAGESH..HEBREW 1A7F ; Extend # Mn TAI THAM COMBINING CRYPTOGRAMMIC DOT 1AB0..1ABD ; Extend # Mn [14] COMBINING DOUBLED CIRCUMFLEX ACCENT..COMBINING PARENTHESES BELOW 1ABE ; Extend # Me COMBINING PARENTHESES OVERLAY -1ABF..1ACE ; Extend # Mn [16] COMBINING LATIN SMALL LETTER W BELOW..COMBINING LATIN SMALL LETTER INSULAR T +1ABF..1ADD ; Extend # Mn [31] COMBINING LATIN SMALL LETTER W BELOW..COMBINING DOT-AND-RING BELOW +1AE0..1AEB ; Extend # Mn [12] COMBINING LEFT TACK ABOVE..COMBINING DOUBLE RIGHTWARDS ARROW ABOVE 1B00..1B03 ; Extend # Mn [4] BALINESE SIGN ULU RICEM..BALINESE SIGN SURANG 1B04 ; Extend # Mc BALINESE SIGN BISAH 1B34 ; Extend # Mn BALINESE SIGN REREKAN @@ -409,7 +410,7 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT 10D24..10D27 ; Extend # Mn [4] HANIFI ROHINGYA SIGN HARBAHAY..HANIFI ROHINGYA SIGN TASSI 10D69..10D6D ; Extend # Mn [5] GARAY VOWEL SIGN E..GARAY CONSONANT NASALIZATION MARK 10EAB..10EAC ; Extend # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK -10EFC..10EFF ; Extend # Mn [4] ARABIC COMBINING ALEF OVERLAY..ARABIC SMALL LOW WORD MADDA +10EFA..10EFF ; Extend # Mn [6] ARABIC DOUBLE VERTICAL BAR BELOW..ARABIC SMALL LOW WORD MADDA 10F46..10F50 ; Extend # Mn [11] SOGDIAN COMBINING DOT BELOW..SOGDIAN COMBINING STROKE BELOW 10F82..10F85 ; Extend # Mn [4] OLD UYGHUR COMBINING DOT ABOVE..OLD UYGHUR COMBINING TWO DOTS BELOW 11000 ; Extend # Mc BRAHMI SIGN CANDRABINDU @@ -544,6 +545,12 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT 11A8A..11A96 ; Extend # Mn [13] SOYOMBO FINAL CONSONANT SIGN G..SOYOMBO SIGN ANUSVARA 11A97 ; Extend # Mc SOYOMBO SIGN VISARGA 11A98..11A99 ; Extend # Mn [2] SOYOMBO GEMINATION MARK..SOYOMBO SUBJOINER +11B60 ; Extend # Mn SHARADA VOWEL SIGN OE +11B61 ; Extend # Mc SHARADA VOWEL SIGN OOE +11B62..11B64 ; Extend # Mn [3] SHARADA VOWEL SIGN UE..SHARADA VOWEL SIGN SHORT E +11B65 ; Extend # Mc SHARADA VOWEL SIGN SHORT O +11B66 ; Extend # Mn SHARADA VOWEL SIGN CANDRA E +11B67 ; Extend # Mc SHARADA VOWEL SIGN CANDRA O 11C2F ; Extend # Mc BHAIKSUKI VOWEL SIGN AA 11C30..11C36 ; Extend # Mn [7] BHAIKSUKI VOWEL SIGN I..BHAIKSUKI VOWEL SIGN VOCALIC L 11C38..11C3D ; Extend # Mn [6] BHAIKSUKI VOWEL SIGN E..BHAIKSUKI SIGN ANUSVARA @@ -617,13 +624,17 @@ FF9E..FF9F ; Extend # Lm [2] HALFWIDTH KATAKANA VOICED SOUND MARK..HALFWIDT 1E2EC..1E2EF ; Extend # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI 1E4EC..1E4EF ; Extend # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH 1E5EE..1E5EF ; Extend # Mn [2] OL ONAL SIGN MU..OL ONAL SIGN IKIR +1E6E3 ; Extend # Mn TAI YO SIGN UE +1E6E6 ; Extend # Mn TAI YO SIGN AU +1E6EE..1E6EF ; Extend # Mn [2] TAI YO SIGN AY..TAI YO SIGN ANG +1E6F5 ; Extend # Mn TAI YO SIGN OM 1E8D0..1E8D6 ; Extend # Mn [7] MENDE KIKAKUI COMBINING NUMBER TEENS..MENDE KIKAKUI COMBINING NUMBER MILLIONS 1E944..1E94A ; Extend # Mn [7] ADLAM ALIF LENGTHENER..ADLAM NUKTA 1F3FB..1F3FF ; Extend # Sk [5] EMOJI MODIFIER FITZPATRICK TYPE-1-2..EMOJI MODIFIER FITZPATRICK TYPE-6 E0020..E007F ; Extend # Cf [96] TAG SPACE..CANCEL TAG E0100..E01EF ; Extend # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 -# Total code points: 2605 +# Total code points: 2647 # ================================================ @@ -679,6 +690,7 @@ FF71..FF9D ; Katakana # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAK 0061..007A ; ALetter # L& [26] LATIN SMALL LETTER A..LATIN SMALL LETTER Z 00AA ; ALetter # Lo FEMININE ORDINAL INDICATOR 00B5 ; ALetter # L& MICRO SIGN +00B8 ; ALetter # Sk CEDILLA 00BA ; ALetter # Lo MASCULINE ORDINAL INDICATOR 00C0..00D6 ; ALetter # L& [23] LATIN CAPITAL LETTER A WITH GRAVE..LATIN CAPITAL LETTER O WITH DIAERESIS 00D8..00F6 ; ALetter # L& [31] LATIN CAPITAL LETTER O WITH STROKE..LATIN SMALL LETTER O WITH DIAERESIS @@ -687,8 +699,8 @@ FF71..FF9D ; Katakana # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAK 01BC..01BF ; ALetter # L& [4] LATIN CAPITAL LETTER TONE FIVE..LATIN LETTER WYNN 01C0..01C3 ; ALetter # Lo [4] LATIN LETTER DENTAL CLICK..LATIN LETTER RETROFLEX CLICK 01C4..0293 ; ALetter # L& [208] LATIN CAPITAL LETTER DZ WITH CARON..LATIN SMALL LETTER EZH WITH CURL -0294 ; ALetter # Lo LATIN LETTER GLOTTAL STOP -0295..02AF ; ALetter # L& [27] LATIN LETTER PHARYNGEAL VOICED FRICATIVE..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL +0294..0295 ; ALetter # Lo [2] LATIN LETTER GLOTTAL STOP..LATIN LETTER PHARYNGEAL VOICED FRICATIVE +0296..02AF ; ALetter # L& [26] LATIN LETTER INVERTED GLOTTAL STOP..LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL 02B0..02C1 ; ALetter # Lm [18] MODIFIER LETTER SMALL H..MODIFIER LETTER REVERSED GLOTTAL STOP 02C2..02C5 ; ALetter # Sk [4] MODIFIER LETTER LEFT ARROWHEAD..MODIFIER LETTER DOWN ARROWHEAD 02C6..02D1 ; ALetter # Lm [12] MODIFIER LETTER CIRCUMFLEX ACCENT..MODIFIER LETTER HALF TRIANGULAR COLON @@ -745,7 +757,7 @@ FF71..FF9D ; Katakana # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAK 0840..0858 ; ALetter # Lo [25] MANDAIC LETTER HALQA..MANDAIC LETTER AIN 0860..086A ; ALetter # Lo [11] SYRIAC LETTER MALAYALAM NGA..SYRIAC LETTER MALAYALAM SSA 0870..0887 ; ALetter # Lo [24] ARABIC LETTER ALEF WITH ATTACHED FATHA..ARABIC BASELINE ROUND DOT -0889..088E ; ALetter # Lo [6] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC VERTICAL TAIL +0889..088F ; ALetter # Lo [7] ARABIC LETTER NOON WITH INVERTED SMALL V..ARABIC LETTER NOON WITH RING ABOVE 08A0..08C8 ; ALetter # Lo [41] ARABIC LETTER BEH WITH SMALL V BELOW..ARABIC LETTER GRAF 08C9 ; ALetter # Lm ARABIC SMALL FARSI YEH 0904..0939 ; ALetter # Lo [54] DEVANAGARI LETTER SHORT A..DEVANAGARI LETTER HA @@ -813,7 +825,7 @@ FF71..FF9D ; Katakana # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAK 0C2A..0C39 ; ALetter # Lo [16] TELUGU LETTER PA..TELUGU LETTER HA 0C3D ; ALetter # Lo TELUGU SIGN AVAGRAHA 0C58..0C5A ; ALetter # Lo [3] TELUGU LETTER TSA..TELUGU LETTER RRRA -0C5D ; ALetter # Lo TELUGU LETTER NAKAARA POLLU +0C5C..0C5D ; ALetter # Lo [2] TELUGU ARCHAIC SHRII..TELUGU LETTER NAKAARA POLLU 0C60..0C61 ; ALetter # Lo [2] TELUGU LETTER VOCALIC RR..TELUGU LETTER VOCALIC LL 0C80 ; ALetter # Lo KANNADA SIGN SPACING CANDRABINDU 0C85..0C8C ; ALetter # Lo [8] KANNADA LETTER A..KANNADA LETTER VOCALIC L @@ -822,7 +834,7 @@ FF71..FF9D ; Katakana # Lo [45] HALFWIDTH KATAKANA LETTER A..HALFWIDTH KATAK 0CAA..0CB3 ; ALetter # Lo [10] KANNADA LETTER PA..KANNADA LETTER LLA 0CB5..0CB9 ; ALetter # Lo [5] KANNADA LETTER VA..KANNADA LETTER HA 0CBD ; ALetter # Lo KANNADA SIGN AVAGRAHA -0CDD..0CDE ; ALetter # Lo [2] KANNADA LETTER NAKAARA POLLU..KANNADA LETTER FA +0CDC..0CDE ; ALetter # Lo [3] KANNADA ARCHAIC SHRII..KANNADA LETTER FA 0CE0..0CE1 ; ALetter # Lo [2] KANNADA LETTER VOCALIC RR..KANNADA LETTER VOCALIC LL 0CF1..0CF2 ; ALetter # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA 0D04..0D0C ; ALetter # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L @@ -1002,11 +1014,8 @@ A788 ; ALetter # Lm MODIFIER LETTER LOW CIRCUMFLEX ACCENT A789..A78A ; ALetter # Sk [2] MODIFIER LETTER COLON..MODIFIER LETTER SHORT EQUALS SIGN A78B..A78E ; ALetter # L& [4] LATIN CAPITAL LETTER SALTILLO..LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT A78F ; ALetter # Lo LATIN LETTER SINOLOGICAL DOT -A790..A7CD ; ALetter # L& [62] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN SMALL LETTER S WITH DIAGONAL STROKE -A7D0..A7D1 ; ALetter # L& [2] LATIN CAPITAL LETTER CLOSED INSULAR G..LATIN SMALL LETTER CLOSED INSULAR G -A7D3 ; ALetter # L& LATIN SMALL LETTER DOUBLE THORN -A7D5..A7DC ; ALetter # L& [8] LATIN SMALL LETTER DOUBLE WYNN..LATIN CAPITAL LETTER LAMBDA WITH STROKE -A7F2..A7F4 ; ALetter # Lm [3] MODIFIER LETTER CAPITAL C..MODIFIER LETTER CAPITAL Q +A790..A7DC ; ALetter # L& [77] LATIN CAPITAL LETTER N WITH DESCENDER..LATIN CAPITAL LETTER LAMBDA WITH STROKE +A7F1..A7F4 ; ALetter # Lm [4] MODIFIER LETTER CAPITAL S..MODIFIER LETTER CAPITAL Q A7F5..A7F6 ; ALetter # L& [2] LATIN CAPITAL LETTER REVERSED HALF H..LATIN SMALL LETTER REVERSED HALF H A7F7 ; ALetter # Lo LATIN EPIGRAPHIC LETTER SIDEWAYS I A7F8..A7F9 ; ALetter # Lm [2] MODIFIER LETTER CAPITAL H WITH STROKE..MODIFIER LETTER SMALL LIGATURE OE @@ -1115,6 +1124,7 @@ FFDA..FFDC ; ALetter # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 108F4..108F5 ; ALetter # Lo [2] HATRAN LETTER SHIN..HATRAN LETTER TAW 10900..10915 ; ALetter # Lo [22] PHOENICIAN LETTER ALF..PHOENICIAN LETTER TAU 10920..10939 ; ALetter # Lo [26] LYDIAN LETTER A..LYDIAN LETTER C +10940..10959 ; ALetter # Lo [26] SIDETIC LETTER N01..SIDETIC LETTER N26 10980..109B7 ; ALetter # Lo [56] MEROITIC HIEROGLYPHIC LETTER A..MEROITIC CURSIVE LETTER DA 109BE..109BF ; ALetter # Lo [2] MEROITIC CURSIVE LOGOGRAM RMT..MEROITIC CURSIVE LOGOGRAM IMN 10A00 ; ALetter # Lo KHAROSHTHI LETTER A @@ -1142,6 +1152,8 @@ FFDA..FFDC ; ALetter # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 10E80..10EA9 ; ALetter # Lo [42] YEZIDI LETTER ELIF..YEZIDI LETTER ET 10EB0..10EB1 ; ALetter # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE 10EC2..10EC4 ; ALetter # Lo [3] ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW +10EC5 ; ALetter # Lm ARABIC SMALL YEH BARREE WITH TWO DOTS BELOW +10EC6..10EC7 ; ALetter # Lo [2] ARABIC LETTER THIN NOON..ARABIC LETTER YEH WITH FOUR DOTS BELOW 10F00..10F1C ; ALetter # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL 10F27 ; ALetter # Lo OLD SOGDIAN LIGATURE AYIN-DALETH 10F30..10F45 ; ALetter # Lo [22] SOGDIAN LETTER ALEPH..SOGDIAN INDEPENDENT SHIN @@ -1232,6 +1244,9 @@ FFDA..FFDC ; ALetter # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 11D67..11D68 ; ALetter # Lo [2] GUNJALA GONDI LETTER EE..GUNJALA GONDI LETTER AI 11D6A..11D89 ; ALetter # Lo [32] GUNJALA GONDI LETTER OO..GUNJALA GONDI LETTER SA 11D98 ; ALetter # Lo GUNJALA GONDI OM +11DB0..11DD8 ; ALetter # Lo [41] TOLONG SIKI LETTER I..TOLONG SIKI LETTER RRH +11DD9 ; ALetter # Lm TOLONG SIKI SIGN SELA +11DDA..11DDB ; ALetter # Lo [2] TOLONG SIKI SIGN HECAKA..TOLONG SIKI UNGGA 11EE0..11EF2 ; ALetter # Lo [19] MAKASAR LETTER KA..MAKASAR ANGKA 11F02 ; ALetter # Lo KAWI SIGN REPHA 11F04..11F10 ; ALetter # Lo [13] KAWI LETTER A..KAWI LETTER O @@ -1258,6 +1273,8 @@ FFDA..FFDC ; ALetter # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 16D43..16D6A ; ALetter # Lo [40] KIRAT RAI LETTER A..KIRAT RAI VOWEL SIGN AU 16D6B..16D6C ; ALetter # Lm [2] KIRAT RAI SIGN VIRAMA..KIRAT RAI SIGN SAAT 16E40..16E7F ; ALetter # L& [64] MEDEFAIDRIN CAPITAL LETTER M..MEDEFAIDRIN SMALL LETTER Y +16EA0..16EB8 ; ALetter # L& [25] BERIA ERFE CAPITAL LETTER ARKAB..BERIA ERFE CAPITAL LETTER AY +16EBB..16ED3 ; ALetter # L& [25] BERIA ERFE SMALL LETTER ARKAB..BERIA ERFE SMALL LETTER AY 16F00..16F4A ; ALetter # Lo [75] MIAO LETTER PA..MIAO LETTER RTE 16F50 ; ALetter # Lo MIAO LETTER NASALIZATION 16F93..16F9F ; ALetter # Lm [13] MIAO LETTER TONE-2..MIAO LETTER REFORMED TONE-8 @@ -1311,6 +1328,13 @@ FFDA..FFDC ; ALetter # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 1E4EB ; ALetter # Lm NAG MUNDARI SIGN OJOD 1E5D0..1E5ED ; ALetter # Lo [30] OL ONAL LETTER O..OL ONAL LETTER EG 1E5F0 ; ALetter # Lo OL ONAL SIGN HODDOND +1E6C0..1E6DE ; ALetter # Lo [31] TAI YO LETTER LOW KO..TAI YO LETTER HIGH KVO +1E6E0..1E6E2 ; ALetter # Lo [3] TAI YO LETTER AA..TAI YO LETTER UE +1E6E4..1E6E5 ; ALetter # Lo [2] TAI YO LETTER U..TAI YO LETTER AE +1E6E7..1E6ED ; ALetter # Lo [7] TAI YO LETTER O..TAI YO LETTER AUE +1E6F0..1E6F4 ; ALetter # Lo [5] TAI YO LETTER AN..TAI YO LETTER AP +1E6FE ; ALetter # Lo TAI YO SYMBOL MUEANG +1E6FF ; ALetter # Lm TAI YO XAM LAI 1E7E0..1E7E6 ; ALetter # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO 1E7E8..1E7EB ; ALetter # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE 1E7ED..1E7EE ; ALetter # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE @@ -1355,7 +1379,7 @@ FFDA..FFDC ; ALetter # Lo [3] HALFWIDTH HANGUL LETTER EU..HALFWIDTH HANGUL 1F150..1F169 ; ALetter # So [26] NEGATIVE CIRCLED LATIN CAPITAL LETTER A..NEGATIVE CIRCLED LATIN CAPITAL LETTER Z 1F170..1F189 ; ALetter # So [26] NEGATIVE SQUARED LATIN CAPITAL LETTER A..NEGATIVE SQUARED LATIN CAPITAL LETTER Z -# Total code points: 33791 +# Total code points: 33973 # ================================================ @@ -1467,6 +1491,7 @@ FF10..FF19 ; Numeric # Nd [10] FULLWIDTH DIGIT ZERO..FULLWIDTH DIGIT NINE 11C50..11C59 ; Numeric # Nd [10] BHAIKSUKI DIGIT ZERO..BHAIKSUKI DIGIT NINE 11D50..11D59 ; Numeric # Nd [10] MASARAM GONDI DIGIT ZERO..MASARAM GONDI DIGIT NINE 11DA0..11DA9 ; Numeric # Nd [10] GUNJALA GONDI DIGIT ZERO..GUNJALA GONDI DIGIT NINE +11DE0..11DE9 ; Numeric # Nd [10] TOLONG SIKI DIGIT ZERO..TOLONG SIKI DIGIT NINE 11F50..11F59 ; Numeric # Nd [10] KAWI DIGIT ZERO..KAWI DIGIT NINE 16130..16139 ; Numeric # Nd [10] GURUNG KHEMA DIGIT ZERO..GURUNG KHEMA DIGIT NINE 16A60..16A69 ; Numeric # Nd [10] MRO DIGIT ZERO..MRO DIGIT NINE @@ -1482,7 +1507,7 @@ FF10..FF19 ; Numeric # Nd [10] FULLWIDTH DIGIT ZERO..FULLWIDTH DIGIT NINE 1E950..1E959 ; Numeric # Nd [10] ADLAM DIGIT ZERO..ADLAM DIGIT NINE 1FBF0..1FBF9 ; Numeric # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE -# Total code points: 774 +# Total code points: 784 # ================================================ diff --git a/resources/standards-data/unicode-character-database/emoji-data.txt b/resources/standards-data/unicode-character-database/emoji-data.txt index ff99028248..450252c4df 100644 --- a/resources/standards-data/unicode-character-database/emoji-data.txt +++ b/resources/standards-data/unicode-character-database/emoji-data.txt @@ -1,11 +1,11 @@ # emoji-data.txt -# Date: 2024-05-01, 21:25:24 GMT -# © 2024 Unicode®, Inc. +# Date: 2025-07-25, 17:54:31 GMT +# © 2025 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use and license, see https://www.unicode.org/terms_of_use.html # # Emoji Data for UTS #51 -# Used with Emoji Version 16.0 and subsequent minor revisions (if any) +# Version: 17.0 # # For documentation and usage, see https://www.unicode.org/reports/tr51 # @@ -340,6 +340,7 @@ 1F6D1..1F6D2 ; Emoji # E3.0 [2] (🛑..🛒) stop sign..shopping cart 1F6D5 ; Emoji # E12.0 [1] (🛕) hindu temple 1F6D6..1F6D7 ; Emoji # E13.0 [2] (🛖..🛗) hut..elevator +1F6D8 ; Emoji # E17.0 [1] (🛘) landslide 1F6DC ; Emoji # E15.0 [1] (🛜) wireless 1F6DD..1F6DF ; Emoji # E14.0 [3] (🛝..🛟) playground slide..ring buoy 1F6E0..1F6E5 ; Emoji # E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat @@ -408,6 +409,8 @@ 1FA83..1FA86 ; Emoji # E13.0 [4] (🪃..🪆) boomerang..nesting dolls 1FA87..1FA88 ; Emoji # E15.0 [2] (🪇..🪈) maracas..flute 1FA89 ; Emoji # E16.0 [1] (🪉) harp +1FA8A ; Emoji # E17.0 [1] (🪊) trombone +1FA8E ; Emoji # E17.0 [1] (🪎) treasure chest 1FA8F ; Emoji # E16.0 [1] (🪏) shovel 1FA90..1FA95 ; Emoji # E12.0 [6] (🪐..🪕) ringed planet..banjo 1FA96..1FAA8 ; Emoji # E13.0 [19] (🪖..🪨) military helmet..rock @@ -421,6 +424,8 @@ 1FAC0..1FAC2 ; Emoji # E13.0 [3] (🫀..🫂) anatomical heart..people hugging 1FAC3..1FAC5 ; Emoji # E14.0 [3] (🫃..🫅) pregnant man..person with crown 1FAC6 ; Emoji # E16.0 [1] (🫆) fingerprint +1FAC8 ; Emoji # E17.0 [1] (🫈) hairy creature +1FACD ; Emoji # E17.0 [1] (🫍) orca 1FACE..1FACF ; Emoji # E15.0 [2] (🫎..🫏) moose..donkey 1FAD0..1FAD6 ; Emoji # E13.0 [7] (🫐..🫖) blueberries..teapot 1FAD7..1FAD9 ; Emoji # E14.0 [3] (🫗..🫙) pouring liquid..jar @@ -430,10 +435,12 @@ 1FAE0..1FAE7 ; Emoji # E14.0 [8] (🫠..🫧) melting face..bubbles 1FAE8 ; Emoji # E15.0 [1] (🫨) shaking face 1FAE9 ; Emoji # E16.0 [1] (🫩) face with bags under eyes +1FAEA ; Emoji # E17.0 [1] (🫪) distorted face +1FAEF ; Emoji # E17.0 [1] (🫯) fight cloud 1FAF0..1FAF6 ; Emoji # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands 1FAF7..1FAF8 ; Emoji # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand -# Total elements: 1431 +# Total elements: 1438 # ================================================ @@ -640,6 +647,7 @@ 1F6D1..1F6D2 ; Emoji_Presentation # E3.0 [2] (🛑..🛒) stop sign..shopping cart 1F6D5 ; Emoji_Presentation # E12.0 [1] (🛕) hindu temple 1F6D6..1F6D7 ; Emoji_Presentation # E13.0 [2] (🛖..🛗) hut..elevator +1F6D8 ; Emoji_Presentation # E17.0 [1] (🛘) landslide 1F6DC ; Emoji_Presentation # E15.0 [1] (🛜) wireless 1F6DD..1F6DF ; Emoji_Presentation # E14.0 [3] (🛝..🛟) playground slide..ring buoy 1F6EB..1F6EC ; Emoji_Presentation # E1.0 [2] (🛫..🛬) airplane departure..airplane arrival @@ -704,6 +712,8 @@ 1FA83..1FA86 ; Emoji_Presentation # E13.0 [4] (🪃..🪆) boomerang..nesting dolls 1FA87..1FA88 ; Emoji_Presentation # E15.0 [2] (🪇..🪈) maracas..flute 1FA89 ; Emoji_Presentation # E16.0 [1] (🪉) harp +1FA8A ; Emoji_Presentation # E17.0 [1] (🪊) trombone +1FA8E ; Emoji_Presentation # E17.0 [1] (🪎) treasure chest 1FA8F ; Emoji_Presentation # E16.0 [1] (🪏) shovel 1FA90..1FA95 ; Emoji_Presentation # E12.0 [6] (🪐..🪕) ringed planet..banjo 1FA96..1FAA8 ; Emoji_Presentation # E13.0 [19] (🪖..🪨) military helmet..rock @@ -717,6 +727,8 @@ 1FAC0..1FAC2 ; Emoji_Presentation # E13.0 [3] (🫀..🫂) anatomical heart..people hugging 1FAC3..1FAC5 ; Emoji_Presentation # E14.0 [3] (🫃..🫅) pregnant man..person with crown 1FAC6 ; Emoji_Presentation # E16.0 [1] (🫆) fingerprint +1FAC8 ; Emoji_Presentation # E17.0 [1] (🫈) hairy creature +1FACD ; Emoji_Presentation # E17.0 [1] (🫍) orca 1FACE..1FACF ; Emoji_Presentation # E15.0 [2] (🫎..🫏) moose..donkey 1FAD0..1FAD6 ; Emoji_Presentation # E13.0 [7] (🫐..🫖) blueberries..teapot 1FAD7..1FAD9 ; Emoji_Presentation # E14.0 [3] (🫗..🫙) pouring liquid..jar @@ -726,10 +738,12 @@ 1FAE0..1FAE7 ; Emoji_Presentation # E14.0 [8] (🫠..🫧) melting face..bubbles 1FAE8 ; Emoji_Presentation # E15.0 [1] (🫨) shaking face 1FAE9 ; Emoji_Presentation # E16.0 [1] (🫩) face with bags under eyes +1FAEA ; Emoji_Presentation # E17.0 [1] (🫪) distorted face +1FAEF ; Emoji_Presentation # E17.0 [1] (🫯) fight cloud 1FAF0..1FAF6 ; Emoji_Presentation # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands 1FAF7..1FAF8 ; Emoji_Presentation # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand -# Total elements: 1212 +# Total elements: 1219 # ================================================ @@ -827,7 +841,6 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 21A9..21AA ; Extended_Pictographic# E0.6 [2] (↩️..↪️) right arrow curving left..left arrow curving right 231A..231B ; Extended_Pictographic# E0.6 [2] (⌚..⌛) watch..hourglass done 2328 ; Extended_Pictographic# E1.0 [1] (⌨️) keyboard -2388 ; Extended_Pictographic# E0.0 [1] (⎈) HELM SYMBOL 23CF ; Extended_Pictographic# E1.0 [1] (⏏️) eject button 23E9..23EC ; Extended_Pictographic# E0.6 [4] (⏩..⏬) fast-forward button..fast down button 23ED..23EE ; Extended_Pictographic# E0.7 [2] (⏭️..⏮️) next track button..last track button @@ -844,106 +857,63 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 2600..2601 ; Extended_Pictographic# E0.6 [2] (☀️..☁️) sun..cloud 2602..2603 ; Extended_Pictographic# E0.7 [2] (☂️..☃️) umbrella..snowman 2604 ; Extended_Pictographic# E1.0 [1] (☄️) comet -2605 ; Extended_Pictographic# E0.0 [1] (★) BLACK STAR -2607..260D ; Extended_Pictographic# E0.0 [7] (☇..☍) LIGHTNING..OPPOSITION 260E ; Extended_Pictographic# E0.6 [1] (☎️) telephone -260F..2610 ; Extended_Pictographic# E0.0 [2] (☏..☐) WHITE TELEPHONE..BALLOT BOX 2611 ; Extended_Pictographic# E0.6 [1] (☑️) check box with check -2612 ; Extended_Pictographic# E0.0 [1] (☒) BALLOT BOX WITH X 2614..2615 ; Extended_Pictographic# E0.6 [2] (☔..☕) umbrella with rain drops..hot beverage -2616..2617 ; Extended_Pictographic# E0.0 [2] (☖..☗) WHITE SHOGI PIECE..BLACK SHOGI PIECE 2618 ; Extended_Pictographic# E1.0 [1] (☘️) shamrock -2619..261C ; Extended_Pictographic# E0.0 [4] (☙..☜) REVERSED ROTATED FLORAL HEART BULLET..WHITE LEFT POINTING INDEX 261D ; Extended_Pictographic# E0.6 [1] (☝️) index pointing up -261E..261F ; Extended_Pictographic# E0.0 [2] (☞..☟) WHITE RIGHT POINTING INDEX..WHITE DOWN POINTING INDEX 2620 ; Extended_Pictographic# E1.0 [1] (☠️) skull and crossbones -2621 ; Extended_Pictographic# E0.0 [1] (☡) CAUTION SIGN 2622..2623 ; Extended_Pictographic# E1.0 [2] (☢️..☣️) radioactive..biohazard -2624..2625 ; Extended_Pictographic# E0.0 [2] (☤..☥) CADUCEUS..ANKH 2626 ; Extended_Pictographic# E1.0 [1] (☦️) orthodox cross -2627..2629 ; Extended_Pictographic# E0.0 [3] (☧..☩) CHI RHO..CROSS OF JERUSALEM 262A ; Extended_Pictographic# E0.7 [1] (☪️) star and crescent -262B..262D ; Extended_Pictographic# E0.0 [3] (☫..☭) FARSI SYMBOL..HAMMER AND SICKLE 262E ; Extended_Pictographic# E1.0 [1] (☮️) peace symbol 262F ; Extended_Pictographic# E0.7 [1] (☯️) yin yang -2630..2637 ; Extended_Pictographic# E0.0 [8] (☰..☷) TRIGRAM FOR HEAVEN..TRIGRAM FOR EARTH 2638..2639 ; Extended_Pictographic# E0.7 [2] (☸️..☹️) wheel of dharma..frowning face 263A ; Extended_Pictographic# E0.6 [1] (☺️) smiling face -263B..263F ; Extended_Pictographic# E0.0 [5] (☻..☿) BLACK SMILING FACE..MERCURY 2640 ; Extended_Pictographic# E4.0 [1] (♀️) female sign -2641 ; Extended_Pictographic# E0.0 [1] (♁) EARTH 2642 ; Extended_Pictographic# E4.0 [1] (♂️) male sign -2643..2647 ; Extended_Pictographic# E0.0 [5] (♃..♇) JUPITER..PLUTO 2648..2653 ; Extended_Pictographic# E0.6 [12] (♈..♓) Aries..Pisces -2654..265E ; Extended_Pictographic# E0.0 [11] (♔..♞) WHITE CHESS KING..BLACK CHESS KNIGHT 265F ; Extended_Pictographic# E11.0 [1] (♟️) chess pawn 2660 ; Extended_Pictographic# E0.6 [1] (♠️) spade suit -2661..2662 ; Extended_Pictographic# E0.0 [2] (♡..♢) WHITE HEART SUIT..WHITE DIAMOND SUIT 2663 ; Extended_Pictographic# E0.6 [1] (♣️) club suit -2664 ; Extended_Pictographic# E0.0 [1] (♤) WHITE SPADE SUIT 2665..2666 ; Extended_Pictographic# E0.6 [2] (♥️..♦️) heart suit..diamond suit -2667 ; Extended_Pictographic# E0.0 [1] (♧) WHITE CLUB SUIT 2668 ; Extended_Pictographic# E0.6 [1] (♨️) hot springs -2669..267A ; Extended_Pictographic# E0.0 [18] (♩..♺) QUARTER NOTE..RECYCLING SYMBOL FOR GENERIC MATERIALS 267B ; Extended_Pictographic# E0.6 [1] (♻️) recycling symbol -267C..267D ; Extended_Pictographic# E0.0 [2] (♼..♽) RECYCLED PAPER SYMBOL..PARTIALLY-RECYCLED PAPER SYMBOL 267E ; Extended_Pictographic# E11.0 [1] (♾️) infinity 267F ; Extended_Pictographic# E0.6 [1] (♿) wheelchair symbol -2680..2685 ; Extended_Pictographic# E0.0 [6] (⚀..⚅) DIE FACE-1..DIE FACE-6 -2690..2691 ; Extended_Pictographic# E0.0 [2] (⚐..⚑) WHITE FLAG..BLACK FLAG 2692 ; Extended_Pictographic# E1.0 [1] (⚒️) hammer and pick 2693 ; Extended_Pictographic# E0.6 [1] (⚓) anchor 2694 ; Extended_Pictographic# E1.0 [1] (⚔️) crossed swords 2695 ; Extended_Pictographic# E4.0 [1] (⚕️) medical symbol 2696..2697 ; Extended_Pictographic# E1.0 [2] (⚖️..⚗️) balance scale..alembic -2698 ; Extended_Pictographic# E0.0 [1] (⚘) FLOWER 2699 ; Extended_Pictographic# E1.0 [1] (⚙️) gear -269A ; Extended_Pictographic# E0.0 [1] (⚚) STAFF OF HERMES 269B..269C ; Extended_Pictographic# E1.0 [2] (⚛️..⚜️) atom symbol..fleur-de-lis -269D..269F ; Extended_Pictographic# E0.0 [3] (⚝..⚟) OUTLINED WHITE STAR..THREE LINES CONVERGING LEFT 26A0..26A1 ; Extended_Pictographic# E0.6 [2] (⚠️..⚡) warning..high voltage -26A2..26A6 ; Extended_Pictographic# E0.0 [5] (⚢..⚦) DOUBLED FEMALE SIGN..MALE WITH STROKE SIGN 26A7 ; Extended_Pictographic# E13.0 [1] (⚧️) transgender symbol -26A8..26A9 ; Extended_Pictographic# E0.0 [2] (⚨..⚩) VERTICAL MALE WITH STROKE SIGN..HORIZONTAL MALE WITH STROKE SIGN 26AA..26AB ; Extended_Pictographic# E0.6 [2] (⚪..⚫) white circle..black circle -26AC..26AF ; Extended_Pictographic# E0.0 [4] (⚬..⚯) MEDIUM SMALL WHITE CIRCLE..UNMARRIED PARTNERSHIP SYMBOL 26B0..26B1 ; Extended_Pictographic# E1.0 [2] (⚰️..⚱️) coffin..funeral urn -26B2..26BC ; Extended_Pictographic# E0.0 [11] (⚲..⚼) NEUTER..SESQUIQUADRATE 26BD..26BE ; Extended_Pictographic# E0.6 [2] (⚽..⚾) soccer ball..baseball -26BF..26C3 ; Extended_Pictographic# E0.0 [5] (⚿..⛃) SQUARED KEY..BLACK DRAUGHTS KING 26C4..26C5 ; Extended_Pictographic# E0.6 [2] (⛄..⛅) snowman without snow..sun behind cloud -26C6..26C7 ; Extended_Pictographic# E0.0 [2] (⛆..⛇) RAIN..BLACK SNOWMAN 26C8 ; Extended_Pictographic# E0.7 [1] (⛈️) cloud with lightning and rain -26C9..26CD ; Extended_Pictographic# E0.0 [5] (⛉..⛍) TURNED WHITE SHOGI PIECE..DISABLED CAR 26CE ; Extended_Pictographic# E0.6 [1] (⛎) Ophiuchus 26CF ; Extended_Pictographic# E0.7 [1] (⛏️) pick -26D0 ; Extended_Pictographic# E0.0 [1] (⛐) CAR SLIDING 26D1 ; Extended_Pictographic# E0.7 [1] (⛑️) rescue worker’s helmet -26D2 ; Extended_Pictographic# E0.0 [1] (⛒) CIRCLED CROSSING LANES 26D3 ; Extended_Pictographic# E0.7 [1] (⛓️) chains 26D4 ; Extended_Pictographic# E0.6 [1] (⛔) no entry -26D5..26E8 ; Extended_Pictographic# E0.0 [20] (⛕..⛨) ALTERNATE ONE-WAY LEFT WAY TRAFFIC..BLACK CROSS ON SHIELD 26E9 ; Extended_Pictographic# E0.7 [1] (⛩️) shinto shrine 26EA ; Extended_Pictographic# E0.6 [1] (⛪) church -26EB..26EF ; Extended_Pictographic# E0.0 [5] (⛫..⛯) CASTLE..MAP SYMBOL FOR LIGHTHOUSE 26F0..26F1 ; Extended_Pictographic# E0.7 [2] (⛰️..⛱️) mountain..umbrella on ground 26F2..26F3 ; Extended_Pictographic# E0.6 [2] (⛲..⛳) fountain..flag in hole 26F4 ; Extended_Pictographic# E0.7 [1] (⛴️) ferry 26F5 ; Extended_Pictographic# E0.6 [1] (⛵) sailboat -26F6 ; Extended_Pictographic# E0.0 [1] (⛶) SQUARE FOUR CORNERS 26F7..26F9 ; Extended_Pictographic# E0.7 [3] (⛷️..⛹️) skier..person bouncing ball 26FA ; Extended_Pictographic# E0.6 [1] (⛺) tent -26FB..26FC ; Extended_Pictographic# E0.0 [2] (⛻..⛼) JAPANESE BANK SYMBOL..HEADSTONE GRAVEYARD SYMBOL 26FD ; Extended_Pictographic# E0.6 [1] (⛽) fuel pump -26FE..2701 ; Extended_Pictographic# E0.0 [4] (⛾..✁) CUP ON BLACK SQUARE..UPPER BLADE SCISSORS 2702 ; Extended_Pictographic# E0.6 [1] (✂️) scissors -2703..2704 ; Extended_Pictographic# E0.0 [2] (✃..✄) LOWER BLADE SCISSORS..WHITE SCISSORS 2705 ; Extended_Pictographic# E0.6 [1] (✅) check mark button 2708..270C ; Extended_Pictographic# E0.6 [5] (✈️..✌️) airplane..victory hand 270D ; Extended_Pictographic# E0.7 [1] (✍️) writing hand -270E ; Extended_Pictographic# E0.0 [1] (✎) LOWER RIGHT PENCIL 270F ; Extended_Pictographic# E0.6 [1] (✏️) pencil -2710..2711 ; Extended_Pictographic# E0.0 [2] (✐..✑) UPPER RIGHT PENCIL..WHITE NIB 2712 ; Extended_Pictographic# E0.6 [1] (✒️) black nib 2714 ; Extended_Pictographic# E0.6 [1] (✔️) check mark 2716 ; Extended_Pictographic# E0.6 [1] (✖️) multiply @@ -959,7 +929,6 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 2757 ; Extended_Pictographic# E0.6 [1] (❗) red exclamation mark 2763 ; Extended_Pictographic# E1.0 [1] (❣️) heart exclamation 2764 ; Extended_Pictographic# E0.6 [1] (❤️) red heart -2765..2767 ; Extended_Pictographic# E0.0 [3] (❥..❧) ROTATED HEAVY BLACK HEART BULLET..ROTATED FLORAL HEART BULLET 2795..2797 ; Extended_Pictographic# E0.6 [3] (➕..➗) plus..divide 27A1 ; Extended_Pictographic# E0.6 [1] (➡️) right arrow 27B0 ; Extended_Pictographic# E0.6 [1] (➰) curly loop @@ -973,19 +942,19 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 303D ; Extended_Pictographic# E0.6 [1] (〽️) part alternation mark 3297 ; Extended_Pictographic# E0.6 [1] (㊗️) Japanese “congratulations” button 3299 ; Extended_Pictographic# E0.6 [1] (㊙️) Japanese “secret” button -1F000..1F003 ; Extended_Pictographic# E0.0 [4] (🀀..🀃) MAHJONG TILE EAST WIND..MAHJONG TILE NORTH WIND 1F004 ; Extended_Pictographic# E0.6 [1] (🀄) mahjong red dragon -1F005..1F0CE ; Extended_Pictographic# E0.0 [202] (🀅..🃎) MAHJONG TILE GREEN DRAGON..PLAYING CARD KING OF DIAMONDS +1F02C..1F02F ; Extended_Pictographic# E0.0 [4] (🀬..🀯) .. +1F094..1F09F ; Extended_Pictographic# E0.0 [12] (🂔..🂟) .. +1F0AF..1F0B0 ; Extended_Pictographic# E0.0 [2] (🂯..🂰) .. +1F0C0 ; Extended_Pictographic# E0.0 [1] (🃀) 1F0CF ; Extended_Pictographic# E0.6 [1] (🃏) joker -1F0D0..1F0FF ; Extended_Pictographic# E0.0 [48] (🃐..🃿) .. -1F10D..1F10F ; Extended_Pictographic# E0.0 [3] (🄍..🄏) CIRCLED ZERO WITH SLASH..CIRCLED DOLLAR SIGN WITH OVERLAID BACKSLASH -1F12F ; Extended_Pictographic# E0.0 [1] (🄯) COPYLEFT SYMBOL -1F16C..1F16F ; Extended_Pictographic# E0.0 [4] (🅬..🅯) RAISED MR SIGN..CIRCLED HUMAN FIGURE +1F0D0 ; Extended_Pictographic# E0.0 [1] (🃐) +1F0F6..1F0FF ; Extended_Pictographic# E0.0 [10] (🃶..🃿) .. 1F170..1F171 ; Extended_Pictographic# E0.6 [2] (🅰️..🅱️) A button (blood type)..B button (blood type) 1F17E..1F17F ; Extended_Pictographic# E0.6 [2] (🅾️..🅿️) O button (blood type)..P button 1F18E ; Extended_Pictographic# E0.6 [1] (🆎) AB button (blood type) 1F191..1F19A ; Extended_Pictographic# E0.6 [10] (🆑..🆚) CL button..VS button -1F1AD..1F1E5 ; Extended_Pictographic# E0.0 [57] (🆭..🇥) MASK WORK SYMBOL.. +1F1AE..1F1E5 ; Extended_Pictographic# E0.0 [56] (🆮..🇥) .. 1F201..1F202 ; Extended_Pictographic# E0.6 [2] (🈁..🈂️) Japanese “here” button..Japanese “service charge” button 1F203..1F20F ; Extended_Pictographic# E0.0 [13] (🈃..🈏) .. 1F21A ; Extended_Pictographic# E0.6 [1] (🈚) Japanese “free of charge” button @@ -994,7 +963,8 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F23C..1F23F ; Extended_Pictographic# E0.0 [4] (🈼..🈿) .. 1F249..1F24F ; Extended_Pictographic# E0.0 [7] (🉉..🉏) .. 1F250..1F251 ; Extended_Pictographic# E0.6 [2] (🉐..🉑) Japanese “bargain” button..Japanese “acceptable” button -1F252..1F2FF ; Extended_Pictographic# E0.0 [174] (🉒..🋿) .. +1F252..1F25F ; Extended_Pictographic# E0.0 [14] (🉒..🉟) .. +1F266..1F2FF ; Extended_Pictographic# E0.0 [154] (🉦..🋿) .. 1F300..1F30C ; Extended_Pictographic# E0.6 [13] (🌀..🌌) cyclone..milky way 1F30D..1F30E ; Extended_Pictographic# E0.7 [2] (🌍..🌎) globe showing Europe-Africa..globe showing Americas 1F30F ; Extended_Pictographic# E0.6 [1] (🌏) globe showing Asia-Australia @@ -1010,7 +980,6 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F31D..1F31E ; Extended_Pictographic# E1.0 [2] (🌝..🌞) full moon face..sun with face 1F31F..1F320 ; Extended_Pictographic# E0.6 [2] (🌟..🌠) glowing star..shooting star 1F321 ; Extended_Pictographic# E0.7 [1] (🌡️) thermometer -1F322..1F323 ; Extended_Pictographic# E0.0 [2] (🌢..🌣) BLACK DROPLET..WHITE SUN 1F324..1F32C ; Extended_Pictographic# E0.7 [9] (🌤️..🌬️) sun behind small cloud..wind face 1F32D..1F32F ; Extended_Pictographic# E1.0 [3] (🌭..🌯) hot dog..burrito 1F330..1F331 ; Extended_Pictographic# E0.6 [2] (🌰..🌱) chestnut..seedling @@ -1026,11 +995,8 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F37D ; Extended_Pictographic# E0.7 [1] (🍽️) fork and knife with plate 1F37E..1F37F ; Extended_Pictographic# E1.0 [2] (🍾..🍿) bottle with popping cork..popcorn 1F380..1F393 ; Extended_Pictographic# E0.6 [20] (🎀..🎓) ribbon..graduation cap -1F394..1F395 ; Extended_Pictographic# E0.0 [2] (🎔..🎕) HEART WITH TIP ON THE LEFT..BOUQUET OF FLOWERS 1F396..1F397 ; Extended_Pictographic# E0.7 [2] (🎖️..🎗️) military medal..reminder ribbon -1F398 ; Extended_Pictographic# E0.0 [1] (🎘) MUSICAL KEYBOARD WITH JACKS 1F399..1F39B ; Extended_Pictographic# E0.7 [3] (🎙️..🎛️) studio microphone..control knobs -1F39C..1F39D ; Extended_Pictographic# E0.0 [2] (🎜..🎝) BEAMED ASCENDING MUSICAL NOTES..BEAMED DESCENDING MUSICAL NOTES 1F39E..1F39F ; Extended_Pictographic# E0.7 [2] (🎞️..🎟️) film frames..admission tickets 1F3A0..1F3C4 ; Extended_Pictographic# E0.6 [37] (🎠..🏄) carousel horse..person surfing 1F3C5 ; Extended_Pictographic# E1.0 [1] (🏅) sports medal @@ -1045,11 +1011,9 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F3E0..1F3E3 ; Extended_Pictographic# E0.6 [4] (🏠..🏣) house..Japanese post office 1F3E4 ; Extended_Pictographic# E1.0 [1] (🏤) post office 1F3E5..1F3F0 ; Extended_Pictographic# E0.6 [12] (🏥..🏰) hospital..castle -1F3F1..1F3F2 ; Extended_Pictographic# E0.0 [2] (🏱..🏲) WHITE PENNANT..BLACK PENNANT 1F3F3 ; Extended_Pictographic# E0.7 [1] (🏳️) white flag 1F3F4 ; Extended_Pictographic# E1.0 [1] (🏴) black flag 1F3F5 ; Extended_Pictographic# E0.7 [1] (🏵️) rosette -1F3F6 ; Extended_Pictographic# E0.0 [1] (🏶) BLACK ROSETTE 1F3F7 ; Extended_Pictographic# E0.7 [1] (🏷️) label 1F3F8..1F3FA ; Extended_Pictographic# E1.0 [3] (🏸..🏺) badminton..amphora 1F400..1F407 ; Extended_Pictographic# E1.0 [8] (🐀..🐇) rat..rabbit @@ -1086,7 +1050,6 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F4F8 ; Extended_Pictographic# E1.0 [1] (📸) camera with flash 1F4F9..1F4FC ; Extended_Pictographic# E0.6 [4] (📹..📼) video camera..videocassette 1F4FD ; Extended_Pictographic# E0.7 [1] (📽️) film projector -1F4FE ; Extended_Pictographic# E0.0 [1] (📾) PORTABLE STEREO 1F4FF..1F502 ; Extended_Pictographic# E1.0 [4] (📿..🔂) prayer beads..repeat single button 1F503 ; Extended_Pictographic# E0.6 [1] (🔃) clockwise vertical arrows 1F504..1F507 ; Extended_Pictographic# E1.0 [4] (🔄..🔇) counterclockwise arrows button..muted speaker @@ -1097,51 +1060,30 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F516..1F52B ; Extended_Pictographic# E0.6 [22] (🔖..🔫) bookmark..water pistol 1F52C..1F52D ; Extended_Pictographic# E1.0 [2] (🔬..🔭) microscope..telescope 1F52E..1F53D ; Extended_Pictographic# E0.6 [16] (🔮..🔽) crystal ball..downwards button -1F546..1F548 ; Extended_Pictographic# E0.0 [3] (🕆..🕈) WHITE LATIN CROSS..CELTIC CROSS 1F549..1F54A ; Extended_Pictographic# E0.7 [2] (🕉️..🕊️) om..dove 1F54B..1F54E ; Extended_Pictographic# E1.0 [4] (🕋..🕎) kaaba..menorah -1F54F ; Extended_Pictographic# E0.0 [1] (🕏) BOWL OF HYGIEIA 1F550..1F55B ; Extended_Pictographic# E0.6 [12] (🕐..🕛) one o’clock..twelve o’clock 1F55C..1F567 ; Extended_Pictographic# E0.7 [12] (🕜..🕧) one-thirty..twelve-thirty -1F568..1F56E ; Extended_Pictographic# E0.0 [7] (🕨..🕮) RIGHT SPEAKER..BOOK 1F56F..1F570 ; Extended_Pictographic# E0.7 [2] (🕯️..🕰️) candle..mantelpiece clock -1F571..1F572 ; Extended_Pictographic# E0.0 [2] (🕱..🕲) BLACK SKULL AND CROSSBONES..NO PIRACY 1F573..1F579 ; Extended_Pictographic# E0.7 [7] (🕳️..🕹️) hole..joystick 1F57A ; Extended_Pictographic# E3.0 [1] (🕺) man dancing -1F57B..1F586 ; Extended_Pictographic# E0.0 [12] (🕻..🖆) LEFT HAND TELEPHONE RECEIVER..PEN OVER STAMPED ENVELOPE 1F587 ; Extended_Pictographic# E0.7 [1] (🖇️) linked paperclips -1F588..1F589 ; Extended_Pictographic# E0.0 [2] (🖈..🖉) BLACK PUSHPIN..LOWER LEFT PENCIL 1F58A..1F58D ; Extended_Pictographic# E0.7 [4] (🖊️..🖍️) pen..crayon -1F58E..1F58F ; Extended_Pictographic# E0.0 [2] (🖎..🖏) LEFT WRITING HAND..TURNED OK HAND SIGN 1F590 ; Extended_Pictographic# E0.7 [1] (🖐️) hand with fingers splayed -1F591..1F594 ; Extended_Pictographic# E0.0 [4] (🖑..🖔) REVERSED RAISED HAND WITH FINGERS SPLAYED..REVERSED VICTORY HAND 1F595..1F596 ; Extended_Pictographic# E1.0 [2] (🖕..🖖) middle finger..vulcan salute -1F597..1F5A3 ; Extended_Pictographic# E0.0 [13] (🖗..🖣) WHITE DOWN POINTING LEFT HAND INDEX..BLACK DOWN POINTING BACKHAND INDEX 1F5A4 ; Extended_Pictographic# E3.0 [1] (🖤) black heart 1F5A5 ; Extended_Pictographic# E0.7 [1] (🖥️) desktop computer -1F5A6..1F5A7 ; Extended_Pictographic# E0.0 [2] (🖦..🖧) KEYBOARD AND MOUSE..THREE NETWORKED COMPUTERS 1F5A8 ; Extended_Pictographic# E0.7 [1] (🖨️) printer -1F5A9..1F5B0 ; Extended_Pictographic# E0.0 [8] (🖩..🖰) POCKET CALCULATOR..TWO BUTTON MOUSE 1F5B1..1F5B2 ; Extended_Pictographic# E0.7 [2] (🖱️..🖲️) computer mouse..trackball -1F5B3..1F5BB ; Extended_Pictographic# E0.0 [9] (🖳..🖻) OLD PERSONAL COMPUTER..DOCUMENT WITH PICTURE 1F5BC ; Extended_Pictographic# E0.7 [1] (🖼️) framed picture -1F5BD..1F5C1 ; Extended_Pictographic# E0.0 [5] (🖽..🗁) FRAME WITH TILES..OPEN FOLDER 1F5C2..1F5C4 ; Extended_Pictographic# E0.7 [3] (🗂️..🗄️) card index dividers..file cabinet -1F5C5..1F5D0 ; Extended_Pictographic# E0.0 [12] (🗅..🗐) EMPTY NOTE..PAGES 1F5D1..1F5D3 ; Extended_Pictographic# E0.7 [3] (🗑️..🗓️) wastebasket..spiral calendar -1F5D4..1F5DB ; Extended_Pictographic# E0.0 [8] (🗔..🗛) DESKTOP WINDOW..DECREASE FONT SIZE SYMBOL 1F5DC..1F5DE ; Extended_Pictographic# E0.7 [3] (🗜️..🗞️) clamp..rolled-up newspaper -1F5DF..1F5E0 ; Extended_Pictographic# E0.0 [2] (🗟..🗠) PAGE WITH CIRCLED TEXT..STOCK CHART 1F5E1 ; Extended_Pictographic# E0.7 [1] (🗡️) dagger -1F5E2 ; Extended_Pictographic# E0.0 [1] (🗢) LIPS 1F5E3 ; Extended_Pictographic# E0.7 [1] (🗣️) speaking head -1F5E4..1F5E7 ; Extended_Pictographic# E0.0 [4] (🗤..🗧) THREE RAYS ABOVE..THREE RAYS RIGHT 1F5E8 ; Extended_Pictographic# E2.0 [1] (🗨️) left speech bubble -1F5E9..1F5EE ; Extended_Pictographic# E0.0 [6] (🗩..🗮) RIGHT SPEECH BUBBLE..LEFT ANGER BUBBLE 1F5EF ; Extended_Pictographic# E0.7 [1] (🗯️) right anger bubble -1F5F0..1F5F2 ; Extended_Pictographic# E0.0 [3] (🗰..🗲) MOOD BUBBLE..LIGHTNING MOOD 1F5F3 ; Extended_Pictographic# E0.7 [1] (🗳️) ballot box with ballot -1F5F4..1F5F9 ; Extended_Pictographic# E0.0 [6] (🗴..🗹) BALLOT SCRIPT X..BALLOT BOX WITH BOLD CHECK 1F5FA ; Extended_Pictographic# E0.7 [1] (🗺️) world map 1F5FB..1F5FF ; Extended_Pictographic# E0.6 [5] (🗻..🗿) mount fuji..moai 1F600 ; Extended_Pictographic# E1.0 [1] (😀) grinning face @@ -1210,26 +1152,22 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F6BF ; Extended_Pictographic# E1.0 [1] (🚿) shower 1F6C0 ; Extended_Pictographic# E0.6 [1] (🛀) person taking bath 1F6C1..1F6C5 ; Extended_Pictographic# E1.0 [5] (🛁..🛅) bathtub..left luggage -1F6C6..1F6CA ; Extended_Pictographic# E0.0 [5] (🛆..🛊) TRIANGLE WITH ROUNDED CORNERS..GIRLS SYMBOL 1F6CB ; Extended_Pictographic# E0.7 [1] (🛋️) couch and lamp 1F6CC ; Extended_Pictographic# E1.0 [1] (🛌) person in bed 1F6CD..1F6CF ; Extended_Pictographic# E0.7 [3] (🛍️..🛏️) shopping bags..bed 1F6D0 ; Extended_Pictographic# E1.0 [1] (🛐) place of worship 1F6D1..1F6D2 ; Extended_Pictographic# E3.0 [2] (🛑..🛒) stop sign..shopping cart -1F6D3..1F6D4 ; Extended_Pictographic# E0.0 [2] (🛓..🛔) STUPA..PAGODA 1F6D5 ; Extended_Pictographic# E12.0 [1] (🛕) hindu temple 1F6D6..1F6D7 ; Extended_Pictographic# E13.0 [2] (🛖..🛗) hut..elevator -1F6D8..1F6DB ; Extended_Pictographic# E0.0 [4] (🛘..🛛) .. +1F6D8 ; Extended_Pictographic# E17.0 [1] (🛘) landslide +1F6D9..1F6DB ; Extended_Pictographic# E0.0 [3] (🛙..🛛) .. 1F6DC ; Extended_Pictographic# E15.0 [1] (🛜) wireless 1F6DD..1F6DF ; Extended_Pictographic# E14.0 [3] (🛝..🛟) playground slide..ring buoy 1F6E0..1F6E5 ; Extended_Pictographic# E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat -1F6E6..1F6E8 ; Extended_Pictographic# E0.0 [3] (🛦..🛨) UP-POINTING MILITARY AIRPLANE..UP-POINTING SMALL AIRPLANE 1F6E9 ; Extended_Pictographic# E0.7 [1] (🛩️) small airplane -1F6EA ; Extended_Pictographic# E0.0 [1] (🛪) NORTHEAST-POINTING AIRPLANE 1F6EB..1F6EC ; Extended_Pictographic# E1.0 [2] (🛫..🛬) airplane departure..airplane arrival 1F6ED..1F6EF ; Extended_Pictographic# E0.0 [3] (🛭..🛯) .. 1F6F0 ; Extended_Pictographic# E0.7 [1] (🛰️) satellite -1F6F1..1F6F2 ; Extended_Pictographic# E0.0 [2] (🛱..🛲) ONCOMING FIRE ENGINE..DIESEL LOCOMOTIVE 1F6F3 ; Extended_Pictographic# E0.7 [1] (🛳️) passenger ship 1F6F4..1F6F6 ; Extended_Pictographic# E3.0 [3] (🛴..🛶) kick scooter..canoe 1F6F7..1F6F8 ; Extended_Pictographic# E5.0 [2] (🛷..🛸) sled..flying saucer @@ -1237,8 +1175,7 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F6FA ; Extended_Pictographic# E12.0 [1] (🛺) auto rickshaw 1F6FB..1F6FC ; Extended_Pictographic# E13.0 [2] (🛻..🛼) pickup truck..roller skate 1F6FD..1F6FF ; Extended_Pictographic# E0.0 [3] (🛽..🛿) .. -1F774..1F77F ; Extended_Pictographic# E0.0 [12] (🝴..🝿) LOT OF FORTUNE..ORCUS -1F7D5..1F7DF ; Extended_Pictographic# E0.0 [11] (🟕..🟟) CIRCLED TRIANGLE.. +1F7DA..1F7DF ; Extended_Pictographic# E0.0 [6] (🟚..🟟) .. 1F7E0..1F7EB ; Extended_Pictographic# E12.0 [12] (🟠..🟫) orange circle..brown square 1F7EC..1F7EF ; Extended_Pictographic# E0.0 [4] (🟬..🟯) .. 1F7F0 ; Extended_Pictographic# E14.0 [1] (🟰) heavy equals sign @@ -1247,7 +1184,10 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F848..1F84F ; Extended_Pictographic# E0.0 [8] (🡈..🡏) .. 1F85A..1F85F ; Extended_Pictographic# E0.0 [6] (🡚..🡟) .. 1F888..1F88F ; Extended_Pictographic# E0.0 [8] (🢈..🢏) .. -1F8AE..1F8FF ; Extended_Pictographic# E0.0 [82] (🢮..🣿) .. +1F8AE..1F8AF ; Extended_Pictographic# E0.0 [2] (🢮..🢯) .. +1F8BC..1F8BF ; Extended_Pictographic# E0.0 [4] (🢼..🢿) .. +1F8C2..1F8CF ; Extended_Pictographic# E0.0 [14] (🣂..🣏) .. +1F8D9..1F8FF ; Extended_Pictographic# E0.0 [39] (🣙..🣿) .. 1F90C ; Extended_Pictographic# E13.0 [1] (🤌) pinched fingers 1F90D..1F90F ; Extended_Pictographic# E12.0 [3] (🤍..🤏) white heart..pinching hand 1F910..1F918 ; Extended_Pictographic# E1.0 [9] (🤐..🤘) zipper-mouth face..sign of the horns @@ -1293,7 +1233,8 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1F9CD..1F9CF ; Extended_Pictographic# E12.0 [3] (🧍..🧏) person standing..deaf person 1F9D0..1F9E6 ; Extended_Pictographic# E5.0 [23] (🧐..🧦) face with monocle..socks 1F9E7..1F9FF ; Extended_Pictographic# E11.0 [25] (🧧..🧿) red envelope..nazar amulet -1FA00..1FA6F ; Extended_Pictographic# E0.0 [112] (🨀..🩯) NEUTRAL CHESS KING.. +1FA58..1FA5F ; Extended_Pictographic# E0.0 [8] (🩘..🩟) .. +1FA6E..1FA6F ; Extended_Pictographic# E0.0 [2] (🩮..🩯) .. 1FA70..1FA73 ; Extended_Pictographic# E12.0 [4] (🩰..🩳) ballet shoes..shorts 1FA74 ; Extended_Pictographic# E13.0 [1] (🩴) thong sandal 1FA75..1FA77 ; Extended_Pictographic# E15.0 [3] (🩵..🩷) light blue heart..pink heart @@ -1304,7 +1245,9 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1FA83..1FA86 ; Extended_Pictographic# E13.0 [4] (🪃..🪆) boomerang..nesting dolls 1FA87..1FA88 ; Extended_Pictographic# E15.0 [2] (🪇..🪈) maracas..flute 1FA89 ; Extended_Pictographic# E16.0 [1] (🪉) harp -1FA8A..1FA8E ; Extended_Pictographic# E0.0 [5] (🪊..🪎) .. +1FA8A ; Extended_Pictographic# E17.0 [1] (🪊) trombone +1FA8B..1FA8D ; Extended_Pictographic# E0.0 [3] (🪋..🪍) .. +1FA8E ; Extended_Pictographic# E17.0 [1] (🪎) treasure chest 1FA8F ; Extended_Pictographic# E16.0 [1] (🪏) shovel 1FA90..1FA95 ; Extended_Pictographic# E12.0 [6] (🪐..🪕) ringed planet..banjo 1FA96..1FAA8 ; Extended_Pictographic# E13.0 [19] (🪖..🪨) military helmet..rock @@ -1318,7 +1261,10 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1FAC0..1FAC2 ; Extended_Pictographic# E13.0 [3] (🫀..🫂) anatomical heart..people hugging 1FAC3..1FAC5 ; Extended_Pictographic# E14.0 [3] (🫃..🫅) pregnant man..person with crown 1FAC6 ; Extended_Pictographic# E16.0 [1] (🫆) fingerprint -1FAC7..1FACD ; Extended_Pictographic# E0.0 [7] (🫇..🫍) .. +1FAC7 ; Extended_Pictographic# E0.0 [1] (🫇) +1FAC8 ; Extended_Pictographic# E17.0 [1] (🫈) hairy creature +1FAC9..1FACC ; Extended_Pictographic# E0.0 [4] (🫉..🫌) .. +1FACD ; Extended_Pictographic# E17.0 [1] (🫍) orca 1FACE..1FACF ; Extended_Pictographic# E15.0 [2] (🫎..🫏) moose..donkey 1FAD0..1FAD6 ; Extended_Pictographic# E13.0 [7] (🫐..🫖) blueberries..teapot 1FAD7..1FAD9 ; Extended_Pictographic# E14.0 [3] (🫗..🫙) pouring liquid..jar @@ -1329,12 +1275,14 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (󠀠..󠁿) tag space..c 1FAE0..1FAE7 ; Extended_Pictographic# E14.0 [8] (🫠..🫧) melting face..bubbles 1FAE8 ; Extended_Pictographic# E15.0 [1] (🫨) shaking face 1FAE9 ; Extended_Pictographic# E16.0 [1] (🫩) face with bags under eyes -1FAEA..1FAEF ; Extended_Pictographic# E0.0 [6] (🫪..🫯) .. +1FAEA ; Extended_Pictographic# E17.0 [1] (🫪) distorted face +1FAEB..1FAEE ; Extended_Pictographic# E0.0 [4] (🫫..🫮) .. +1FAEF ; Extended_Pictographic# E17.0 [1] (🫯) fight cloud 1FAF0..1FAF6 ; Extended_Pictographic# E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands 1FAF7..1FAF8 ; Extended_Pictographic# E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand 1FAF9..1FAFF ; Extended_Pictographic# E0.0 [7] (🫹..🫿) .. 1FC00..1FFFD ; Extended_Pictographic# E0.0[1022] (🰀..🿽) .. -# Total elements: 3537 +# Total elements: 2848 #EOF From 390a5bc6dba73cd32bc8c7290ac73321f6504deb Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 11 Sep 2025 11:35:40 +0700 Subject: [PATCH 42/52] docs(windows): Update supported Unicode version --- windows/docs/help/common/unicode.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/docs/help/common/unicode.md b/windows/docs/help/common/unicode.md index 614934e72c..fd567c8ce4 100644 --- a/windows/docs/help/common/unicode.md +++ b/windows/docs/help/common/unicode.md @@ -3,5 +3,5 @@ title: Does Keyman support Unicode? --- Yes, since Keyman 5, Keyman fully supports the Unicode Standard, up to -and including Unicode 13.0. We also update Keyman with each new release +and including Unicode 17.0. We also update Keyman with each new release of the Unicode Standard. From a49b0146ce3df8c4f4b41a5ba55aef92d7370a27 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 11 Sep 2025 06:49:27 +0200 Subject: [PATCH 43/52] fix(resources): ignore KEYMAN_VERSION_FOR_FILENAME in test Follows: #14703 Test-bot: skip Build-bot: skip build:common --- resources/build/publish-minimum-versions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/build/publish-minimum-versions.sh b/resources/build/publish-minimum-versions.sh index 5da012737a..a9d6098c72 100755 --- a/resources/build/publish-minimum-versions.sh +++ b/resources/build/publish-minimum-versions.sh @@ -35,7 +35,7 @@ generate_table() { KEYMAN_VERSION | KEYMAN_VERSION_MAJOR | KEYMAN_VERSION_MINOR | \ KEYMAN_VERSION_PATCH | KEYMAN_VERSION_RELEASE | KEYMAN_VERSION_WIN | \ KEYMAN_VERSION_TAG | KEYMAN_VERSION_WITH_TAG | \ - KEYMAN_VERSION_ENVIRONMENT | KEYMAN_VERSION_GIT_TAG) + KEYMAN_VERSION_ENVIRONMENT | KEYMAN_VERSION_GIT_TAG | KEYMAN_VERSION_FOR_FILENAME) # ignore the variables that contain the Keyman version ;; *) From 1433f1550659c84e1ef8ea8353950556308c7053 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 11 Sep 2025 12:59:16 +0200 Subject: [PATCH 44/52] fix(resources): pr build status - find 'failure' state in status checks and log Fixes: #14731 Build-bot: skip Test-bot: skip --- .github/workflows/pr-build-status.yml | 47 +- resources/build/pr-build-status/build.sh | 2 +- ...d32bc8c7290ac73321f6504deb-check-runs.json | 660 ++++ ...3cd32bc8c7290ac73321f6504deb-statuses.json | 3038 +++++++++++++++++ .../build/pr-build-status/pr-build-status.mjs | 47 +- .../pr-build-status/pr-build-status.tests.mjs | 18 +- 6 files changed, 3778 insertions(+), 34 deletions(-) create mode 100644 resources/build/pr-build-status/fixtures/390a5bc6dba73cd32bc8c7290ac73321f6504deb-check-runs.json create mode 100644 resources/build/pr-build-status/fixtures/390a5bc6dba73cd32bc8c7290ac73321f6504deb-statuses.json diff --git a/.github/workflows/pr-build-status.yml b/.github/workflows/pr-build-status.yml index 236970f141..89a56b3877 100644 --- a/.github/workflows/pr-build-status.yml +++ b/.github/workflows/pr-build-status.yml @@ -55,35 +55,52 @@ jobs: // check/web/file-size // + function addLog(message) { + console.log(message); + return message + String.fromCharCode(13); + } + + function addStatus(o, type, context, state) { + if(!o[context]) { + o[context] = {type, state}; + return addLog(`Found ${context}: ${state}`); + } else { + return addLog(`Skipping repeated ${context}: ${state}`); + } + } + function reduceStatuses(statuses) { + let summary = ''; const filtered_statuses = statuses.reduce((o, status) => { if(status.creator?.login == 'keyman-server' && status.context.startsWith('Test')) { - if(!o[status.context]) o[status.context] = {type: 'build', state: status.state}; + summary += addStatus(o, 'build', status.context, status.state); } else if(status.creator?.login == 'keymanapp-test-bot[bot]' && status.context == 'user_testing') { - if(!o[status.context]) o[status.context] = {type: 'user-test', state: status.state};; + summary += addStatus(o, 'user-test', status.context, status.state); } else if(status.context == 'API Verification') { - if(!o[status.context]) o[status.context] = {type: 'check', state: status.state}; + summary += addStatus(o, 'check', status.context, status.state); } else if(status.context == 'Ubuntu Packaging') { - if(!o[status.context]) o[status.context] = {type: 'build', state: status.state}; + summary += addStatus(o, 'build', status.context, status.state); } else if(status.context == 'check/web/file-size') { // Ignore check/web/file-size -- we won't block automerge for this at this point + summary += addLog(`Skipping ${status.context}`); } else { // We fail with an 'unknown status' response if we get a new status check // so we can be sure we are not skipping known status checks + summary += addLog(`Found unknown ${status.context}: ${status.state}`); o[status.context] = {type: 'unknown', state: status.state}; } return o; }, {}); - return filtered_statuses; + return { filtered_statuses, summary }; } // // Given the collection of status checks we care about, return - // an aggregate status -- error, failed, pending, or success, + // an aggregate status -- error, failure, pending, or success, // and a summary description // - function calculateFinalStatus(filtered_statuses) { + function calculateFinalStatus(filtered_statuses, summary) { const counts = {}; let hasBuilds = false; for(const context of Object.keys(filtered_statuses)) { @@ -107,7 +124,7 @@ jobs: const state = counts.error ? 'error' : - counts.failed ? 'failed' : + counts.failure ? 'failure' : counts.pending ? 'pending' : 'success'; @@ -118,11 +135,11 @@ jobs: description += `${count} check${count == 1 ? '' : 's'} ${state}`; } appendDescription(counts.error, 'in an error state ❌'); - appendDescription(counts.failed, 'failed ❌'); + appendDescription(counts.failure, 'failed ❌'); appendDescription(counts.pending, 'pending ⌛'); appendDescription(counts.success, 'completed successfully ✅'); - return [ state, description ]; + return [ state, description, summary ]; } async function getCommitStatuses(github, owner, repo, sha) { @@ -154,9 +171,9 @@ jobs: return ['error', 'Failed to retrieve status checks from GitHub ❌']; } - const filtered_statuses = reduceStatuses(statuses); + const { filtered_statuses, summary } = reduceStatuses(statuses); - const result = calculateFinalStatus(filtered_statuses); + const result = calculateFinalStatus(filtered_statuses, summary); return result; } @@ -177,7 +194,7 @@ jobs: return check.data.id; } - async function updateCheck(github, owner, repo, checkRunId, status, description) { + async function updateCheck(github, owner, repo, checkRunId, status, description, summary) { const checkStatus = status == 'pending' ? 'in_progress' : 'completed'; const conclusion = checkStatus == 'in_progress' ? undefined : (status == 'success' ? 'success' : 'failure'); @@ -189,7 +206,7 @@ jobs: conclusion, output: { title: description, - summary: '' + summary } }); } @@ -204,5 +221,5 @@ jobs: const checkRunId = await createCheck(github, owner, repo, sha); const res = await test(github, owner, repo, sha); - await updateCheck(github, owner, repo, checkRunId, res[0], res[1]); + await updateCheck(github, owner, repo, checkRunId, res[0], res[1], res[2]); diff --git a/resources/build/pr-build-status/build.sh b/resources/build/pr-build-status/build.sh index ca87de813d..42683bc3bc 100755 --- a/resources/build/pr-build-status/build.sh +++ b/resources/build/pr-build-status/build.sh @@ -19,7 +19,7 @@ function do_build() { local YML_PREFIX=$(cat "$YML_PREFIX_FILE") local SCRIPT_CLONE=$(sed -n '/CLONE:START/,/CLONE:END/{//!p}' < "$MJS_FILE") local SCRIPT_SUFFIX=$(sed -n '/CLONE-COMMENTED:START/,/CLONE-COMMENTED:END/{//!p}' < "$MJS_FILE" | sed 's/\/\/ //g') - echo -e "# GENERATED FILE - DO NOT EDIT!\n$YML_PREFIX\n$SCRIPT_CLONE\n$SCRIPT_SUFFIX\n" > "$YML_FILE" + echo $'# GENERATED FILE - DO NOT EDIT!\n'"$YML_PREFIX"$'\n'"$SCRIPT_CLONE"$'\n'"$SCRIPT_SUFFIX"$'\n' > "$YML_FILE" } builder_run_action build do_build diff --git a/resources/build/pr-build-status/fixtures/390a5bc6dba73cd32bc8c7290ac73321f6504deb-check-runs.json b/resources/build/pr-build-status/fixtures/390a5bc6dba73cd32bc8c7290ac73321f6504deb-check-runs.json new file mode 100644 index 0000000000..2544408133 --- /dev/null +++ b/resources/build/pr-build-status/fixtures/390a5bc6dba73cd32bc8c7290ac73321f6504deb-check-runs.json @@ -0,0 +1,660 @@ +[ + { + "id": 50109411782, + "name": "Build Outcome", + "node_id": "CR_kwDOAY2xT88AAAALqsDxxg", + "head_sha": "390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "external_id": "", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50109411782", + "html_url": "https://github.com/keymanapp/keyman/runs/50109411782", + "details_url": "https://github.com/keymanapp/keyman/runs/50109411782", + "status": "completed", + "conclusion": "success", + "started_at": "2025-09-11T05:29:44Z", + "completed_at": "2025-09-11T05:29:45Z", + "output": { + "title": "21 checks completed successfully ✅", + "summary": "", + "text": null, + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50109411782/annotations" + }, + "check_suite": { + "id": 45240794914 + }, + "app": { + "id": 15368, + "client_id": "Iv1.05c79e9ad1f6bdfa", + "slug": "github-actions", + "node_id": "MDM6QXBwMTUzNjg=", + "owner": { + "login": "github", + "id": 9919, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjk5MTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/9919?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github", + "html_url": "https://github.com/github", + "followers_url": "https://api.github.com/users/github/followers", + "following_url": "https://api.github.com/users/github/following{/other_user}", + "gists_url": "https://api.github.com/users/github/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github/subscriptions", + "organizations_url": "https://api.github.com/users/github/orgs", + "repos_url": "https://api.github.com/users/github/repos", + "events_url": "https://api.github.com/users/github/events{/privacy}", + "received_events_url": "https://api.github.com/users/github/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "name": "GitHub Actions", + "description": "Automate your workflow from idea to production", + "external_url": "https://help.github.com/en/actions", + "html_url": "https://github.com/apps/github-actions", + "created_at": "2018-07-30T09:30:17Z", + "updated_at": "2025-03-07T16:35:00Z", + "permissions": { + "actions": "write", + "administration": "read", + "attestations": "write", + "checks": "write", + "contents": "write", + "deployments": "write", + "discussions": "write", + "issues": "write", + "merge_queues": "write", + "metadata": "read", + "models": "read", + "packages": "write", + "pages": "write", + "pull_requests": "write", + "repository_hooks": "write", + "repository_projects": "write", + "security_events": "write", + "statuses": "write", + "vulnerability_alerts": "read" + }, + "events": [ + "branch_protection_rule", + "check_run", + "check_suite", + "create", + "delete", + "deployment", + "deployment_status", + "discussion", + "discussion_comment", + "fork", + "gollum", + "issues", + "issue_comment", + "label", + "merge_group", + "milestone", + "page_build", + "project", + "project_card", + "project_column", + "public", + "pull_request", + "pull_request_review", + "pull_request_review_comment", + "push", + "registry_package", + "release", + "repository", + "repository_dispatch", + "status", + "watch", + "workflow_dispatch", + "workflow_run" + ] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/keymanapp/keyman/pulls/14728", + "id": 2817549354, + "number": 14728, + "head": { + "ref": "chore/common/unicode-17", + "sha": "ebeacc83d0b5cfd7ab523874631e5740721f1f2f", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "master", + "sha": "898cd340a33f29b766b08da66a519be7e3d79fe6", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + } + } + ] + }, + { + "id": 50107239504, + "name": "GitGuardian Security Checks", + "node_id": "CR_kwDOAY2xT88AAAALqp_MUA", + "head_sha": "390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "external_id": "", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50107239504", + "html_url": "https://github.com/keymanapp/keyman/runs/50107239504", + "details_url": "https://dashboard.gitguardian.com", + "status": "completed", + "conclusion": "success", + "started_at": "2025-09-11T04:38:47Z", + "completed_at": "2025-09-11T04:38:48Z", + "output": { + "title": "No secrets detected ✅", + "summary": "**2** commits were scanned without uncovering any secrets.\n", + "text": "Commits scanned: **2**\n\n\n\n- Pull request #14728: `chore/common/unicode-17` 👉 `master`\n \n\n🦉 [GitGuardian](https://dashboard.gitguardian.com/auth/login/?utm_medium=checkruns&utm_source=github&utm_campaign=cr1) detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
\n", + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50107239504/annotations" + }, + "check_suite": { + "id": 45240794052 + }, + "app": { + "id": 46505, + "client_id": "Iv1.ec3c001966b4cc5a", + "slug": "gitguardian", + "node_id": "MDM6QXBwNDY1MDU=", + "owner": { + "login": "GitGuardian", + "id": 27360172, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI3MzYwMTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/27360172?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/GitGuardian", + "html_url": "https://github.com/GitGuardian", + "followers_url": "https://api.github.com/users/GitGuardian/followers", + "following_url": "https://api.github.com/users/GitGuardian/following{/other_user}", + "gists_url": "https://api.github.com/users/GitGuardian/gists{/gist_id}", + "starred_url": "https://api.github.com/users/GitGuardian/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/GitGuardian/subscriptions", + "organizations_url": "https://api.github.com/users/GitGuardian/orgs", + "repos_url": "https://api.github.com/users/GitGuardian/repos", + "events_url": "https://api.github.com/users/GitGuardian/events{/privacy}", + "received_events_url": "https://api.github.com/users/GitGuardian/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "name": "GitGuardian", + "description": "# 🦉 What is GitGuardian?\r\n\r\nGitGuardian Secrets Detection detects and fixes vulnerabilities in source code at every step of the software development lifecycle, covering 350+ types of secrets like API keys, database connection strings, private keys, certificates, and more. The platform’s policy engine enables security teams to monitor and enforce rules across their VCS, DevOps tools, and infrastructure-as-code configurations. Our automated remediation playbooks and collaboration features bring security and development teams together to resolve incidents fast and in full.\r\n\r\n## 1. Scan your codebase for 350+ types of secrets\r\nGitGuardian scans your GitHub repositories and raises alerts only for critical secrets, such as API keys or other credentials. At scale, GitGuardian’s detection algorithm has been battle-tested on over three years of activity in all public GitHub repositories – totaling over 1 billion scanned commits!\r\n\r\n## 2. Quickly remediate your hard-coded secrets\r\nIf you ever experience a leak involving a credential, we have a complete remediation guide used by 100k+ developers each year. We’ll show you how to revoke the secret and remove it from your git history.\r\n\r\n## 3. Prevent secrets from reaching GitHub\r\nInstall ggshield, the GitGuardian CLI, and add secrets detection to your local development workflow using pre-commit and pre-push git hooks integrations.\r\n\r\n# 🙋‍♂️ FAQ\r\n\r\n**What is your pricing?**\r\nGitGuardian is free for teams under 25 developers and offers a 30-day trial for larger teams.\r\n\r\n**How can I be sure that GitGuardian won’t raise too many false positives?**\r\nWe have scanned billions of commits, sent millions of alerts since 2018, and integrated each feedback to improve our algorithm. Our alerts currently receive 91% “true positive” feedback from developers.\r\n\r\n**My repositories are private; why should I install automated secret detection?**\r\nImagine if there were a plain text file with all your credit card numbers inside, you wouldn’t put this file inside your company’s git repository. Secrets are just as sensitive and should be handled with special care.\r\n\r\n**Is GitGuardian available to be installed on-premise?**\r\nYes, you can contact one of our security specialists to look over the possibility of installing GitGuardian on-premise on your repositories.\r\n\r\n# ⚒️ Installation notes \r\n\r\nYou should install GitGuardian directly through your [GitGuardian](https://dashboard.gitguardian.com/) workspace on the Integration settings page. \r\n\r\nSo that you know, your GitHub organization or GitHub account can only be associated with a single GitGuardian workspace.\r\n\r\n# 👋 Support\r\n\r\nIf you experience any difficulties or have any questions, please reach out to us by email ([support@gitguardian.com](mailto:support@gitguardian.com)).", + "external_url": "https://dashboard.gitguardian.com", + "html_url": "https://github.com/apps/gitguardian", + "created_at": "2019-11-12T15:44:31Z", + "updated_at": "2023-07-18T09:06:22Z", + "permissions": { + "checks": "write", + "contents": "read", + "emails": "read", + "issues": "write", + "members": "read", + "metadata": "read", + "organization_hooks": "write", + "pull_requests": "write" + }, + "events": [ + "check_run", + "check_suite", + "commit_comment", + "create", + "delete", + "organization", + "public", + "pull_request", + "pull_request_review", + "pull_request_review_comment", + "push", + "repository" + ] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/keymanapp/keyman/pulls/14728", + "id": 2817549354, + "number": 14728, + "head": { + "ref": "chore/common/unicode-17", + "sha": "ebeacc83d0b5cfd7ab523874631e5740721f1f2f", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "master", + "sha": "898cd340a33f29b766b08da66a519be7e3d79fe6", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + } + } + ] + }, + { + "id": 50107238217, + "name": "build", + "node_id": "CR_kwDOAY2xT88AAAALqp_HSQ", + "head_sha": "390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "external_id": "584213e9-9caa-5e7a-be2b-4a4fb7afc574", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50107238217", + "html_url": "https://github.com/keymanapp/keyman/actions/runs/17634180116/job/50107238217", + "details_url": "https://github.com/keymanapp/keyman/actions/runs/17634180116/job/50107238217", + "status": "completed", + "conclusion": "skipped", + "started_at": "2025-09-11T04:38:46Z", + "completed_at": "2025-09-11T04:38:45Z", + "output": { + "title": null, + "summary": null, + "text": null, + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50107238217/annotations" + }, + "check_suite": { + "id": 45240899113 + }, + "app": { + "id": 15368, + "client_id": "Iv1.05c79e9ad1f6bdfa", + "slug": "github-actions", + "node_id": "MDM6QXBwMTUzNjg=", + "owner": { + "login": "github", + "id": 9919, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjk5MTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/9919?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github", + "html_url": "https://github.com/github", + "followers_url": "https://api.github.com/users/github/followers", + "following_url": "https://api.github.com/users/github/following{/other_user}", + "gists_url": "https://api.github.com/users/github/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github/subscriptions", + "organizations_url": "https://api.github.com/users/github/orgs", + "repos_url": "https://api.github.com/users/github/repos", + "events_url": "https://api.github.com/users/github/events{/privacy}", + "received_events_url": "https://api.github.com/users/github/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "name": "GitHub Actions", + "description": "Automate your workflow from idea to production", + "external_url": "https://help.github.com/en/actions", + "html_url": "https://github.com/apps/github-actions", + "created_at": "2018-07-30T09:30:17Z", + "updated_at": "2025-03-07T16:35:00Z", + "permissions": { + "actions": "write", + "administration": "read", + "attestations": "write", + "checks": "write", + "contents": "write", + "deployments": "write", + "discussions": "write", + "issues": "write", + "merge_queues": "write", + "metadata": "read", + "models": "read", + "packages": "write", + "pages": "write", + "pull_requests": "write", + "repository_hooks": "write", + "repository_projects": "write", + "security_events": "write", + "statuses": "write", + "vulnerability_alerts": "read" + }, + "events": [ + "branch_protection_rule", + "check_run", + "check_suite", + "create", + "delete", + "deployment", + "deployment_status", + "discussion", + "discussion_comment", + "fork", + "gollum", + "issues", + "issue_comment", + "label", + "merge_group", + "milestone", + "page_build", + "project", + "project_card", + "project_column", + "public", + "pull_request", + "pull_request_review", + "pull_request_review_comment", + "push", + "registry_package", + "release", + "repository", + "repository_dispatch", + "status", + "watch", + "workflow_dispatch", + "workflow_run" + ] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/keymanapp/keyman/pulls/14728", + "id": 2817549354, + "number": 14728, + "head": { + "ref": "chore/common/unicode-17", + "sha": "ebeacc83d0b5cfd7ab523874631e5740721f1f2f", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "master", + "sha": "898cd340a33f29b766b08da66a519be7e3d79fe6", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + } + } + ] + }, + { + "id": 50107238052, + "name": "triage", + "node_id": "CR_kwDOAY2xT88AAAALqp_GpA", + "head_sha": "390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "external_id": "90e392c4-1805-5238-9d01-622e4d746355", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50107238052", + "html_url": "https://github.com/keymanapp/keyman/actions/runs/17634180101/job/50107238052", + "details_url": "https://github.com/keymanapp/keyman/actions/runs/17634180101/job/50107238052", + "status": "completed", + "conclusion": "success", + "started_at": "2025-09-11T04:38:47Z", + "completed_at": "2025-09-11T04:38:52Z", + "output": { + "title": null, + "summary": null, + "text": null, + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50107238052/annotations" + }, + "check_suite": { + "id": 45240899080 + }, + "app": { + "id": 15368, + "client_id": "Iv1.05c79e9ad1f6bdfa", + "slug": "github-actions", + "node_id": "MDM6QXBwMTUzNjg=", + "owner": { + "login": "github", + "id": 9919, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjk5MTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/9919?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github", + "html_url": "https://github.com/github", + "followers_url": "https://api.github.com/users/github/followers", + "following_url": "https://api.github.com/users/github/following{/other_user}", + "gists_url": "https://api.github.com/users/github/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github/subscriptions", + "organizations_url": "https://api.github.com/users/github/orgs", + "repos_url": "https://api.github.com/users/github/repos", + "events_url": "https://api.github.com/users/github/events{/privacy}", + "received_events_url": "https://api.github.com/users/github/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "name": "GitHub Actions", + "description": "Automate your workflow from idea to production", + "external_url": "https://help.github.com/en/actions", + "html_url": "https://github.com/apps/github-actions", + "created_at": "2018-07-30T09:30:17Z", + "updated_at": "2025-03-07T16:35:00Z", + "permissions": { + "actions": "write", + "administration": "read", + "attestations": "write", + "checks": "write", + "contents": "write", + "deployments": "write", + "discussions": "write", + "issues": "write", + "merge_queues": "write", + "metadata": "read", + "models": "read", + "packages": "write", + "pages": "write", + "pull_requests": "write", + "repository_hooks": "write", + "repository_projects": "write", + "security_events": "write", + "statuses": "write", + "vulnerability_alerts": "read" + }, + "events": [ + "branch_protection_rule", + "check_run", + "check_suite", + "create", + "delete", + "deployment", + "deployment_status", + "discussion", + "discussion_comment", + "fork", + "gollum", + "issues", + "issue_comment", + "label", + "merge_group", + "milestone", + "page_build", + "project", + "project_card", + "project_column", + "public", + "pull_request", + "pull_request_review", + "pull_request_review_comment", + "push", + "registry_package", + "release", + "repository", + "repository_dispatch", + "status", + "watch", + "workflow_dispatch", + "workflow_run" + ] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/keymanapp/keyman/pulls/14728", + "id": 2817549354, + "number": 14728, + "head": { + "ref": "chore/common/unicode-17", + "sha": "ebeacc83d0b5cfd7ab523874631e5740721f1f2f", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "master", + "sha": "898cd340a33f29b766b08da66a519be7e3d79fe6", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + } + } + ] + }, + { + "id": 50107108425, + "name": "Summarize build status checks", + "node_id": "CR_kwDOAY2xT88AAAALqp3MSQ", + "head_sha": "390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "external_id": "7e5f7f3d-df60-5a09-9991-ce263cfb45c6", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50107108425", + "html_url": "https://github.com/keymanapp/keyman/actions/runs/17634132698/job/50107108425", + "details_url": "https://github.com/keymanapp/keyman/actions/runs/17634132698/job/50107108425", + "status": "completed", + "conclusion": "success", + "started_at": "2025-09-11T04:35:52Z", + "completed_at": "2025-09-11T04:35:55Z", + "output": { + "title": null, + "summary": null, + "text": null, + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/keymanapp/keyman/check-runs/50107108425/annotations" + }, + "check_suite": { + "id": 45240794914 + }, + "app": { + "id": 15368, + "client_id": "Iv1.05c79e9ad1f6bdfa", + "slug": "github-actions", + "node_id": "MDM6QXBwMTUzNjg=", + "owner": { + "login": "github", + "id": 9919, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjk5MTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/9919?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github", + "html_url": "https://github.com/github", + "followers_url": "https://api.github.com/users/github/followers", + "following_url": "https://api.github.com/users/github/following{/other_user}", + "gists_url": "https://api.github.com/users/github/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github/subscriptions", + "organizations_url": "https://api.github.com/users/github/orgs", + "repos_url": "https://api.github.com/users/github/repos", + "events_url": "https://api.github.com/users/github/events{/privacy}", + "received_events_url": "https://api.github.com/users/github/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "name": "GitHub Actions", + "description": "Automate your workflow from idea to production", + "external_url": "https://help.github.com/en/actions", + "html_url": "https://github.com/apps/github-actions", + "created_at": "2018-07-30T09:30:17Z", + "updated_at": "2025-03-07T16:35:00Z", + "permissions": { + "actions": "write", + "administration": "read", + "attestations": "write", + "checks": "write", + "contents": "write", + "deployments": "write", + "discussions": "write", + "issues": "write", + "merge_queues": "write", + "metadata": "read", + "models": "read", + "packages": "write", + "pages": "write", + "pull_requests": "write", + "repository_hooks": "write", + "repository_projects": "write", + "security_events": "write", + "statuses": "write", + "vulnerability_alerts": "read" + }, + "events": [ + "branch_protection_rule", + "check_run", + "check_suite", + "create", + "delete", + "deployment", + "deployment_status", + "discussion", + "discussion_comment", + "fork", + "gollum", + "issues", + "issue_comment", + "label", + "merge_group", + "milestone", + "page_build", + "project", + "project_card", + "project_column", + "public", + "pull_request", + "pull_request_review", + "pull_request_review_comment", + "push", + "registry_package", + "release", + "repository", + "repository_dispatch", + "status", + "watch", + "workflow_dispatch", + "workflow_run" + ] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/keymanapp/keyman/pulls/14728", + "id": 2817549354, + "number": 14728, + "head": { + "ref": "chore/common/unicode-17", + "sha": "ebeacc83d0b5cfd7ab523874631e5740721f1f2f", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "master", + "sha": "898cd340a33f29b766b08da66a519be7e3d79fe6", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + } + } + ] + } +] \ No newline at end of file diff --git a/resources/build/pr-build-status/fixtures/390a5bc6dba73cd32bc8c7290ac73321f6504deb-statuses.json b/resources/build/pr-build-status/fixtures/390a5bc6dba73cd32bc8c7290ac73321f6504deb-statuses.json new file mode 100644 index 0000000000..213931a7ed --- /dev/null +++ b/resources/build/pr-build-status/fixtures/390a5bc6dba73cd32bc8c7290ac73321f6504deb-statuses.json @@ -0,0 +1,3038 @@ +[ + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175572529, + "node_id": "SC_kwDOAY2xT88AAAAJHwvMMQ", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T05:29:36Z", + "updated_at": "2025-09-11T05:29:36Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175567982, + "node_id": "SC_kwDOAY2xT88AAAAJHwu6bg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Developer_Test/576932", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-09-11T05:29:14Z", + "updated_at": "2025-09-11T05:29:14Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175162089, + "node_id": "SC_kwDOAY2xT88AAAAJHwWI6Q", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T05:00:11Z", + "updated_at": "2025-09-11T05:00:11Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175160705, + "node_id": "SC_kwDOAY2xT88AAAAJHwWDgQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Windows/576928", + "context": "Test: Keyman Core - Windows (Common)", + "created_at": "2025-09-11T05:00:07Z", + "updated_at": "2025-09-11T05:00:07Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175159732, + "node_id": "SC_kwDOAY2xT88AAAAJHwV_tA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Mac/576933", + "context": "Test - macOS (Keyman - Developer)", + "created_at": "2025-09-11T05:00:02Z", + "updated_at": "2025-09-11T05:00:02Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175118416, + "node_id": "SC_kwDOAY2xT88AAAAJHwTeUA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:56:41Z", + "updated_at": "2025-09-11T04:56:41Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175115406, + "node_id": "SC_kwDOAY2xT88AAAAJHwTSjg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_WASM/576918", + "context": "Test: Keyman Core - WASM (Common)", + "created_at": "2025-09-11T04:56:27Z", + "updated_at": "2025-09-11T04:56:27Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175107216, + "node_id": "SC_kwDOAY2xT88AAAAJHwSykA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:55:44Z", + "updated_at": "2025-09-11T04:55:44Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 39175106689, + "node_id": "SC_kwDOAY2xT88AAAAJHwSwgQ", + "state": "success", + "description": "API verification succeeded", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/17634394358", + "context": "API Verification", + "created_at": "2025-09-11T04:55:42Z", + "updated_at": "2025-09-11T04:55:42Z", + "creator": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175098588, + "node_id": "SC_kwDOAY2xT88AAAAJHwSQ3A", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:55:00Z", + "updated_at": "2025-09-11T04:55:00Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175098146, + "node_id": "SC_kwDOAY2xT88AAAAJHwSPIg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:54:58Z", + "updated_at": "2025-09-11T04:54:58Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175098035, + "node_id": "SC_kwDOAY2xT88AAAAJHwSOsw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Linux_Test_Integration/576920", + "context": "Test:Integration (Keyman - Linux)", + "created_at": "2025-09-11T04:54:58Z", + "updated_at": "2025-09-11T04:54:58Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175097199, + "node_id": "SC_kwDOAY2xT88AAAAJHwSLbw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_macOS/576925", + "context": "Test: Keyman Core - macOS (Common)", + "created_at": "2025-09-11T04:54:54Z", + "updated_at": "2025-09-11T04:54:54Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175096032, + "node_id": "SC_kwDOAY2xT88AAAAJHwSG4A", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:54:47Z", + "updated_at": "2025-09-11T04:54:47Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175095550, + "node_id": "SC_kwDOAY2xT88AAAAJHwSE_g", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Mac/576933", + "context": "Test - macOS (Keyman - Developer)", + "created_at": "2025-09-11T04:54:45Z", + "updated_at": "2025-09-11T04:54:45Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175095475, + "node_id": "SC_kwDOAY2xT88AAAAJHwSEsw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestPullRequests/576915", + "context": "Test (Keyman - iOS)", + "created_at": "2025-09-11T04:54:44Z", + "updated_at": "2025-09-11T04:54:44Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175080948, + "node_id": "SC_kwDOAY2xT88AAAAJHwRL9A", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:53:36Z", + "updated_at": "2025-09-11T04:53:36Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175080759, + "node_id": "SC_kwDOAY2xT88AAAAJHwRLNw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:53:35Z", + "updated_at": "2025-09-11T04:53:35Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175078800, + "node_id": "SC_kwDOAY2xT88AAAAJHwRDkA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestSamplesAndTestProjects/576931", + "context": "Test: Samples and Test projects (Keyman - Android)", + "created_at": "2025-09-11T04:53:25Z", + "updated_at": "2025-09-11T04:53:25Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 39175078429, + "node_id": "SC_kwDOAY2xT88AAAAJHwRCHQ", + "state": "pending", + "description": "API verification started", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/17634394358", + "context": "API Verification", + "created_at": "2025-09-11T04:53:23Z", + "updated_at": "2025-09-11T04:53:23Z", + "creator": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 39175076234, + "node_id": "SC_kwDOAY2xT88AAAAJHwQ5ig", + "state": "success", + "description": "Package build succeeded", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/17634187943", + "context": "Ubuntu Packaging", + "created_at": "2025-09-11T04:53:11Z", + "updated_at": "2025-09-11T04:53:11Z", + "creator": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175068238, + "node_id": "SC_kwDOAY2xT88AAAAJHwQaTg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Linux_Test_Integration/576920", + "context": "Test:Integration (Keyman - Linux)", + "created_at": "2025-09-11T04:52:31Z", + "updated_at": "2025-09-11T04:52:31Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175063216, + "node_id": "SC_kwDOAY2xT88AAAAJHwQGsA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:52:10Z", + "updated_at": "2025-09-11T04:52:10Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175058340, + "node_id": "SC_kwDOAY2xT88AAAAJHwPzpA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Linux/576934", + "context": "Test - Linux (Keyman - Developer)", + "created_at": "2025-09-11T04:51:48Z", + "updated_at": "2025-09-11T04:51:48Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175051151, + "node_id": "SC_kwDOAY2xT88AAAAJHwPXjw", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Linux/576935", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-09-11T04:51:21Z", + "updated_at": "2025-09-11T04:51:21Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175051020, + "node_id": "SC_kwDOAY2xT88AAAAJHwPXDA", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Linux/576935", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-09-11T04:51:21Z", + "updated_at": "2025-09-11T04:51:21Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175049448, + "node_id": "SC_kwDOAY2xT88AAAAJHwPQ6A", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:51:13Z", + "updated_at": "2025-09-11T04:51:13Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175048733, + "node_id": "SC_kwDOAY2xT88AAAAJHwPOHQ", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:51:09Z", + "updated_at": "2025-09-11T04:51:09Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175044363, + "node_id": "SC_kwDOAY2xT88AAAAJHwO9Cw", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Linux/576935", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-09-11T04:50:47Z", + "updated_at": "2025-09-11T04:50:47Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175044256, + "node_id": "SC_kwDOAY2xT88AAAAJHwO8oA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestPullRequests/576930", + "context": "Test (Keyman - Android)", + "created_at": "2025-09-11T04:50:46Z", + "updated_at": "2025-09-11T04:50:46Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175038088, + "node_id": "SC_kwDOAY2xT88AAAAJHwOkiA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:50:14Z", + "updated_at": "2025-09-11T04:50:14Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175033010, + "node_id": "SC_kwDOAY2xT88AAAAJHwOQsg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_macOS/576925", + "context": "Test: Keyman Core - macOS (Common)", + "created_at": "2025-09-11T04:49:50Z", + "updated_at": "2025-09-11T04:49:50Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175032935, + "node_id": "SC_kwDOAY2xT88AAAAJHwOQZw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_KeymanMac_PullRequests/576924", + "context": "Test (Keyman - macOS)", + "created_at": "2025-09-11T04:49:49Z", + "updated_at": "2025-09-11T04:49:49Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175026018, + "node_id": "SC_kwDOAY2xT88AAAAJHwN1Yg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:49:12Z", + "updated_at": "2025-09-11T04:49:12Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175005335, + "node_id": "SC_kwDOAY2xT88AAAAJHwMklw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:47:30Z", + "updated_at": "2025-09-11T04:47:30Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39175005126, + "node_id": "SC_kwDOAY2xT88AAAAJHwMjxg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:47:29Z", + "updated_at": "2025-09-11T04:47:29Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175001040, + "node_id": "SC_kwDOAY2xT88AAAAJHwMT0A", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestSamplesAndTestProjects/576931", + "context": "Test: Samples and Test projects (Keyman - Android)", + "created_at": "2025-09-11T04:47:09Z", + "updated_at": "2025-09-11T04:47:09Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39175000940, + "node_id": "SC_kwDOAY2xT88AAAAJHwMTbA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPullRequests/576926", + "context": "Test (Keyman - Windows)", + "created_at": "2025-09-11T04:47:09Z", + "updated_at": "2025-09-11T04:47:09Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174999511, + "node_id": "SC_kwDOAY2xT88AAAAJHwMN1w", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Developer_Test/576932", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-09-11T04:47:02Z", + "updated_at": "2025-09-11T04:47:02Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174999409, + "node_id": "SC_kwDOAY2xT88AAAAJHwMNcQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Web/576929", + "context": "Test: Common - Web (Common)", + "created_at": "2025-09-11T04:47:02Z", + "updated_at": "2025-09-11T04:47:02Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174993947, + "node_id": "SC_kwDOAY2xT88AAAAJHwL4Gw", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_KeymanMac_PullRequests/576924", + "context": "Test (Keyman - macOS)", + "created_at": "2025-09-11T04:46:36Z", + "updated_at": "2025-09-11T04:46:36Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174993870, + "node_id": "SC_kwDOAY2xT88AAAAJHwL3zg", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Mac/576922", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-09-11T04:46:35Z", + "updated_at": "2025-09-11T04:46:35Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174993783, + "node_id": "SC_kwDOAY2xT88AAAAJHwL3dw", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Mac/576922", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-09-11T04:46:35Z", + "updated_at": "2025-09-11T04:46:35Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39174990184, + "node_id": "SC_kwDOAY2xT88AAAAJHwLpaA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:46:18Z", + "updated_at": "2025-09-11T04:46:18Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39174989998, + "node_id": "SC_kwDOAY2xT88AAAAJHwLorg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:46:17Z", + "updated_at": "2025-09-11T04:46:17Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39174989781, + "node_id": "SC_kwDOAY2xT88AAAAJHwLn1Q", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:46:16Z", + "updated_at": "2025-09-11T04:46:16Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39174989098, + "node_id": "SC_kwDOAY2xT88AAAAJHwLlKg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:46:12Z", + "updated_at": "2025-09-11T04:46:12Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174985891, + "node_id": "SC_kwDOAY2xT88AAAAJHwLYow", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Linux/576934", + "context": "Test - Linux (Keyman - Developer)", + "created_at": "2025-09-11T04:45:56Z", + "updated_at": "2025-09-11T04:45:56Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174985810, + "node_id": "SC_kwDOAY2xT88AAAAJHwLYUg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keymanweb_TestPullRequests/576917", + "context": "Test (Keyman - Web)", + "created_at": "2025-09-11T04:45:56Z", + "updated_at": "2025-09-11T04:45:56Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174985723, + "node_id": "SC_kwDOAY2xT88AAAAJHwLX-w", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestPullRequests/576930", + "context": "Test (Keyman - Android)", + "created_at": "2025-09-11T04:45:55Z", + "updated_at": "2025-09-11T04:45:55Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174985645, + "node_id": "SC_kwDOAY2xT88AAAAJHwLXrQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Linux/576921", + "context": "Test: Keyman Core - Linux (Common)", + "created_at": "2025-09-11T04:45:55Z", + "updated_at": "2025-09-11T04:45:55Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174985556, + "node_id": "SC_kwDOAY2xT88AAAAJHwLXVA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Mac/576922", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-09-11T04:45:54Z", + "updated_at": "2025-09-11T04:45:54Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174985505, + "node_id": "SC_kwDOAY2xT88AAAAJHwLXIQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestSamplesAndTestProjects/576916", + "context": "Test: Samples and Test Projects (Keyman - iOS)", + "created_at": "2025-09-11T04:45:54Z", + "updated_at": "2025-09-11T04:45:54Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174984029, + "node_id": "SC_kwDOAY2xT88AAAAJHwLRXQ", + "state": "success", + "description": "⚠️ Warning: keymanweb.js is 170 bytes (0%) larger than 19.0.116, now 432,283 bytes", + "target_url": null, + "context": "check/web/file-size", + "created_at": "2025-09-11T04:45:46Z", + "updated_at": "2025-09-11T04:45:46Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174958855, + "node_id": "SC_kwDOAY2xT88AAAAJHwJvBw", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Web/576929", + "context": "Test: Common - Web (Common)", + "created_at": "2025-09-11T04:43:34Z", + "updated_at": "2025-09-11T04:43:34Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174958775, + "node_id": "SC_kwDOAY2xT88AAAAJHwJutw", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/576923", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-09-11T04:43:33Z", + "updated_at": "2025-09-11T04:43:33Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174958372, + "node_id": "SC_kwDOAY2xT88AAAAJHwJtJA", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/576923", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-09-11T04:43:31Z", + "updated_at": "2025-09-11T04:43:31Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174938339, + "node_id": "SC_kwDOAY2xT88AAAAJHwIe4w", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Linux/576921", + "context": "Test: Keyman Core - Linux (Common)", + "created_at": "2025-09-11T04:41:54Z", + "updated_at": "2025-09-11T04:41:54Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174938237, + "node_id": "SC_kwDOAY2xT88AAAAJHwIefQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanLinux_TestPullRequests/576919", + "context": "Test (Keyman - Linux)", + "created_at": "2025-09-11T04:41:53Z", + "updated_at": "2025-09-11T04:41:53Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174932962, + "node_id": "SC_kwDOAY2xT88AAAAJHwIJ4g", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Windows/576928", + "context": "Test: Keyman Core - Windows (Common)", + "created_at": "2025-09-11T04:41:25Z", + "updated_at": "2025-09-11T04:41:25Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174932882, + "node_id": "SC_kwDOAY2xT88AAAAJHwIJkg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPrRenderOnScreenKeyboards/576927", + "context": "Test: Render On Screen Keyboards (Keyman - Windows)", + "created_at": "2025-09-11T04:41:24Z", + "updated_at": "2025-09-11T04:41:24Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 39174908967, + "node_id": "SC_kwDOAY2xT88AAAAJHwGsJw", + "state": "pending", + "description": "Ubuntu packaging started", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/17634187943", + "context": "Ubuntu Packaging", + "created_at": "2025-09-11T04:39:26Z", + "updated_at": "2025-09-11T04:39:26Z", + "creator": { + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174908607, + "node_id": "SC_kwDOAY2xT88AAAAJHwGqvw", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanLinux_TestPullRequests/576919", + "context": "Test (Keyman - Linux)", + "created_at": "2025-09-11T04:39:24Z", + "updated_at": "2025-09-11T04:39:24Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174908507, + "node_id": "SC_kwDOAY2xT88AAAAJHwGqWw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576935", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-09-11T04:39:24Z", + "updated_at": "2025-09-11T04:39:24Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174908427, + "node_id": "SC_kwDOAY2xT88AAAAJHwGqCw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576934", + "context": "Test - Linux (Keyman - Developer)", + "created_at": "2025-09-11T04:39:23Z", + "updated_at": "2025-09-11T04:39:23Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174908273, + "node_id": "SC_kwDOAY2xT88AAAAJHwGpcQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576933", + "context": "Test - macOS (Keyman - Developer)", + "created_at": "2025-09-11T04:39:22Z", + "updated_at": "2025-09-11T04:39:22Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174908169, + "node_id": "SC_kwDOAY2xT88AAAAJHwGpCQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576932", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-09-11T04:39:22Z", + "updated_at": "2025-09-11T04:39:22Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174908061, + "node_id": "SC_kwDOAY2xT88AAAAJHwGonQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576931", + "context": "Test: Samples and Test projects (Keyman - Android)", + "created_at": "2025-09-11T04:39:22Z", + "updated_at": "2025-09-11T04:39:22Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907908, + "node_id": "SC_kwDOAY2xT88AAAAJHwGoBA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576930", + "context": "Test (Keyman - Android)", + "created_at": "2025-09-11T04:39:21Z", + "updated_at": "2025-09-11T04:39:21Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907798, + "node_id": "SC_kwDOAY2xT88AAAAJHwGnlg", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576929", + "context": "Test: Common - Web (Common)", + "created_at": "2025-09-11T04:39:20Z", + "updated_at": "2025-09-11T04:39:20Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907673, + "node_id": "SC_kwDOAY2xT88AAAAJHwGnGQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576928", + "context": "Test: Keyman Core - Windows (Common)", + "created_at": "2025-09-11T04:39:20Z", + "updated_at": "2025-09-11T04:39:20Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907586, + "node_id": "SC_kwDOAY2xT88AAAAJHwGmwg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPrRenderOnScreenKeyboards/576927", + "context": "Test: Render On Screen Keyboards (Keyman - Windows)", + "created_at": "2025-09-11T04:39:19Z", + "updated_at": "2025-09-11T04:39:19Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907492, + "node_id": "SC_kwDOAY2xT88AAAAJHwGmZA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPrRenderOnScreenKeyboards/576927", + "context": "Test: Render On Screen Keyboards (Keyman - Windows)", + "created_at": "2025-09-11T04:39:19Z", + "updated_at": "2025-09-11T04:39:19Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907382, + "node_id": "SC_kwDOAY2xT88AAAAJHwGl9g", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPullRequests/576926", + "context": "Test (Keyman - Windows)", + "created_at": "2025-09-11T04:39:19Z", + "updated_at": "2025-09-11T04:39:19Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907255, + "node_id": "SC_kwDOAY2xT88AAAAJHwGldw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPullRequests/576926", + "context": "Test (Keyman - Windows)", + "created_at": "2025-09-11T04:39:18Z", + "updated_at": "2025-09-11T04:39:18Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907127, + "node_id": "SC_kwDOAY2xT88AAAAJHwGk9w", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576925", + "context": "Test: Keyman Core - macOS (Common)", + "created_at": "2025-09-11T04:39:17Z", + "updated_at": "2025-09-11T04:39:17Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174907022, + "node_id": "SC_kwDOAY2xT88AAAAJHwGkjg", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576924", + "context": "Test (Keyman - macOS)", + "created_at": "2025-09-11T04:39:17Z", + "updated_at": "2025-09-11T04:39:17Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906933, + "node_id": "SC_kwDOAY2xT88AAAAJHwGkNQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/576923", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-09-11T04:39:16Z", + "updated_at": "2025-09-11T04:39:16Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906844, + "node_id": "SC_kwDOAY2xT88AAAAJHwGj3A", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/576923", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-09-11T04:39:16Z", + "updated_at": "2025-09-11T04:39:16Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906746, + "node_id": "SC_kwDOAY2xT88AAAAJHwGjeg", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576922", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-09-11T04:39:16Z", + "updated_at": "2025-09-11T04:39:16Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906635, + "node_id": "SC_kwDOAY2xT88AAAAJHwGjCw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576921", + "context": "Test: Keyman Core - Linux (Common)", + "created_at": "2025-09-11T04:39:15Z", + "updated_at": "2025-09-11T04:39:15Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906548, + "node_id": "SC_kwDOAY2xT88AAAAJHwGitA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576920", + "context": "Test:Integration (Keyman - Linux)", + "created_at": "2025-09-11T04:39:14Z", + "updated_at": "2025-09-11T04:39:14Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906463, + "node_id": "SC_kwDOAY2xT88AAAAJHwGiXw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/576919", + "context": "Test (Keyman - Linux)", + "created_at": "2025-09-11T04:39:14Z", + "updated_at": "2025-09-11T04:39:14Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906355, + "node_id": "SC_kwDOAY2xT88AAAAJHwGh8w", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_WASM/576918", + "context": "Test: Keyman Core - WASM (Common)", + "created_at": "2025-09-11T04:39:14Z", + "updated_at": "2025-09-11T04:39:14Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906251, + "node_id": "SC_kwDOAY2xT88AAAAJHwGhiw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_WASM/576918", + "context": "Test: Keyman Core - WASM (Common)", + "created_at": "2025-09-11T04:39:13Z", + "updated_at": "2025-09-11T04:39:13Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906139, + "node_id": "SC_kwDOAY2xT88AAAAJHwGhGw", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keymanweb_TestPullRequests/576917", + "context": "Test (Keyman - Web)", + "created_at": "2025-09-11T04:39:12Z", + "updated_at": "2025-09-11T04:39:12Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174906037, + "node_id": "SC_kwDOAY2xT88AAAAJHwGgtQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keymanweb_TestPullRequests/576917", + "context": "Test (Keyman - Web)", + "created_at": "2025-09-11T04:39:12Z", + "updated_at": "2025-09-11T04:39:12Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174905929, + "node_id": "SC_kwDOAY2xT88AAAAJHwGgSQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestPullRequests/576915", + "context": "Test (Keyman - iOS)", + "created_at": "2025-09-11T04:39:12Z", + "updated_at": "2025-09-11T04:39:12Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174905842, + "node_id": "SC_kwDOAY2xT88AAAAJHwGf8g", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestSamplesAndTestProjects/576916", + "context": "Test: Samples and Test Projects (Keyman - iOS)", + "created_at": "2025-09-11T04:39:11Z", + "updated_at": "2025-09-11T04:39:11Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174905756, + "node_id": "SC_kwDOAY2xT88AAAAJHwGfnA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestSamplesAndTestProjects/576916", + "context": "Test: Samples and Test Projects (Keyman - iOS)", + "created_at": "2025-09-11T04:39:11Z", + "updated_at": "2025-09-11T04:39:11Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 39174905667, + "node_id": "SC_kwDOAY2xT88AAAAJHwGfQw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestPullRequests/576915", + "context": "Test (Keyman - iOS)", + "created_at": "2025-09-11T04:39:10Z", + "updated_at": "2025-09-11T04:39:10Z", + "creator": { + "login": "keyman-server", + "id": 7018967, + "node_id": "MDQ6VXNlcjcwMTg5Njc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keyman-server", + "html_url": "https://github.com/keyman-server", + "followers_url": "https://api.github.com/users/keyman-server/followers", + "following_url": "https://api.github.com/users/keyman-server/following{/other_user}", + "gists_url": "https://api.github.com/users/keyman-server/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keyman-server/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keyman-server/subscriptions", + "organizations_url": "https://api.github.com/users/keyman-server/orgs", + "repos_url": "https://api.github.com/users/keyman-server/repos", + "events_url": "https://api.github.com/users/keyman-server/events{/privacy}", + "received_events_url": "https://api.github.com/users/keyman-server/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/390a5bc6dba73cd32bc8c7290ac73321f6504deb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 39174900789, + "node_id": "SC_kwDOAY2xT88AAAAJHwGMNQ", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14728#issuecomment-3277442589", + "context": "user_testing", + "created_at": "2025-09-11T04:38:46Z", + "updated_at": "2025-09-11T04:38:46Z", + "creator": { + "login": "keymanapp-test-bot[bot]", + "id": 89363325, + "node_id": "MDM6Qm90ODkzNjMzMjU=", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D", + "html_url": "https://github.com/apps/keymanapp-test-bot", + "followers_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp-test-bot%5Bbot%5D/received_events", + "type": "Bot", + "user_view_type": "public", + "site_admin": false + } + } +] \ No newline at end of file diff --git a/resources/build/pr-build-status/pr-build-status.mjs b/resources/build/pr-build-status/pr-build-status.mjs index 091377d942..e799d002e1 100644 --- a/resources/build/pr-build-status/pr-build-status.mjs +++ b/resources/build/pr-build-status/pr-build-status.mjs @@ -27,35 +27,52 @@ // check/web/file-size // + function addLog(message) { + console.log(message); + return message + String.fromCharCode(13); + } + + function addStatus(o, type, context, state) { + if(!o[context]) { + o[context] = {type, state}; + return addLog(`Found ${context}: ${state}`); + } else { + return addLog(`Skipping repeated ${context}: ${state}`); + } + } + function reduceStatuses(statuses) { + let summary = ''; const filtered_statuses = statuses.reduce((o, status) => { if(status.creator?.login == 'keyman-server' && status.context.startsWith('Test')) { - if(!o[status.context]) o[status.context] = {type: 'build', state: status.state}; + summary += addStatus(o, 'build', status.context, status.state); } else if(status.creator?.login == 'keymanapp-test-bot[bot]' && status.context == 'user_testing') { - if(!o[status.context]) o[status.context] = {type: 'user-test', state: status.state};; + summary += addStatus(o, 'user-test', status.context, status.state); } else if(status.context == 'API Verification') { - if(!o[status.context]) o[status.context] = {type: 'check', state: status.state}; + summary += addStatus(o, 'check', status.context, status.state); } else if(status.context == 'Ubuntu Packaging') { - if(!o[status.context]) o[status.context] = {type: 'build', state: status.state}; + summary += addStatus(o, 'build', status.context, status.state); } else if(status.context == 'check/web/file-size') { // Ignore check/web/file-size -- we won't block automerge for this at this point + summary += addLog(`Skipping ${status.context}`); } else { // We fail with an 'unknown status' response if we get a new status check // so we can be sure we are not skipping known status checks + summary += addLog(`Found unknown ${status.context}: ${status.state}`); o[status.context] = {type: 'unknown', state: status.state}; } return o; }, {}); - return filtered_statuses; + return { filtered_statuses, summary }; } // // Given the collection of status checks we care about, return - // an aggregate status -- error, failed, pending, or success, + // an aggregate status -- error, failure, pending, or success, // and a summary description // - function calculateFinalStatus(filtered_statuses) { + function calculateFinalStatus(filtered_statuses, summary) { const counts = {}; let hasBuilds = false; for(const context of Object.keys(filtered_statuses)) { @@ -79,7 +96,7 @@ const state = counts.error ? 'error' : - counts.failed ? 'failed' : + counts.failure ? 'failure' : counts.pending ? 'pending' : 'success'; @@ -90,11 +107,11 @@ description += `${count} check${count == 1 ? '' : 's'} ${state}`; } appendDescription(counts.error, 'in an error state ❌'); - appendDescription(counts.failed, 'failed ❌'); + appendDescription(counts.failure, 'failed ❌'); appendDescription(counts.pending, 'pending ⌛'); appendDescription(counts.success, 'completed successfully ✅'); - return [ state, description ]; + return [ state, description, summary ]; } async function getCommitStatuses(github, owner, repo, sha) { @@ -126,9 +143,9 @@ return ['error', 'Failed to retrieve status checks from GitHub ❌']; } - const filtered_statuses = reduceStatuses(statuses); + const { filtered_statuses, summary } = reduceStatuses(statuses); - const result = calculateFinalStatus(filtered_statuses); + const result = calculateFinalStatus(filtered_statuses, summary); return result; } @@ -149,7 +166,7 @@ return check.data.id; } - async function updateCheck(github, owner, repo, checkRunId, status, description) { + async function updateCheck(github, owner, repo, checkRunId, status, description, summary) { const checkStatus = status == 'pending' ? 'in_progress' : 'completed'; const conclusion = checkStatus == 'in_progress' ? undefined : (status == 'success' ? 'success' : 'failure'); @@ -161,7 +178,7 @@ conclusion, output: { title: description, - summary: '' + summary } }); } @@ -179,7 +196,7 @@ // const checkRunId = await createCheck(github, owner, repo, sha); // const res = await test(github, owner, repo, sha); - // await updateCheck(github, owner, repo, checkRunId, res[0], res[1]); + // await updateCheck(github, owner, repo, checkRunId, res[0], res[1], res[2]); // CLONE-COMMENTED:END diff --git a/resources/build/pr-build-status/pr-build-status.tests.mjs b/resources/build/pr-build-status/pr-build-status.tests.mjs index bfb49fe64b..fae7f65628 100644 --- a/resources/build/pr-build-status/pr-build-status.tests.mjs +++ b/resources/build/pr-build-status/pr-build-status.tests.mjs @@ -1,6 +1,7 @@ import * as fs from 'node:fs'; import 'mocha'; import { assert } from 'chai'; +import sinon from 'sinon'; import { unitTestEndpoints } from './pr-build-status.mjs'; @@ -9,9 +10,10 @@ import { getOctokit } from '@actions/github'; import * as process from 'node:process'; const commits = { + '390a5bc6dba73cd32bc8c7290ac73321f6504deb': [ 'failure', '3 checks failed ❌; 21 checks completed successfully ✅'], '825f7d2df0069fa56a8f2a534b42a49ee1f21ca1': [ 'success', '24 checks completed successfully ✅' ], - '870d37f56b489ef5067b4352a06505369d9038e0': [ 'error', '6 checks in an error state ❌; 17 checks completed successfully ✅' ], - '11a558e926370253db37026c95b7ff5a6aa1e8fc': [ 'success', '22 checks completed successfully ✅' ], + '870d37f56b489ef5067b4352a06505369d9038e0': [ 'error', '6 checks in an error state ❌; 1 check failed ❌; 17 checks completed successfully ✅' ], + '11a558e926370253db37026c95b7ff5a6aa1e8fc': [ 'failure', '2 checks failed ❌; 22 checks completed successfully ✅' ], '8fd5ccc9026b85d6a780d8bcf862380e7a89aefb': [ 'pending', 'Builds have not yet been triggered ⌛' ], '153683cfb007c6066c9dcf71d25afa4c66efa17f': [ 'pending', 'Builds have not yet been triggered ⌛' ], }; @@ -32,13 +34,23 @@ if(debug) { } describe('pr-build-status', function () { + beforeEach(() => { + // just silencing the logging from pr-build-status bot + // in order to make the test results clearer + sinon.stub(console, 'log'); + }); + + afterEach(() => { + console.log.restore(); + }); + for(const sha of Object.keys(commits)) { it(`should return '${commits[sha][1]}' for sha ${sha}`, function () { const json = JSON.parse(fs.readFileSync(`fixtures/${sha}-statuses.json`,'utf-8')); const status = unitTestEndpoints.calculateCheckResult(json); assert.isNotNull(status); assert.isArray(status); - assert.deepStrictEqual(status, commits[sha]); + assert.deepStrictEqual(status.splice(0, 2), commits[sha]); }); } }); From 6b4a0af5c4049deb1fe02f0f8f3f9f49871d3639 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 11 Sep 2025 13:15:42 +0200 Subject: [PATCH 45/52] chore: test failing summary check --- .github/workflows/pr-build-status.yml | 74 ++++++++++++++------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/.github/workflows/pr-build-status.yml b/.github/workflows/pr-build-status.yml index 89a56b3877..b26af5f7cd 100644 --- a/.github/workflows/pr-build-status.yml +++ b/.github/workflows/pr-build-status.yml @@ -1,40 +1,40 @@ # GENERATED FILE - DO NOT EDIT! -# -# Keyman is copyright (C) SIL Global. MIT License. -# -# Do not modify the script in .github/workflows/pr-build-status.yml directly; -# instead work on the sources in resources/build/pr-build-status and use the -# build.sh script to rebuild the .github/workflows/pr-build-status.yml file from -# them. -# -# build.sh will append the relevant portions of -# resources/build/pr-build-status/pr-build-status.mjs to the content in -# resources/build/pr-build-status/pr-build-status.prefix.yml to form the .github -# workflow file. -# -name: Keyman Build Summary -on: - status: - push: - branches-ignore: - - master - - beta - - stable-* - workflow_dispatch: - inputs: - commit: - description: 'Commit sha' - required: true - type: string -jobs: - run_pr_build_status: - name: Summarize build status checks - runs-on: ubuntu-latest - steps: - - name: Check PR build status - id: run_pr_build_status_script - uses: actions/github-script@v7 - with: +# +# Keyman is copyright (C) SIL Global. MIT License. +# +# Do not modify the script in .github/workflows/pr-build-status.yml directly; +# instead work on the sources in resources/build/pr-build-status and use the +# build.sh script to rebuild the .github/workflows/pr-build-status.yml file from +# them. +# +# build.sh will append the relevant portions of +# resources/build/pr-build-status/pr-build-status.mjs to the content in +# resources/build/pr-build-status/pr-build-status.prefix.yml to form the .github +# workflow file. +# +name: Keyman Build Summary +on: + status: + push: + branches-ignore: + - master + - beta + - stable-* + workflow_dispatch: + inputs: + commit: + description: 'Commit sha' + required: true + type: string +jobs: + run_pr_build_status: + name: Summarize build status checks + runs-on: ubuntu-latest + steps: + - name: Check PR build status + id: run_pr_build_status_script + uses: actions/github-script@v7 + with: script: | // This code is copied out of resources/build/pr-build-status/pr-build-status.mjs // where it is tested. It is copied inline here in order to avoid requiring the @@ -198,6 +198,8 @@ jobs: const checkStatus = status == 'pending' ? 'in_progress' : 'completed'; const conclusion = checkStatus == 'in_progress' ? undefined : (status == 'success' ? 'success' : 'failure'); + console.log(`updateCheck: status:'${status}', description:'${description}'', summary:'${summary}'`); + await github.rest.checks.update({ owner, repo, From 7e3925ebfc54727ea22dfab61b3d35ad6394b1bb Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 11 Sep 2025 13:18:43 +0200 Subject: [PATCH 46/52] fix(resources): always pass summary through Fixes: #14731 --- .github/workflows/pr-build-status.yml | 82 +++++++++---------- .../build/pr-build-status/pr-build-status.mjs | 8 +- 2 files changed, 42 insertions(+), 48 deletions(-) diff --git a/.github/workflows/pr-build-status.yml b/.github/workflows/pr-build-status.yml index b26af5f7cd..43474da0f2 100644 --- a/.github/workflows/pr-build-status.yml +++ b/.github/workflows/pr-build-status.yml @@ -1,40 +1,40 @@ # GENERATED FILE - DO NOT EDIT! -# -# Keyman is copyright (C) SIL Global. MIT License. -# -# Do not modify the script in .github/workflows/pr-build-status.yml directly; -# instead work on the sources in resources/build/pr-build-status and use the -# build.sh script to rebuild the .github/workflows/pr-build-status.yml file from -# them. -# -# build.sh will append the relevant portions of -# resources/build/pr-build-status/pr-build-status.mjs to the content in -# resources/build/pr-build-status/pr-build-status.prefix.yml to form the .github -# workflow file. -# -name: Keyman Build Summary -on: - status: - push: - branches-ignore: - - master - - beta - - stable-* - workflow_dispatch: - inputs: - commit: - description: 'Commit sha' - required: true - type: string -jobs: - run_pr_build_status: - name: Summarize build status checks - runs-on: ubuntu-latest - steps: - - name: Check PR build status - id: run_pr_build_status_script - uses: actions/github-script@v7 - with: +# +# Keyman is copyright (C) SIL Global. MIT License. +# +# Do not modify the script in .github/workflows/pr-build-status.yml directly; +# instead work on the sources in resources/build/pr-build-status and use the +# build.sh script to rebuild the .github/workflows/pr-build-status.yml file from +# them. +# +# build.sh will append the relevant portions of +# resources/build/pr-build-status/pr-build-status.mjs to the content in +# resources/build/pr-build-status/pr-build-status.prefix.yml to form the .github +# workflow file. +# +name: Keyman Build Summary +on: + status: + push: + branches-ignore: + - master + - beta + - stable-* + workflow_dispatch: + inputs: + commit: + description: 'Commit sha' + required: true + type: string +jobs: + run_pr_build_status: + name: Summarize build status checks + runs-on: ubuntu-latest + steps: + - name: Check PR build status + id: run_pr_build_status_script + uses: actions/github-script@v7 + with: script: | // This code is copied out of resources/build/pr-build-status/pr-build-status.mjs // where it is tested. It is copied inline here in order to avoid requiring the @@ -108,7 +108,7 @@ jobs: if(type == 'unknown') { // We special-case for unknown status checks, and never permit them return [ - 'error', `An unknown context ${context} was found, cannot calculate build status.` + 'error', `An unknown context ${context} was found, cannot calculate build status.`, summary ]; } if(type == 'build') { @@ -119,7 +119,7 @@ jobs: // If we do not have any statuses yet, we wait if(Object.keys(filtered_statuses).length == 0 || !hasBuilds) { - return ['pending', 'Builds have not yet been triggered ⌛']; + return ['pending', 'Builds have not yet been triggered ⌛', summary]; } const state = @@ -172,9 +172,7 @@ jobs: } const { filtered_statuses, summary } = reduceStatuses(statuses); - - const result = calculateFinalStatus(filtered_statuses, summary); - return result; + return calculateFinalStatus(filtered_statuses, summary); } async function test(github, owner, repo, sha) { @@ -198,8 +196,6 @@ jobs: const checkStatus = status == 'pending' ? 'in_progress' : 'completed'; const conclusion = checkStatus == 'in_progress' ? undefined : (status == 'success' ? 'success' : 'failure'); - console.log(`updateCheck: status:'${status}', description:'${description}'', summary:'${summary}'`); - await github.rest.checks.update({ owner, repo, diff --git a/resources/build/pr-build-status/pr-build-status.mjs b/resources/build/pr-build-status/pr-build-status.mjs index e799d002e1..64f16ad58a 100644 --- a/resources/build/pr-build-status/pr-build-status.mjs +++ b/resources/build/pr-build-status/pr-build-status.mjs @@ -80,7 +80,7 @@ if(type == 'unknown') { // We special-case for unknown status checks, and never permit them return [ - 'error', `An unknown context ${context} was found, cannot calculate build status.` + 'error', `An unknown context ${context} was found, cannot calculate build status.`, summary ]; } if(type == 'build') { @@ -91,7 +91,7 @@ // If we do not have any statuses yet, we wait if(Object.keys(filtered_statuses).length == 0 || !hasBuilds) { - return ['pending', 'Builds have not yet been triggered ⌛']; + return ['pending', 'Builds have not yet been triggered ⌛', summary]; } const state = @@ -144,9 +144,7 @@ } const { filtered_statuses, summary } = reduceStatuses(statuses); - - const result = calculateFinalStatus(filtered_statuses, summary); - return result; + return calculateFinalStatus(filtered_statuses, summary); } async function test(github, owner, repo, sha) { From 6d6cc36c8f9a816dc91dc42522aed2c5f197f147 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 11 Sep 2025 13:25:45 +0200 Subject: [PATCH 47/52] fix(resources): add more logging for build-status --- .github/workflows/pr-build-status.yml | 7 ++++++- resources/build/pr-build-status/pr-build-status.mjs | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-build-status.yml b/.github/workflows/pr-build-status.yml index 43474da0f2..185ba2247e 100644 --- a/.github/workflows/pr-build-status.yml +++ b/.github/workflows/pr-build-status.yml @@ -107,6 +107,7 @@ jobs: const { state, type } = filtered_statuses[context]; if(type == 'unknown') { // We special-case for unknown status checks, and never permit them + console.error(`An unknown context ${context} was found, cannot calculate build status.`); return [ 'error', `An unknown context ${context} was found, cannot calculate build status.`, summary ]; @@ -119,6 +120,7 @@ jobs: // If we do not have any statuses yet, we wait if(Object.keys(filtered_statuses).length == 0 || !hasBuilds) { + console.log(`Builds not yet triggered`); return ['pending', 'Builds have not yet been triggered ⌛', summary]; } @@ -139,10 +141,12 @@ jobs: appendDescription(counts.pending, 'pending ⌛'); appendDescription(counts.success, 'completed successfully ✅'); + console.log(`Finished filtering and reviewing status checks: ${state}: ${description}`); return [ state, description, summary ]; } async function getCommitStatuses(github, owner, repo, sha) { + console.log(`Getting commit statuses for ${sha}`); const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{sha}/statuses', { owner, repo, @@ -155,6 +159,7 @@ jobs: } async function getCommitCheckRuns(github, owner, repo, sha) { + console.log(`Getting commit check runs for ${sha}`); const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{sha}/check-runs', { owner, repo, @@ -168,7 +173,7 @@ jobs: function calculateCheckResult(statuses) { if(!Array.isArray(statuses)) { - return ['error', 'Failed to retrieve status checks from GitHub ❌']; + return ['error', 'Failed to retrieve status checks from GitHub ❌', 'statuses is not an array']; } const { filtered_statuses, summary } = reduceStatuses(statuses); diff --git a/resources/build/pr-build-status/pr-build-status.mjs b/resources/build/pr-build-status/pr-build-status.mjs index 64f16ad58a..f8f1b62c1c 100644 --- a/resources/build/pr-build-status/pr-build-status.mjs +++ b/resources/build/pr-build-status/pr-build-status.mjs @@ -79,6 +79,7 @@ const { state, type } = filtered_statuses[context]; if(type == 'unknown') { // We special-case for unknown status checks, and never permit them + console.error(`An unknown context ${context} was found, cannot calculate build status.`); return [ 'error', `An unknown context ${context} was found, cannot calculate build status.`, summary ]; @@ -91,6 +92,7 @@ // If we do not have any statuses yet, we wait if(Object.keys(filtered_statuses).length == 0 || !hasBuilds) { + console.log(`Builds not yet triggered`); return ['pending', 'Builds have not yet been triggered ⌛', summary]; } @@ -111,10 +113,12 @@ appendDescription(counts.pending, 'pending ⌛'); appendDescription(counts.success, 'completed successfully ✅'); + console.log(`Finished filtering and reviewing status checks: ${state}: ${description}`); return [ state, description, summary ]; } async function getCommitStatuses(github, owner, repo, sha) { + console.log(`Getting commit statuses for ${sha}`); const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{sha}/statuses', { owner, repo, @@ -127,6 +131,7 @@ } async function getCommitCheckRuns(github, owner, repo, sha) { + console.log(`Getting commit check runs for ${sha}`); const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{sha}/check-runs', { owner, repo, @@ -140,7 +145,7 @@ function calculateCheckResult(statuses) { if(!Array.isArray(statuses)) { - return ['error', 'Failed to retrieve status checks from GitHub ❌']; + return ['error', 'Failed to retrieve status checks from GitHub ❌', 'statuses is not an array']; } const { filtered_statuses, summary } = reduceStatuses(statuses); From c1782234dfb72d015df9ce18a943e18877219cce Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 11 Sep 2025 14:50:10 +0200 Subject: [PATCH 48/52] Update .github/workflows/pr-build-status.yml --- .github/workflows/pr-build-status.yml | 384 +++++++++++++------------- 1 file changed, 192 insertions(+), 192 deletions(-) diff --git a/.github/workflows/pr-build-status.yml b/.github/workflows/pr-build-status.yml index 185ba2247e..585a0b19b8 100644 --- a/.github/workflows/pr-build-status.yml +++ b/.github/workflows/pr-build-status.yml @@ -1,4 +1,4 @@ -# GENERATED FILE - DO NOT EDIT! +# GENERATED FILE - DO NOT EDIT! # # Keyman is copyright (C) SIL Global. MIT License. # @@ -35,194 +35,194 @@ jobs: id: run_pr_build_status_script uses: actions/github-script@v7 with: - script: | - // This code is copied out of resources/build/pr-build-status/pr-build-status.mjs - // where it is tested. It is copied inline here in order to avoid requiring the - // repository to be checked out, which dramatically reduces the run time of the - // check. - // - // Note: we don't currently look at check runs, only statuses - // - // Verify the following statuses: - // 'user_testing' - // 'API Verification' (github-actions[bot]) - // - // At least 1 of the following statuses must be found: - // 'Test*' (keyman-server), e.g. 'Test Build (Keyman)' - // 'Ubuntu Packaging' (github-actions[bot]) - // - // Ignore the following statuses: - // check/web/file-size - // - - function addLog(message) { - console.log(message); - return message + String.fromCharCode(13); - } - - function addStatus(o, type, context, state) { - if(!o[context]) { - o[context] = {type, state}; - return addLog(`Found ${context}: ${state}`); - } else { - return addLog(`Skipping repeated ${context}: ${state}`); - } - } - - function reduceStatuses(statuses) { - let summary = ''; - const filtered_statuses = statuses.reduce((o, status) => { - if(status.creator?.login == 'keyman-server' && status.context.startsWith('Test')) { - summary += addStatus(o, 'build', status.context, status.state); - } else if(status.creator?.login == 'keymanapp-test-bot[bot]' && status.context == 'user_testing') { - summary += addStatus(o, 'user-test', status.context, status.state); - } else if(status.context == 'API Verification') { - summary += addStatus(o, 'check', status.context, status.state); - } else if(status.context == 'Ubuntu Packaging') { - summary += addStatus(o, 'build', status.context, status.state); - } else if(status.context == 'check/web/file-size') { - // Ignore check/web/file-size -- we won't block automerge for this at this point - summary += addLog(`Skipping ${status.context}`); - } else { - // We fail with an 'unknown status' response if we get a new status check - // so we can be sure we are not skipping known status checks - summary += addLog(`Found unknown ${status.context}: ${status.state}`); - o[status.context] = {type: 'unknown', state: status.state}; - } - return o; - - }, {}); - return { filtered_statuses, summary }; - } - - // - // Given the collection of status checks we care about, return - // an aggregate status -- error, failure, pending, or success, - // and a summary description - // - function calculateFinalStatus(filtered_statuses, summary) { - const counts = {}; - let hasBuilds = false; - for(const context of Object.keys(filtered_statuses)) { - const { state, type } = filtered_statuses[context]; - if(type == 'unknown') { - // We special-case for unknown status checks, and never permit them - console.error(`An unknown context ${context} was found, cannot calculate build status.`); - return [ - 'error', `An unknown context ${context} was found, cannot calculate build status.`, summary - ]; - } - if(type == 'build') { - hasBuilds = true; - } - counts[state] = counts[state] ? counts[state] + 1 : 1; - } - - // If we do not have any statuses yet, we wait - if(Object.keys(filtered_statuses).length == 0 || !hasBuilds) { - console.log(`Builds not yet triggered`); - return ['pending', 'Builds have not yet been triggered ⌛', summary]; - } - - const state = - counts.error ? 'error' : - counts.failure ? 'failure' : - counts.pending ? 'pending' : - 'success'; - - let description = ''; - function appendDescription(count, state) { - if(!count) return; - if(description != '') description += '; '; - description += `${count} check${count == 1 ? '' : 's'} ${state}`; - } - appendDescription(counts.error, 'in an error state ❌'); - appendDescription(counts.failure, 'failed ❌'); - appendDescription(counts.pending, 'pending ⌛'); - appendDescription(counts.success, 'completed successfully ✅'); - - console.log(`Finished filtering and reviewing status checks: ${state}: ${description}`); - return [ state, description, summary ]; - } - - async function getCommitStatuses(github, owner, repo, sha) { - console.log(`Getting commit statuses for ${sha}`); - const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{sha}/statuses', { - owner, - repo, - sha, - headers: { - 'X-GitHub-Api-Version': '2022-11-28' - } - }); - return statuses; - } - - async function getCommitCheckRuns(github, owner, repo, sha) { - console.log(`Getting commit check runs for ${sha}`); - const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{sha}/check-runs', { - owner, - repo, - sha, - headers: { - 'X-GitHub-Api-Version': '2022-11-28' - } - }); - return statuses; - } - - function calculateCheckResult(statuses) { - if(!Array.isArray(statuses)) { - return ['error', 'Failed to retrieve status checks from GitHub ❌', 'statuses is not an array']; - } - - const { filtered_statuses, summary } = reduceStatuses(statuses); - return calculateFinalStatus(filtered_statuses, summary); - } - - async function test(github, owner, repo, sha) { - // Get statuses from sha - const statuses = await getCommitStatuses(github, owner, repo, sha); - return calculateCheckResult(statuses); - } - - async function createCheck(github, owner, repo, sha) { - const check = await github.rest.checks.create({ - owner, - repo, - head_sha: sha, - name: 'Build Outcome', - status: 'in_progress', - }); - return check.data.id; - } - - async function updateCheck(github, owner, repo, checkRunId, status, description, summary) { - const checkStatus = status == 'pending' ? 'in_progress' : 'completed'; - const conclusion = checkStatus == 'in_progress' ? undefined : (status == 'success' ? 'success' : 'failure'); - - await github.rest.checks.update({ - owner, - repo, - check_run_id: checkRunId, - status: checkStatus, - conclusion, - output: { - title: description, - summary - } - }); - } - - const { owner, repo } = context.repo; - const sha = - context.payload?.check_suite?.sha || /* check run completed */ - context.payload?.inputs?.commit || /* manual run */ - context.payload?.after || /* push */ - context.payload?.commit?.sha || /* status */ - context.sha; /* probably 'master'! */ - - const checkRunId = await createCheck(github, owner, repo, sha); - const res = await test(github, owner, repo, sha); - await updateCheck(github, owner, repo, checkRunId, res[0], res[1], res[2]); - + script: | + // This code is copied out of resources/build/pr-build-status/pr-build-status.mjs + // where it is tested. It is copied inline here in order to avoid requiring the + // repository to be checked out, which dramatically reduces the run time of the + // check. + // + // Note: we don't currently look at check runs, only statuses + // + // Verify the following statuses: + // 'user_testing' + // 'API Verification' (github-actions[bot]) + // + // At least 1 of the following statuses must be found: + // 'Test*' (keyman-server), e.g. 'Test Build (Keyman)' + // 'Ubuntu Packaging' (github-actions[bot]) + // + // Ignore the following statuses: + // check/web/file-size + // + + function addLog(message) { + console.log(message); + return message + String.fromCharCode(10); + } + + function addStatus(o, type, context, state) { + if(!o[context]) { + o[context] = {type, state}; + return addLog(`Found ${context}: ${state}`); + } else { + return addLog(`Skipping repeated ${context}: ${state}`); + } + } + + function reduceStatuses(statuses) { + let summary = ''; + const filtered_statuses = statuses.reduce((o, status) => { + if(status.creator?.login == 'keyman-server' && status.context.startsWith('Test')) { + summary += addStatus(o, 'build', status.context, status.state); + } else if(status.creator?.login == 'keymanapp-test-bot[bot]' && status.context == 'user_testing') { + summary += addStatus(o, 'user-test', status.context, status.state); + } else if(status.context == 'API Verification') { + summary += addStatus(o, 'check', status.context, status.state); + } else if(status.context == 'Ubuntu Packaging') { + summary += addStatus(o, 'build', status.context, status.state); + } else if(status.context == 'check/web/file-size') { + // Ignore check/web/file-size -- we won't block automerge for this at this point + summary += addLog(`Skipping ${status.context}`); + } else { + // We fail with an 'unknown status' response if we get a new status check + // so we can be sure we are not skipping known status checks + summary += addLog(`Found unknown ${status.context}: ${status.state}`); + o[status.context] = {type: 'unknown', state: status.state}; + } + return o; + + }, {}); + return { filtered_statuses, summary }; + } + + // + // Given the collection of status checks we care about, return + // an aggregate status -- error, failure, pending, or success, + // and a summary description + // + function calculateFinalStatus(filtered_statuses, summary) { + const counts = {}; + let hasBuilds = false; + for(const context of Object.keys(filtered_statuses)) { + const { state, type } = filtered_statuses[context]; + if(type == 'unknown') { + // We special-case for unknown status checks, and never permit them + console.error(`An unknown context ${context} was found, cannot calculate build status.`); + return [ + 'error', `An unknown context ${context} was found, cannot calculate build status.`, summary + ]; + } + if(type == 'build') { + hasBuilds = true; + } + counts[state] = counts[state] ? counts[state] + 1 : 1; + } + + // If we do not have any statuses yet, we wait + if(Object.keys(filtered_statuses).length == 0 || !hasBuilds) { + console.log(`Builds not yet triggered`); + return ['pending', 'Builds have not yet been triggered ⌛', summary]; + } + + const state = + counts.error ? 'error' : + counts.failure ? 'failure' : + counts.pending ? 'pending' : + 'success'; + + let description = ''; + function appendDescription(count, state) { + if(!count) return; + if(description != '') description += '; '; + description += `${count} check${count == 1 ? '' : 's'} ${state}`; + } + appendDescription(counts.error, 'in an error state ❌'); + appendDescription(counts.failure, 'failed ❌'); + appendDescription(counts.pending, 'pending ⌛'); + appendDescription(counts.success, 'completed successfully ✅'); + + console.log(`Finished filtering and reviewing status checks: ${state}: ${description}`); + return [ state, description, summary ]; + } + + async function getCommitStatuses(github, owner, repo, sha) { + console.log(`Getting commit statuses for ${sha}`); + const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{sha}/statuses', { + owner, + repo, + sha, + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + } + }); + return statuses; + } + + async function getCommitCheckRuns(github, owner, repo, sha) { + console.log(`Getting commit check runs for ${sha}`); + const statuses = await github.paginate('GET /repos/{owner}/{repo}/commits/{sha}/check-runs', { + owner, + repo, + sha, + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + } + }); + return statuses; + } + + function calculateCheckResult(statuses) { + if(!Array.isArray(statuses)) { + return ['error', 'Failed to retrieve status checks from GitHub ❌', 'statuses is not an array']; + } + + const { filtered_statuses, summary } = reduceStatuses(statuses); + return calculateFinalStatus(filtered_statuses, summary); + } + + async function test(github, owner, repo, sha) { + // Get statuses from sha + const statuses = await getCommitStatuses(github, owner, repo, sha); + return calculateCheckResult(statuses); + } + + async function createCheck(github, owner, repo, sha) { + const check = await github.rest.checks.create({ + owner, + repo, + head_sha: sha, + name: 'Build Outcome', + status: 'in_progress', + }); + return check.data.id; + } + + async function updateCheck(github, owner, repo, checkRunId, status, description, summary) { + const checkStatus = status == 'pending' ? 'in_progress' : 'completed'; + const conclusion = checkStatus == 'in_progress' ? undefined : (status == 'success' ? 'success' : 'failure'); + + await github.rest.checks.update({ + owner, + repo, + check_run_id: checkRunId, + status: checkStatus, + conclusion, + output: { + title: description, + summary + } + }); + } + + const { owner, repo } = context.repo; + const sha = + context.payload?.check_suite?.sha || /* check run completed */ + context.payload?.inputs?.commit || /* manual run */ + context.payload?.after || /* push */ + context.payload?.commit?.sha || /* status */ + context.sha; /* probably 'master'! */ + + const checkRunId = await createCheck(github, owner, repo, sha); + const res = await test(github, owner, repo, sha); + await updateCheck(github, owner, repo, checkRunId, res[0], res[1], res[2]); + From ca63bc07caa22ce22a99059f1224d5f6581fd501 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Thu, 11 Sep 2025 14:02:46 -0400 Subject: [PATCH 49/52] auto: increment master version to 19.0.118 Test-bot: skip Build-bot: skip --- HISTORY.md | 5 +++++ VERSION.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 692db6bb10..9361b9a402 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # Keyman Version History +## 19.0.117 alpha 2025-09-11 + +* fix(resources): ignore KEYMAN_VERSION_FOR_FILENAME in test (#14730) +* fix(resources): pr build status - find 'failure' state in status checks and log (#14732) + ## 19.0.116 alpha 2025-09-10 * fix(windows): kmshell continues when sm is in waitingrestart and returns to idle (#14644) diff --git a/VERSION.md b/VERSION.md index 67eaf27978..773f715f5c 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.117 \ No newline at end of file +19.0.118 \ No newline at end of file From db51d21d0ae399b05b509da2e31c6e2591d7d70c Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Tue, 23 Sep 2025 20:17:21 +0700 Subject: [PATCH 50/52] change(web): Apply linting-style suggestions from code review Co-authored-by: Eberhard Beilharz --- .../worker-thread/src/main/correction/classical-calculation.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/classical-calculation.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/classical-calculation.ts index 152c9cf2cf..88e51a676a 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/correction/classical-calculation.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/classical-calculation.ts @@ -619,7 +619,6 @@ export class ClassicalDistanceCalculation { return trimmedInstance; } - // Inputs add an extra row / first index entry. // Inputs add an extra row / first index entry. addInputChar(token: TUnit): ClassicalDistanceCalculation { const returnBuffer = new ClassicalDistanceCalculation(this); @@ -647,7 +646,7 @@ export class ClassicalDistanceCalculation { } addMatchChar(token: TUnit): ClassicalDistanceCalculation { - let returnBuffer = new ClassicalDistanceCalculation(this); + const returnBuffer = new ClassicalDistanceCalculation(this); returnBuffer._addMatchChar(token); return returnBuffer; } From ba431ed3b83d2e2389631a49473d1d060c2e2d31 Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Tue, 23 Sep 2025 20:32:03 +0700 Subject: [PATCH 51/52] change(web): Apply property-alignment suggestions from code review Co-authored-by: Eberhard Beilharz --- .../context/alignment-helpers.tests.ts | 26 +++++++++---------- .../context/context-tokenization.tests.ts | 12 ++++----- .../classical-calculation.tests.ts | 12 ++++----- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/web/src/test/auto/headless/engine/predictive-text/worker-thread/context/alignment-helpers.tests.ts b/web/src/test/auto/headless/engine/predictive-text/worker-thread/context/alignment-helpers.tests.ts index d25528a7fe..4872d31766 100644 --- a/web/src/test/auto/headless/engine/predictive-text/worker-thread/context/alignment-helpers.tests.ts +++ b/web/src/test/auto/headless/engine/predictive-text/worker-thread/context/alignment-helpers.tests.ts @@ -188,8 +188,8 @@ describe('computeAlignment', () => { canAlign: true, editPath: [ {op: 'substitute', input: 0, match: 0}, - {op: 'insert', match: 1}, - {op: 'insert', match: 2} + {op: 'insert', match: 1}, + {op: 'insert', match: 2} ], leadTokenShift: 0, leadEditLength: 0, @@ -347,7 +347,7 @@ describe('computeAlignment', () => { assert.deepEqual(computedAlignment, { canAlign: true, editPath: [ - {op: 'insert', match: 0}, + {op: 'insert', match: 0}, {op: 'match', input: 0, match: 1}, {op: 'match', input: 1, match: 2}, {op: 'match', input: 2, match: 3}, @@ -399,7 +399,7 @@ describe('computeAlignment', () => { assert.deepEqual(computedAlignment, { canAlign: true, editPath: [ - {op: 'insert', match: 0}, + {op: 'insert', match: 0}, {op: 'substitute', input: 0, match: 1}, {op: 'match', input: 1, match: 2}, {op: 'match', input: 2, match: 3}, @@ -430,7 +430,7 @@ describe('computeAlignment', () => { {op: 'match', input: 2, match: 1}, {op: 'match', input: 3, match: 2}, {op: 'match', input: 4, match: 3}, - {op: 'insert', match: 4} + {op: 'insert', match: 4} ], leadTokenShift: -1, leadEditLength: 0, @@ -483,7 +483,7 @@ describe('computeAlignment', () => { {op: 'match', input: 2, match: 2}, {op: 'match', input: 3, match: 3}, {op: 'substitute', input: 4, match: 4}, - {op: 'insert', match: 5} + {op: 'insert', match: 5} ], leadTokenShift: 0, leadEditLength: 1, @@ -505,7 +505,7 @@ describe('computeAlignment', () => { assert.deepEqual(computedAlignment, { canAlign: true, editPath: [ - {op: 'insert', match: 0}, + {op: 'insert', match: 0}, {op: 'match', input: 0, match: 1}, {op: 'match', input: 1, match: 2}, {op: 'match', input: 2, match: 3}, @@ -532,7 +532,7 @@ describe('computeAlignment', () => { assert.deepEqual(computedAlignment, { canAlign: true, editPath: [ - {op: 'insert', match: 0}, + {op: 'insert', match: 0}, {op: 'match', input: 0, match: 1}, {op: 'match', input: 1, match: 2}, {op: 'match', input: 2, match: 3}, @@ -559,7 +559,7 @@ describe('computeAlignment', () => { assert.deepEqual(computedAlignment, { canAlign: true, editPath: [ - {op: 'insert', match: 0}, + {op: 'insert', match: 0}, {op: 'match', input: 0, match: 1}, {op: 'match', input: 1, match: 2}, {op: 'match', input: 2, match: 3}, @@ -608,7 +608,7 @@ describe('computeAlignment', () => { canAlign: false, editPath: [ {op: 'match', input: 0, match: 0}, - {op: 'insert', match: 1}, + {op: 'insert', match: 1}, {op: 'match', input: 1, match: 2}, {op: 'match', input: 2, match: 3}, {op: 'match', input: 3, match: 4} @@ -653,7 +653,7 @@ describe('computeAlignment', () => { {op: 'match', input: 1, match: 1}, {op: 'match', input: 2, match: 2}, {op: 'match', input: 3, match: 3}, - {op: 'insert', match: 4}, + {op: 'insert', match: 4}, {op: 'match', input: 4, match: 5} ] }); @@ -715,8 +715,8 @@ describe('computeAlignment', () => { {op: 'match', input: 8, match: 8}, {op: 'match', input: 9, match: 9}, {op: 'substitute', input: 10, match: 10}, - {op: 'insert', match: 11}, - {op: 'insert', match: 12} + {op: 'insert', match: 11}, + {op: 'insert', match: 12} ], leadTokenShift: 0, leadEditLength: 0, diff --git a/web/src/test/auto/headless/engine/predictive-text/worker-thread/context/context-tokenization.tests.ts b/web/src/test/auto/headless/engine/predictive-text/worker-thread/context/context-tokenization.tests.ts index 6a79cae8ce..e88ac2ab28 100644 --- a/web/src/test/auto/headless/engine/predictive-text/worker-thread/context/context-tokenization.tests.ts +++ b/web/src/test/auto/headless/engine/predictive-text/worker-thread/context/context-tokenization.tests.ts @@ -138,8 +138,8 @@ describe('ContextTokenization', function() { {op: 'match', input: 4, match: 4}, {op: 'match', input: 5, match: 5}, {op: 'match', input: 6, match: 6}, - {op: 'insert', match: 7}, - {op: 'insert', match: 8} + {op: 'insert', match: 7}, + {op: 'insert', match: 8} ], leadTokenShift: 0, leadEditLength: 0, @@ -436,8 +436,8 @@ describe('ContextTokenization', function() { targetTokens, { canAlign: true, editPath: [ - {op: 'insert', match: 0}, - {op: 'insert', match: 1}, + {op: 'insert', match: 0}, + {op: 'insert', match: 1}, {op: 'match', input: 0, match: 2}, {op: 'match', input: 1, match: 3}, {op: 'match', input: 2, match: 4}, @@ -614,8 +614,8 @@ describe('ContextTokenization', function() { {op: 'match', input: 10, match: 9}, {op: 'match', input: 11, match: 10}, {op: 'match', input: 12, match: 11}, - {op: 'insert', match: 12}, - {op: 'insert', match: 13} + {op: 'insert', match: 12}, + {op: 'insert', match: 13} ], leadTokenShift: 1, // "'", leadEditLength: 1, // "n't" / "t" diff --git a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/classical-calculation.tests.ts b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/classical-calculation.tests.ts index feb5ea53c5..698d95b130 100644 --- a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/classical-calculation.tests.ts +++ b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/classical-calculation.tests.ts @@ -601,7 +601,7 @@ describe('Classical Damerau-Levenshtein edit-distance calculation', function() { { op: 'match', input: 1 /* 'c' */, match: 1 /* 'c' */}, { op: 'match', input: 2 /* 'c' */, match: 2 /* 'c' */}, { op: 'match', input: 3 /* 'o' */, match: 3 /* 'o' */}, - { op: 'insert', match: 4 /* 'm' */}, // insert is earlier + { op: 'insert', match: 4 /* 'm' */}, // insert is earlier { op: 'match', input: 4 /* 'm' */, match: 5 /* 'm' */}, // longer contiguous match sequence { op: 'match', input: 5 /* 'o' */, match: 6 /* 'o' */}, { op: 'match', input: 6 /* 'd' */, match: 7 /* 'd' */}, @@ -614,7 +614,7 @@ describe('Classical Damerau-Levenshtein edit-distance calculation', function() { { op: 'match', input: 2 /* 'c' */, match: 2 /* 'c' */}, { op: 'match', input: 3 /* 'o' */, match: 3 /* 'o' */}, { op: 'match', input: 4 /* 'm' */, match: 4 /* 'm' */}, - { op: 'insert', match: 5 /* 'm' */}, // now inserting later. + { op: 'insert', match: 5 /* 'm' */}, // now inserting later. { op: 'match', input: 5 /* 'o' */, match: 6 /* 'o' */}, { op: 'match', input: 6 /* 'd' */, match: 7 /* 'd' */}, { op: 'match', input: 7 /* 'a' */, match: 8 /* 'a' */}, @@ -644,7 +644,7 @@ describe('Classical Damerau-Levenshtein edit-distance calculation', function() { { op: 'delete', input: 3}, { op: 'match', input: 4, match: 3}, { op: 'match', input: 5, match: 4}, - { op: 'insert', match: 5} + { op: 'insert', match: 5} ]; const viablePaths = buffer.editPath(); @@ -696,14 +696,14 @@ describe('Classical Damerau-Levenshtein edit-distance calculation', function() { { op: 'match', input: 1, match: 1}, // best sequence: continguous matches { op: 'match', input: 2, match: 2}, // + adjacent substitutes { op: 'substitute', input: 3, match: 3}, - { op: 'insert', match: 4}, + { op: 'insert', match: 4}, ]; let altSequence: EditTuple[] = [ { op: 'match', input: 0, match: 0}, { op: 'match', input: 1, match: 1}, { op: 'match', input: 2, match: 2}, - { op: 'insert', match: 3}, + { op: 'insert', match: 3}, { op: 'substitute', input: 3, match: 4}, ]; @@ -726,7 +726,7 @@ describe('Classical Damerau-Levenshtein edit-distance calculation', function() { { op: 'match', input: 5, match: 3}, { op: 'match', input: 6, match: 4}, { op: 'transpose-start', input: 7, match: 5}, - { op: 'transpose-insert', match: 6}, + { op: 'transpose-insert', match: 6}, { op: 'transpose-end', input: 8, match: 7} ]; From f59443d19cbd2cec78eba9d4f861ee381550f6dc Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Wed, 24 Sep 2025 10:23:58 -0500 Subject: [PATCH 52/52] change(web): enhance unit test clarity per review --- .../segmentable-calculation.tests.ts | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/segmentable-calculation.tests.ts b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/segmentable-calculation.tests.ts index 868d51a322..3a0229e82e 100644 --- a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/segmentable-calculation.tests.ts +++ b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/segmentable-calculation.tests.ts @@ -157,7 +157,6 @@ describe('Split/merge aware edit-distance calculation', () => { assert.equal(calc.getFinalCost(), 4); const editPaths = calc.editPath(); - assert.isAbove(editPaths.length, 3); assert.sameDeepOrderedMembers(editPaths[0], [ { op: 'split', input: 0, match: 0 }, { op: 'split', input: 0, match: 1 }, @@ -166,6 +165,26 @@ describe('Split/merge aware edit-distance calculation', () => { { op: 'merge', input: 3, match: 4 }, { op: 'merge', input: 4, match: 4 } ]); + + // ('insert', 'substitute') can be reordered + // ('substitute', 'delete') can be reordered + // There also exist cases where one pair or the other, but not both, + // are considered for the edit path. + assert.includeDeepMembers(editPaths, [[ + { op: 'insert', match: 0 }, // -> 'b' + { op: 'substitute', input: 0, match: 1 }, // 'abc' -> 'c' + { op: 'match', input: 1, match: 2 }, // 'd' == 'd' + { op: 'match', input: 2, match: 3 }, // 'e' == 'e' + { op: 'substitute', input: 3, match: 4 }, // 'f' -> 'fgh' + { op: 'delete', input: 4 } // 'g' -> + ]]); + + // both split and merge: 1 variation + // split, but not merge: 2 variations + // merge, but not split: 2 variations + // neither: 2x2 = 4 variations + // sum: 9 + assert.equal(editPaths.length, 9); }); it("abcd,e,f,g -> b,c,d,efgh = 4", () => { @@ -191,10 +210,10 @@ describe('Split/merge aware edit-distance calculation', () => { ]); assert.sameDeepOrderedMembers(editPaths[1], [ - { op: 'substitute', input: 0, match: 0 }, - { op: 'substitute', input: 1, match: 1 }, - { op: 'substitute', input: 2, match: 2 }, - { op: 'substitute', input: 3, match: 3 } + { op: 'substitute', input: 0, match: 0 }, // 'abcd' -> 'b' + { op: 'substitute', input: 1, match: 1 }, // 'e' -> 'c' + { op: 'substitute', input: 2, match: 2 }, // 'f' -> 'd' + { op: 'substitute', input: 3, match: 3 } // 'g' -> 'efgh' ]); }); }); \ No newline at end of file