From 5afaf7cbafb381a7044306cde13ab441ea2a6294 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 27 May 2025 20:54:58 +1000 Subject: [PATCH 001/262] feat(windows): human text message for 413 error --- windows/src/engine/tsysinfo/UfrmEmail.pas | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/windows/src/engine/tsysinfo/UfrmEmail.pas b/windows/src/engine/tsysinfo/UfrmEmail.pas index 0999e7f2c7..6be1ba5ccc 100644 --- a/windows/src/engine/tsysinfo/UfrmEmail.pas +++ b/windows/src/engine/tsysinfo/UfrmEmail.pas @@ -7,12 +7,12 @@ Modified Date: 8 Jun 2012 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 13 May 2005 - mcdurdin - Integrated into kmshell from tsysinfo 16 Aug 2005 - mcdurdin - Add privacy statement 20 Jul 2008 - mcdurdin - I1554 - Don't require password for tsysinfo @@ -141,6 +141,11 @@ begin ShowMessage('The report was not successfully sent. An error was returned: '+IntToStr(Response.StatusCode)); Exit; end; + if Response.StatusCode <> 413 then + begin + ShowMessage('The diagnostic report file was to large: '+IntToStr(Response.StatusCode)+ 'Contact us via the Keyman Community Forum for assistance.'); + Exit; + end; s := Trim(string(Response.MessageBodyAsString)); if Copy(s,1,7) = '' then begin From b1b065b9126aa93f7aace95303f0d5c1187ce7ae Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 2 Jun 2025 20:39:46 +1000 Subject: [PATCH 002/262] fix(windows): fix download failing when cache.json missing or stale. This commit adds a precondition check when entering the Download state. It checks to see if the cache.json file still exists then it checks if there are packages or installer files to download. This will also check the version with ucrsUpdateReady, so if the installed version is already newer it will not attempt the download. If the check fails it will removed any cached files and return to the idle state and check for updates. Fixes: #13767 --- .../main/Keyman.System.DownloadUpdate.pas | 2 +- .../main/Keyman.System.UpdateStateMachine.pas | 39 +++++++++++++++---- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas index 2e6ddb4ffe..6ee1d454f3 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.DownloadUpdate.pas @@ -159,7 +159,7 @@ begin else begin ErrorLogMessage('DoDownloadUpdates Failed to download' + Params.InstallURL); - TKeymanSentryClient.Breadcrumb('error', 'Download failded for keyboard: '+ Params.InstallURL, 'cli.download'); + TKeymanSentryClient.Breadcrumb('error', 'Download failded for Keyman Update: '+ Params.InstallURL, 'cli.download'); end; end else diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index b34fbf4fd4..6afdf18155 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -759,11 +759,32 @@ end; { DownloadingState } +// Check that cache.json file exists and packages or keyman versions available to download +function IsUpdateMetaDataValid(ucr :TUpdateCheckResponse) : Boolean; +var + hasPackages, hasKeymanInstallReady: Boolean; +begin + + hasPackages := False; + hasKeymanInstallReady := False; + if TUpdateCheckStorage.LoadUpdateCacheData(ucr) then + begin + // check again has packages or keyman update + hasPackages := TUpdateCheckStorage.HasKeyboardPackages(ucr); + hasKeymanInstallReady := (ucr.Status = ucrsUpdateReady) and TUpdateCheckStorage.HasKeymanInstallFile(ucr); + end; + + Result := (hasPackages Or hasKeymanInstallReady); + +end; + procedure DownloadingState.EnterState; var DownloadResult: Boolean; RetryCount: Integer; FMutex: TKeymanMutex; + ucr: TUpdateCheckResponse; + hasPackages, hasKeymanInstallReady: Boolean; begin // Enter DownloadingState bucStateContext.SetRegistryState(usDownloading); @@ -779,11 +800,15 @@ begin Exit; end; - RetryCount := 0; - repeat - DownloadResult := DownloadUpdatesBackground; - Inc(RetryCount); - until DownloadResult or (RetryCount >= 3); + if not IsUpdateMetaDataValid(ucr) then + begin + // Return to Idle state and check for Updates state + bucStateContext.RemoveCachedFiles; + ChangeState(IdleState); + bucStateContext.CurrentState.HandleCheck; + end; + + DownloadResult := DownloadUpdatesBackground; FMutex.ReleaseOwnership; finally FreeAndNil(FMutex); @@ -791,10 +816,10 @@ begin if (not DownloadResult) then begin - // Failed three times in this process; return to the + // Failed download; return to the // IdleState to wait 'CheckPeriod' before trying again TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR, - 'Error Updates not downloaded after 3 attempts'); + 'Error Updates not downloaded'); bucStateContext.RemoveCachedFiles; ChangeState(IdleState); end From 7a741dc9d0b1e56ccc0a234dedbd4d2d48551209 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Mon, 2 Jun 2025 16:30:29 -0500 Subject: [PATCH 003/262] chore(developer): ldml: additional line numbers - ajv validation - more fixes from out of box testing Fixes: #14099 --- common/web/types/src/kmx/kmx-plus/kmx-plus.ts | 2 +- common/web/types/src/util/util.ts | 2 +- .../web/utils/src/developer-utils-messages.ts | 9 +++-- developer/src/common/web/utils/src/index.ts | 2 +- .../ldml-keyboard/ldml-keyboard-xml-reader.ts | 5 +-- .../src/common/web/utils/src/xml-utils.ts | 36 +++++++++++++++++++ .../ldml/ldml-keyboard-xml-reader.tests.ts | 6 ++-- .../common/web/utils/test/xml-utils.tests.ts | 33 ++++++++++++++++- developer/src/kmc-ldml/src/compiler/layr.ts | 2 +- developer/src/kmc-ldml/src/compiler/vars.ts | 10 +++--- developer/src/kmc-ldml/test/disp.tests.ts | 3 +- developer/src/kmc-ldml/test/helpers/index.ts | 16 +-------- developer/src/kmc-ldml/test/keys.tests.ts | 4 +-- developer/src/kmc-ldml/test/layr.tests.ts | 3 +- developer/src/kmc-ldml/test/loca.tests.ts | 3 +- developer/src/kmc-ldml/test/messages.tests.ts | 3 +- developer/src/kmc-ldml/test/meta.tests.ts | 4 +-- 17 files changed, 101 insertions(+), 42 deletions(-) diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts index 373cc57b27..4ccf80ed17 100644 --- a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts +++ b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts @@ -393,7 +393,7 @@ export class UnicodeSetItem extends VarsItem { super(id, value, sections, x); const needRanges = sections.usetparser.sizeUnicodeSet(value); if (needRanges >= 0) { - this.unicodeSet = sections.usetparser.parseUnicodeSet(value, needRanges); + this.unicodeSet = sections.usetparser.parseUnicodeSet(value, needRanges, x); } // otherwise: error (was recorded via callback) } unicodeSet?: UnicodeSet; diff --git a/common/web/types/src/util/util.ts b/common/web/types/src/util/util.ts index 70e1077cc5..be1a715b17 100644 --- a/common/web/types/src/util/util.ts +++ b/common/web/types/src/util/util.ts @@ -392,4 +392,4 @@ export class NFDAnalyzer extends StringAnalyzer { return null; } } -} +} \ No newline at end of file diff --git a/developer/src/common/web/utils/src/developer-utils-messages.ts b/developer/src/common/web/utils/src/developer-utils-messages.ts index 6fd328802b..cd1eb8d2fe 100644 --- a/developer/src/common/web/utils/src/developer-utils-messages.ts +++ b/developer/src/common/web/utils/src/developer-utils-messages.ts @@ -1,8 +1,9 @@ /* * Keyman is copyright (C) SIL Global. MIT License. */ -import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageDef as def, CompilerMessageSpec as m } from './compiler-interfaces.js'; +import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageDef as def, CompilerMessageSpec as m, CompilerMessageObjectSpec as mx } from './compiler-interfaces.js'; import { constants } from '@keymanapp/ldml-keyboard-constants'; +import { ObjectWithMetadata } from './symbol-utils.js'; const DeveloperUtilsErrMask = CompilerErrorNamespace.DeveloperUtils; // const SevInfo = CompilerErrorSeverity.Info | DeveloperUtilsErrMask; @@ -14,8 +15,10 @@ const SevError = CompilerErrorSeverity.Error | DeveloperUtilsErrMask; export class DeveloperUtilsMessages { // structured Ajv validation error static ERROR_SchemaValidationError = SevError | 0x0001; - static Error_SchemaValidationError = (o:{instancePath:string, keyword:string, message: string, params: string}) => m(this.ERROR_SchemaValidationError, - `Error validating LDML XML file: ${def(o.instancePath)}: ${def(o.keyword)}: ${def(o.message)} ${def(o.params)}`); + static Error_SchemaValidationError = (o:{instancePath:string, keyword:string, message: string, params: string}, x?: ObjectWithMetadata) => mx( + this.ERROR_SchemaValidationError, x, + `Error validating LDML XML file: ${def(o.instancePath)}: ${def(o.keyword)}: ${def(o.message)} ${def(o.params)}`, + ); static ERROR_ImportInvalidBase = SevError | 0x0002; static Error_ImportInvalidBase = (o: { base: string, path: string, subtag: string }) => diff --git a/developer/src/common/web/utils/src/index.ts b/developer/src/common/web/utils/src/index.ts index c39afa8bff..1174dd4684 100644 --- a/developer/src/common/web/utils/src/index.ts +++ b/developer/src/common/web/utils/src/index.ts @@ -67,7 +67,7 @@ export { UrlSubpathCompilerCallback } from './utils/UrlSubpathCompilerCallback.j export { DeveloperUtilsMessages } from './developer-utils-messages.js'; export * as SourceFilenamePatterns from './source-filename-patterns.js'; -export { KeymanXMLType, KeymanXMLWriter, KeymanXMLReader, KeymanXMLMetadata, XML_FILENAME_SYMBOL } from './xml-utils.js'; +export { KeymanXMLType, KeymanXMLWriter, KeymanXMLReader, KeymanXMLMetadata, XML_FILENAME_SYMBOL, withOffset } from './xml-utils.js'; export { SymbolUtils, ObjectWithMetadata } from './symbol-utils.js'; export * as LineUtils from './line-utils.js'; export * as GitHubUrls from './github-urls.js'; diff --git a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts index 0a31bc051b..f25b03582d 100644 --- a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -12,7 +12,7 @@ import { constants } from '@keymanapp/ldml-keyboard-constants'; import { LDMLKeyboardTestDataXMLSourceFile, LKTTest, LKTTests } from './ldml-keyboard-testdata-xml.js'; import boxXmlArray = util.boxXmlArray; import { LineFinderEventResolver } from '../../line-utils.js'; -import { XML_FILENAME_SYMBOL, KeymanXMLReader } from '../../xml-utils.js'; +import { XML_FILENAME_SYMBOL, KeymanXMLReader, findInstanceObject } from '../../xml-utils.js'; interface NameAndProps { '$'?: any; // content @@ -310,12 +310,13 @@ export class LDMLKeyboardXMLSourceFileReader implements EventResolver { public validate(source: LDMLKeyboardXMLSourceFile | LDMLKeyboardTestDataXMLSourceFile): boolean { if(!SchemaValidators.default.ldmlKeyboard3(source)) { for (const err of (SchemaValidators.default.ldmlKeyboard3).errors) { + const context = findInstanceObject(source, err?.instancePath?.split('/')); this.callbacks.reportMessage(DeveloperUtilsMessages.Error_SchemaValidationError({ instancePath: err.instancePath, keyword: err.keyword, message: err.message || 'Unknown AJV Error', // docs say 'message' is optional if 'messages:false' in options params: Object.entries(err.params || {}).sort().map(([k,v])=>`${k}="${v}"`).join(' '), - })); + }, context)); } return false; } diff --git a/developer/src/common/web/utils/src/xml-utils.ts b/developer/src/common/web/utils/src/xml-utils.ts index f83ca75dc7..1d2b01c71e 100644 --- a/developer/src/common/web/utils/src/xml-utils.ts +++ b/developer/src/common/web/utils/src/xml-utils.ts @@ -360,3 +360,39 @@ export class KeymanXMLWriter { } } +/** + * traverse an AJV instancePath and map to an object if possible + * @param source object tree root (contains the root object) + * @param path ajv split instancePath, such as '/keyboard3/layers/0'.split('/') + */ +export function findInstanceObject(source: any, path: string[]) : any { + if(!path || !source || path.length == 0) { + return source; + } else if(path[0] == '') { + return findInstanceObject(source, path.slice(1)); + } else if(Array.isArray(source) || typeof source == 'object') { + const child = source[path[0]]; + if (child == undefined) return child; // nothing here + if (!child || typeof child == 'string') { + return source; // return the *parent* object if the child is empty (could be a property) + } + return findInstanceObject(child, path.slice(1)); + } else { + return null; + } +} + +/** + * Return an object simulating an XML object with an offset number + * For use in calling message functions + * @param c number for the offset setting + * @param x if set, this object will be used as the base object instead of {} + */ +export function withOffset(c: number, x?: any) : KeymanXMLMetadata { + // set metadata on an empty object + const o = Object.assign({}, x); + KeymanXMLReader.setMetaData(o, { + startIndex: c + }); + return o; +} diff --git a/developer/src/common/web/utils/test/ldml/ldml-keyboard-xml-reader.tests.ts b/developer/src/common/web/utils/test/ldml/ldml-keyboard-xml-reader.tests.ts index d09c1dfc41..286edd7958 100644 --- a/developer/src/common/web/utils/test/ldml/ldml-keyboard-xml-reader.tests.ts +++ b/developer/src/common/web/utils/test/ldml/ldml-keyboard-xml-reader.tests.ts @@ -8,7 +8,7 @@ import { testReaderCases } from '../helpers/reader-callback-test.js'; import CLDRScanToVkey = Constants.CLDRScanToVkey; import CLDRScanToKeyMap = Constants.CLDRScanToKeyMap; import USVirtualKeyCodes = Constants.USVirtualKeyCodes; -import { KeymanXMLReader, XML_FILENAME_SYMBOL } from '../../src/xml-utils.js'; +import { KeymanXMLReader, withOffset, XML_FILENAME_SYMBOL } from '../../src/xml-utils.js'; function pluckKeysFromKeybag(keys: LKKey[], ids: string[]) { return keys.filter(({id}) => ids.indexOf(id) !== -1); @@ -25,7 +25,7 @@ describe('ldml keyboard xml reader tests', function () { keyword: 'required', message: `must have required property 'info'`, params: 'missingProperty="info"', - })], + }, withOffset(39))], }, { subpath: 'invalid-conforms-to.xml', @@ -34,7 +34,7 @@ describe('ldml keyboard xml reader tests', function () { keyword: 'enum', message: `must be equal to one of the allowed values`, params: 'allowedValues="45,46"', // this has to be kept in sync with the DTD - })], + }, withOffset(39))], }, { subpath: 'import-minimal.xml', diff --git a/developer/src/common/web/utils/test/xml-utils.tests.ts b/developer/src/common/web/utils/test/xml-utils.tests.ts index 4b3df736b6..ec4f1ae80c 100644 --- a/developer/src/common/web/utils/test/xml-utils.tests.ts +++ b/developer/src/common/web/utils/test/xml-utils.tests.ts @@ -12,7 +12,7 @@ import { env } from 'node:process'; import { readFileSync, writeFileSync } from 'node:fs'; -import { KeymanXMLType, KeymanXMLReader, KeymanXMLWriter } from '../src/xml-utils.js'; +import { KeymanXMLType, KeymanXMLReader, KeymanXMLWriter, findInstanceObject } from '../src/xml-utils.js'; import { LineFinder } from '../src/line-utils.js'; import { makePathToFixture } from './helpers/index.js'; import { SymbolUtils } from '../src/symbol-utils.js'; @@ -198,5 +198,36 @@ describe(`XML Reader line number test`, () => { LineFinder.offsetToLineColumn( getMetaData(actual.keyboard3.transforms).startIndex, lines), { line: 8, column: 2 }); }); + describe('findInstanceObject test', () => { + const path0 = '/keyboard3/layers/0'; + const TARGET = Symbol("Looking for this!"); + it(`Should be able to parse ${path0}`, () => { + const o = { + keyboard3: { + layers: [ + TARGET, + ] + } + }; + assert.equal(findInstanceObject(o, path0.split('/')), TARGET); + }); + // path to property + const path1 = '/keyboard3/conformsTo'; + it(`Should be able to parse ${path1}`, () => { + const keyboard3 = { conformsTo: "1234"}; + const o = { + keyboard3, + }; + assert.equal(findInstanceObject(o, path1.split('/')), keyboard3); + }); + const path2 = '/keyboard3/bad/path'; + it(`Should be able to handle ${path2}`, () => { + const keyboard3 = { conformsTo: "1234"}; + const o = { + keyboard3, + }; + assert.equal(findInstanceObject(o, path2.split('/')), undefined); + }); + }); }); diff --git a/developer/src/kmc-ldml/src/compiler/layr.ts b/developer/src/kmc-ldml/src/compiler/layr.ts index 9dff22a5bf..d450730bf9 100644 --- a/developer/src/kmc-ldml/src/compiler/layr.ts +++ b/developer/src/kmc-ldml/src/compiler/layr.ts @@ -60,7 +60,7 @@ export class LayrCompiler extends SectionCompiler { if (totalLayerCount === 0) { // TODO-LDML: does not validate touch layers yet // no layers seen anywhere valid = false; - this.callbacks.reportMessage(LdmlCompilerMessages.Error_MustBeAtLeastOneLayerElement(this.keyboard3?.layers[0])); + this.callbacks.reportMessage(LdmlCompilerMessages.Error_MustBeAtLeastOneLayerElement(this.keyboard3)); } return valid; } diff --git a/developer/src/kmc-ldml/src/compiler/vars.ts b/developer/src/kmc-ldml/src/compiler/vars.ts index 2a0538732a..f08c5a44e9 100644 --- a/developer/src/kmc-ldml/src/compiler/vars.ts +++ b/developer/src/kmc-ldml/src/compiler/vars.ts @@ -97,7 +97,7 @@ export class VarsCompiler extends SectionCompiler { // Sets for (const e of variables.set) { const { id, value } = e; - if(!this.validateIdentifier(id)) { + if(!this.validateIdentifier(id, e)) { valid = false; continue; } @@ -124,7 +124,7 @@ export class VarsCompiler extends SectionCompiler { // UnicodeSets for (const e of variables.uset) { const { id, value } = e; - if(!this.validateIdentifier(id)) { + if(!this.validateIdentifier(id, e)) { valid = false; continue; } @@ -291,7 +291,7 @@ export class VarsCompiler extends SectionCompiler { let { value } = e; // fix any variables value = result.substituteStrings(value, sections); - result.strings.push(new StringVarItem(id, value, sections)); + result.strings.push(new StringVarItem(id, value, sections, e)); } addSet(result: Vars, e: LDMLKeyboard.LKSet, sections: DependencySections): void { const { id } = e; @@ -306,14 +306,14 @@ export class VarsCompiler extends SectionCompiler { // this is not 'forMatch', all variables are to be assumed as string literals, not regex // content. const cookedItems: string[] = rawItems.map(v => result.substituteMarkerString(v, false)); - result.sets.push(new SetVarItem(id, cookedItems, sections)); + result.sets.push(new SetVarItem(id, cookedItems, sections, e)); } addUnicodeSet(result: Vars, e: LDMLKeyboard.LKUSet, sections: DependencySections): void { const { id } = e; let { value } = e; value = result.substituteStrings(value, sections); value = result.substituteUnicodeSets(value, sections); - result.usets.push(new UnicodeSetItem(id, value, sections, sections.usetparser)); + result.usets.push(new UnicodeSetItem(id, value, sections, sections.usetparser, e)); } // routines for using/substituting variables have been moved to the Vars class and its // properties diff --git a/developer/src/kmc-ldml/test/disp.tests.ts b/developer/src/kmc-ldml/test/disp.tests.ts index 3bad95e339..8b17b35c55 100644 --- a/developer/src/kmc-ldml/test/disp.tests.ts +++ b/developer/src/kmc-ldml/test/disp.tests.ts @@ -1,11 +1,12 @@ import 'mocha'; import {assert} from 'chai'; import { DispCompiler } from '../src/compiler/disp.js'; -import { compilerTestCallbacks, loadSectionFixture, testCompilationCases, withOffset } from './helpers/index.js'; +import { compilerTestCallbacks, loadSectionFixture, testCompilationCases } from './helpers/index.js'; import { KMXPlus } from '@keymanapp/common-types'; import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js'; import Disp = KMXPlus.Disp; +import { withOffset } from '@keymanapp/developer-utils'; describe('disp', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while diff --git a/developer/src/kmc-ldml/test/helpers/index.ts b/developer/src/kmc-ldml/test/helpers/index.ts index 967287cf87..be6216f9c1 100644 --- a/developer/src/kmc-ldml/test/helpers/index.ts +++ b/developer/src/kmc-ldml/test/helpers/index.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import { fileURLToPath } from 'url'; import { SectionCompiler, SectionCompilerNew } from '../../src/compiler/section-compiler.js'; import { util, KMXPlus, LdmlKeyboardTypes } from '@keymanapp/common-types'; -import { CompilerEvent, compilerEventFormat, CompilerCallbacks, LDMLKeyboardXMLSourceFileReader, LDMLKeyboardTestDataXMLSourceFile, LDMLKeyboard, KeymanXMLMetadata, KeymanXMLReader, CompilerError } from "@keymanapp/developer-utils"; +import { CompilerEvent, compilerEventFormat, CompilerCallbacks, LDMLKeyboardXMLSourceFileReader, LDMLKeyboardTestDataXMLSourceFile, LDMLKeyboard, CompilerError } from "@keymanapp/developer-utils"; import { LdmlKeyboardCompiler } from '../../src/main.js'; // make sure main.js compiles import { assert } from 'chai'; import { KMXPlusMetadataCompiler } from '../../src/compiler/metadata-compiler.js'; @@ -358,17 +358,3 @@ export function hex_str(s?: string) : string { return [...s].map(ch => dontEscape.test(ch) ? ch : util.escapeRegexChar(ch)).join(''); } -/** - * Return an object simulating an XML object with an offset number - * For use in calling message functions - * @param c number for the offset setting - * @param x if set, this object will be used as the base object instead of {} - */ -export function withOffset(c: number, x?: any) : KeymanXMLMetadata { - // set metadata on an empty object - const o = Object.assign({}, x); - KeymanXMLReader.setMetaData(o, { - startIndex: c - }); - return o; -} diff --git a/developer/src/kmc-ldml/test/keys.tests.ts b/developer/src/kmc-ldml/test/keys.tests.ts index 3c3c842810..97171cf6fc 100644 --- a/developer/src/kmc-ldml/test/keys.tests.ts +++ b/developer/src/kmc-ldml/test/keys.tests.ts @@ -1,7 +1,7 @@ import 'mocha'; import { assert } from 'chai'; import { KeysCompiler } from '../src/compiler/keys.js'; -import { assertCodePoints, compilerTestCallbacks, loadSectionFixture, testCompilationCases, withOffset } from './helpers/index.js'; +import { assertCodePoints, compilerTestCallbacks, loadSectionFixture, testCompilationCases } from './helpers/index.js'; import { KMXPlus, Constants, LdmlKeyboardTypes } from '@keymanapp/common-types'; import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js'; import { constants } from '@keymanapp/ldml-keyboard-constants'; @@ -9,7 +9,7 @@ import { MetaCompiler } from '../src/compiler/meta.js'; const keysDependencies = [ ...BASIC_DEPENDENCIES, MetaCompiler ]; import Keys = KMXPlus.Keys; import { BASIC_DEPENDENCIES } from '../src/compiler/empty-compiler.js'; -import { LDMLKeyboard } from '@keymanapp/developer-utils'; +import { LDMLKeyboard, withOffset } from '@keymanapp/developer-utils'; const K = Constants.USVirtualKeyCodes; describe('keys', function () { diff --git a/developer/src/kmc-ldml/test/layr.tests.ts b/developer/src/kmc-ldml/test/layr.tests.ts index dc0334c0b1..a1f3c5e184 100644 --- a/developer/src/kmc-ldml/test/layr.tests.ts +++ b/developer/src/kmc-ldml/test/layr.tests.ts @@ -2,12 +2,13 @@ import 'mocha'; import { assert } from 'chai'; import { LayrCompiler } from '../src/compiler/layr.js'; import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js'; -import { compilerTestCallbacks, testCompilationCases, withOffset } from './helpers/index.js'; +import { compilerTestCallbacks, testCompilationCases } from './helpers/index.js'; import { KMXPlus } from '@keymanapp/common-types'; import { constants } from '@keymanapp/ldml-keyboard-constants'; import Layr = KMXPlus.Layr; import LayrRow = KMXPlus.LayrRow; +import { withOffset } from '@keymanapp/developer-utils'; function allKeysOk(row : LayrRow, str : string, msg? : string) { const split = str.split(' '); diff --git a/developer/src/kmc-ldml/test/loca.tests.ts b/developer/src/kmc-ldml/test/loca.tests.ts index 287ee62b00..ffe4f656c7 100644 --- a/developer/src/kmc-ldml/test/loca.tests.ts +++ b/developer/src/kmc-ldml/test/loca.tests.ts @@ -1,11 +1,12 @@ import 'mocha'; import { assert } from 'chai'; import { LocaCompiler } from '../src/compiler/loca.js'; -import { compilerTestCallbacks, loadSectionFixture, withOffset } from './helpers/index.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; import { KMXPlus } from '@keymanapp/common-types'; import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js'; import Loca = KMXPlus.Loca; +import { withOffset } from '@keymanapp/developer-utils'; describe('loca', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while diff --git a/developer/src/kmc-ldml/test/messages.tests.ts b/developer/src/kmc-ldml/test/messages.tests.ts index a7d06006cb..60df66fb09 100644 --- a/developer/src/kmc-ldml/test/messages.tests.ts +++ b/developer/src/kmc-ldml/test/messages.tests.ts @@ -2,8 +2,7 @@ import 'mocha'; import {expect} from 'chai'; import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js'; import { verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers'; -import { CompilerErrorNamespace, CompilerEvent } from '@keymanapp/developer-utils'; -import { withOffset } from './helpers/index.js'; +import { CompilerErrorNamespace, CompilerEvent, withOffset } from '@keymanapp/developer-utils'; describe('LdmlCompilerMessages', function () { it('should have a valid LdmlCompilerMessages object', function() { diff --git a/developer/src/kmc-ldml/test/meta.tests.ts b/developer/src/kmc-ldml/test/meta.tests.ts index c744b3385e..beeed50915 100644 --- a/developer/src/kmc-ldml/test/meta.tests.ts +++ b/developer/src/kmc-ldml/test/meta.tests.ts @@ -1,13 +1,13 @@ import 'mocha'; import {assert} from 'chai'; import { MetaCompiler } from '../src/compiler/meta.js'; -import { compilerTestCallbacks, loadSectionFixture, withOffset } from './helpers/index.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; import { KMXPlus } from '@keymanapp/common-types'; import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js'; import KeyboardSettings = KMXPlus.KeyboardSettings; import Meta = KMXPlus.Meta; -import { LDMLKeyboard } from '@keymanapp/developer-utils'; +import { LDMLKeyboard, withOffset } from '@keymanapp/developer-utils'; describe('meta', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while From ba3bcd95ac7d29fce79bf5832c452a3e6f8c7a07 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 5 Jun 2025 09:08:58 +1000 Subject: [PATCH 004/262] fix(windows): use ucrsUpdateReady in check for updates When determining whether to move to the update available the ucrsUpdateReady is now taken into account, not just the wucSuccess flag. All the cases that check if there is Keyman installer file in the metadata now check for this, and this was the original intent. A two new function HasKeymanInstallFileUpdate and CheckMetaDataForUpdate have been added to aid this check. --- .../main/Keyman.System.UpdateCheckStorage.pas | 31 ++++++++++-- .../main/Keyman.System.UpdateStateMachine.pas | 49 +++---------------- 2 files changed, 35 insertions(+), 45 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas index a7a822df7c..5a09d35068 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateCheckStorage.pas @@ -10,11 +10,25 @@ type private class function MetadataFilename: string; static; public - class function HasUpdates: Boolean; static; class function LoadUpdateCacheData(var data: TUpdateCheckResponse): Boolean; static; class procedure SaveUpdateCacheData(const data: TUpdateCheckResponse); static; class function HasKeyboardPackages(const data: TUpdateCheckResponse): Boolean; static; + + (** In most cases the function `HasKeymanInstallFileUpdate` will be required. + @return True if there is keyman installer file in the metadata cache file *) class function HasKeymanInstallFile(const data: TUpdateCheckResponse): Boolean; static; + + (** @return True if there is keyman installer file in the metadata cache file and it is newer + then the current installed version. `TUpdateCheckResponse.DoParse` + checks this and sets `ucrsUpdateReady` flag. + *) + class function HasKeymanInstallFileUpdate(const data: TUpdateCheckResponse): Boolean; static; + + (** @return True if there is at least one keyboard package or a newer Keyman installer file in the Metadata + cache file. + *) + class function CheckMetaDataForUpdate: Boolean; static; + end; implementation @@ -40,15 +54,19 @@ begin data.SaveToFile(MetadataFilename); end; -class function TUpdateCheckStorage.HasUpdates: Boolean; +class function TUpdateCheckStorage.CheckMetaDataForUpdate: Boolean; +var + ucr: TUpdateCheckResponse; begin - Result := FileExists(MetadataFilename); + Result := TUpdateCheckStorage.LoadUpdateCacheData(ucr) and + (TUpdateCheckStorage.HasKeyboardPackages(ucr) or + TUpdateCheckStorage.HasKeymanInstallFileUpdate(ucr)); end; class function TUpdateCheckStorage.LoadUpdateCacheData(var data: TUpdateCheckResponse): Boolean; begin Result := - HasUpdates and + FileExists(MetadataFilename) and data.LoadFromFile(MetadataFilename, 'bundle', CKeymanVersionInfo.Version); end; @@ -79,4 +97,9 @@ begin Result := False end; +class function TUpdateCheckStorage.HasKeymanInstallFileUpdate(const data: TUpdateCheckResponse): Boolean; +begin + Result := HasKeymanInstallFile(data) and (data.Status = ucrsUpdateReady); +end; + end. diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 6afdf18155..ed662c5e52 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -618,8 +618,8 @@ begin CheckForUpdates.Free; end; - { Response OK and Update is available } - if Result = wucSuccess then + { Response wucSuccess and Update is available } + if (Result = wucSuccess) and (TUpdateCheckStorage.CheckMetaDataForUpdate) then begin ChangeState(UpdateAvailableState); end; @@ -759,32 +759,10 @@ end; { DownloadingState } -// Check that cache.json file exists and packages or keyman versions available to download -function IsUpdateMetaDataValid(ucr :TUpdateCheckResponse) : Boolean; -var - hasPackages, hasKeymanInstallReady: Boolean; -begin - - hasPackages := False; - hasKeymanInstallReady := False; - if TUpdateCheckStorage.LoadUpdateCacheData(ucr) then - begin - // check again has packages or keyman update - hasPackages := TUpdateCheckStorage.HasKeyboardPackages(ucr); - hasKeymanInstallReady := (ucr.Status = ucrsUpdateReady) and TUpdateCheckStorage.HasKeymanInstallFile(ucr); - end; - - Result := (hasPackages Or hasKeymanInstallReady); - -end; - procedure DownloadingState.EnterState; var DownloadResult: Boolean; - RetryCount: Integer; FMutex: TKeymanMutex; - ucr: TUpdateCheckResponse; - hasPackages, hasKeymanInstallReady: Boolean; begin // Enter DownloadingState bucStateContext.SetRegistryState(usDownloading); @@ -799,13 +777,12 @@ begin TKeymanSentryClient.Breadcrumb('default', 'DownloadingState.EnterState: Unable to get Mutex download process exists', 'update'); Exit; end; - - if not IsUpdateMetaDataValid(ucr) then + // verify there is an update available + if not TUpdateCheckStorage.CheckMetaDataForUpdate then begin - // Return to Idle state and check for Updates state + // Return to Idle state bucStateContext.RemoveCachedFiles; ChangeState(IdleState); - bucStateContext.CurrentState.HandleCheck; end; DownloadResult := DownloadUpdatesBackground; @@ -951,9 +928,6 @@ begin end; function WaitingRestartState.HandleKmShell; -var - ucr: TUpdateCheckResponse; - hasPackages, hasKeymanInstall: Boolean; begin // Still can't go if keyman has run if HasKeymanRun then @@ -962,14 +936,7 @@ begin end else begin - hasPackages := False; - hasKeymanInstall := False; - if (TUpdateCheckStorage.LoadUpdateCacheData(ucr)) then - begin - hasPackages := TUpdateCheckStorage.HasKeyboardPackages(ucr); - hasKeymanInstall := TUpdateCheckStorage.HasKeymanInstallFile(ucr); - end; - if not (hasPackages Or hasKeymanInstall) then + if not (TUpdateCheckStorage.CheckMetaDataForUpdate) then begin // Return to Idle state and check for Updates state ChangeState(IdleState); @@ -1143,7 +1110,7 @@ begin if (TUpdateCheckStorage.LoadUpdateCacheData(ucr)) then begin hasPackages := TUpdateCheckStorage.HasKeyboardPackages(ucr); - hasKeymanInstall := TUpdateCheckStorage.HasKeymanInstallFile(ucr); + hasKeymanInstall := TUpdateCheckStorage.HasKeymanInstallFileUpdate(ucr); end; { Notes: The reason packages (keyboards) is installed first is because we are trying to reduce the number of times the user has @@ -1207,7 +1174,7 @@ var hasKeymanInstall : Boolean; begin TUpdateCheckStorage.LoadUpdateCacheData(ucr); - hasKeymanInstall := TUpdateCheckStorage.HasKeymanInstallFile(ucr); + hasKeymanInstall := TUpdateCheckStorage.HasKeymanInstallFileUpdate(ucr); // This event should only be reached in elevated process if not then // move on to just installing Keyman if not kmcom.SystemInfo.IsAdministrator then From b33fec44a54a3c883f05df1150a884029de636b5 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 5 Jun 2025 10:58:15 +1000 Subject: [PATCH 005/262] fix(windows): TRemoteUpdateCheckResult two new values TRemoteUpdateCheckResult now has wucUpdateAvailable and wucNotChecked to avoid confusion --- .../main/Keyman.System.RemoteUpdateCheck.pas | 17 ++++++++++++++--- .../main/Keyman.System.UpdateStateMachine.pas | 13 +++++++------ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas index 0e4cf2cf51..dc41ae59bc 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.RemoteUpdateCheck.pas @@ -21,7 +21,15 @@ const type ERemoteUpdateCheck = class(Exception); - TRemoteUpdateCheckResult = (wucUnknown, wucSuccess, wucNoUpdates, wucFailure, wucOffline); + (** + * wucUnknown: The result of the check is unknown. + * wucUpdateAvailable: The check was successful and a keyboard package or Keyman Installer file is available. + * wucNotChecked: The check was not performed, due to time between check value + * wucNoUpdates: The check was successful but no updates are available. + * wucFailure: The check failed due to an error. + * wucOffline: The user is offline, so the check could not be performed. + *) + TRemoteUpdateCheckResult = (wucUnknown, wucUpdateAvailable, wucNotChecked, wucNoUpdates, wucFailure, wucOffline); TRemoteUpdateCheckDownloadParams = record TotalSize: Integer; @@ -141,7 +149,7 @@ begin proceed := ConfigCheckContinue; if not proceed and not FForce then begin - Result := wucNoUpdates; + Result := wucNotChecked; Exit; end; @@ -184,7 +192,10 @@ begin if ucr.Parse(http.Response.MessageBodyAsString, 'bundle', CKeymanVersionInfo.Version) then begin TUpdateCheckStorage.SaveUpdateCacheData(ucr); - Result := wucSuccess; + if TUpdateCheckStorage.HasKeyboardPackages(ucr) or TUpdateCheckStorage.HasKeymanInstallFileUpdate(ucr) then + Result := wucUpdateAvailable + else + Result := wucNoUpdates; end else begin diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index ed662c5e52..b883a9ba30 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -618,12 +618,11 @@ begin CheckForUpdates.Free; end; - { Response wucSuccess and Update is available } - if (Result = wucSuccess) and (TUpdateCheckStorage.CheckMetaDataForUpdate) then + if (Result = wucUpdateAvailable) then begin ChangeState(UpdateAvailableState); end; - // else staty in idle state + // else stay in idle state end; function IdleState.HandleKmShell; @@ -641,7 +640,7 @@ begin CheckForUpdates.Free; end; { Response OK and Update is available } - if UpdateCheckResult = wucSuccess then + if UpdateCheckResult = wucUpdateAvailable then begin ChangeState(UpdateAvailableState); end; @@ -723,7 +722,7 @@ begin finally CheckForUpdates.Free; end; - if Result <> wucSuccess then + if Result <> wucFailure then begin KL.Log('UpdateAvailableState.HandleCheck CheckForUpdates not successful: '+ GetEnumName(TypeInfo(TUpdateState), Ord(Result))); @@ -921,7 +920,9 @@ begin CheckForUpdates.Free; end; { Response OK and go back to update available so files can be downloaded } - if Result = wucSuccess then + // TODO: This acually needs to check if the updates available are newer then the already downloaded updates + + if Result = wucUpdateAvailable then begin ChangeState(UpdateAvailableState); end; From c0dcec60f1c36209ae7cc4b2e1d4843360a6828c Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 5 Jun 2025 11:10:14 +1000 Subject: [PATCH 006/262] fix(windows): update all update check code --- .../kmshell/main/Keyman.System.UpdateStateMachine.pas | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index ed662c5e52..e3b17ba28b 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -641,7 +641,7 @@ begin CheckForUpdates.Free; end; { Response OK and Update is available } - if UpdateCheckResult = wucSuccess then + if (UpdateCheckResult = wucSuccess) and (TUpdateCheckStorage.CheckMetaDataForUpdate) then begin ChangeState(UpdateAvailableState); end; @@ -921,7 +921,9 @@ begin CheckForUpdates.Free; end; { Response OK and go back to update available so files can be downloaded } - if Result = wucSuccess then + // TODO: first check if downloaded files are the same as the the latest files in + // the metadata, if not then we should download the new files. + if (Result = wucSuccess) and (TUpdateCheckStorage.CheckMetaDataForUpdate) then begin ChangeState(UpdateAvailableState); end; From 4462be91660058171e377ab98f68e87c9b08734e Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 5 Jun 2025 14:38:20 +1000 Subject: [PATCH 007/262] fix(windows): update TODO with issue number --- .../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 e3b17ba28b..a8291ee325 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -921,7 +921,7 @@ begin CheckForUpdates.Free; end; { Response OK and go back to update available so files can be downloaded } - // TODO: first check if downloaded files are the same as the the latest files in + // TODO: #14126 first check if already downloaded files are the same as the the latest files in // the metadata, if not then we should download the new files. if (Result = wucSuccess) and (TUpdateCheckStorage.CheckMetaDataForUpdate) then begin From d9378aebb2a423e3aa5090d8f9802c376a6a8459 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 5 Jun 2025 11:11:42 +0700 Subject: [PATCH 008/262] maint(common): Build-bot now available to control test builds The Build-bot command is available now to control which platforms are built. See the wiki for instructions on how to use Build-bot. Build-bot: build Test-bot: skip --- resources/build.sh | 1 + resources/build/increment-version.sh | 2 +- resources/build/run-required-test-builds.sh | 199 ++++--- .../build/test/build-bot/pr-14013-commits.txt | 171 ++++++ .../build/test/build-bot/pr-14013-data.txt | 534 ++++++++++++++++++ .../build/test/build-bot/pr-9999-commits.txt | 17 + .../build/test/build-bot/pr-9999-data.txt | 3 + .../test/build-bot/trigger-build-bot.test.sh | 198 +++++++ resources/build/test/test-utils.inc.sh | 4 +- resources/build/trigger-build-bot.inc.sh | 163 ++++++ resources/build/trigger-builds.inc.sh | 58 +- resources/build/trigger-definitions.inc.sh | 32 ++ 12 files changed, 1288 insertions(+), 94 deletions(-) create mode 100644 resources/build/test/build-bot/pr-14013-commits.txt create mode 100644 resources/build/test/build-bot/pr-14013-data.txt create mode 100644 resources/build/test/build-bot/pr-9999-commits.txt create mode 100644 resources/build/test/build-bot/pr-9999-data.txt create mode 100755 resources/build/test/build-bot/trigger-build-bot.test.sh create mode 100644 resources/build/trigger-build-bot.inc.sh diff --git a/resources/build.sh b/resources/build.sh index a3cf8b0dd5..5ac7bde55d 100755 --- a/resources/build.sh +++ b/resources/build.sh @@ -15,6 +15,7 @@ builder_parse "$@" if builder_start_action test; then ./build/test/test.sh + ./build/test/build-bot/trigger-build-bot.test.sh ./build/publish-minimum-versions.sh test builder_finish_action success test fi diff --git a/resources/build/increment-version.sh b/resources/build/increment-version.sh index f1d15c6a7d..1ffa98054c 100755 --- a/resources/build/increment-version.sh +++ b/resources/build/increment-version.sh @@ -158,7 +158,7 @@ if [ "$action" == "commit" ]; then # Trigger builds for the previous version on TeamCity and GitHub # - triggerBuilds + triggerReleaseBuilds # # Now, create the PR on GitHub which will be merged when ready diff --git a/resources/build/run-required-test-builds.sh b/resources/build/run-required-test-builds.sh index eada3e90ca..4e6e324087 100755 --- a/resources/build/run-required-test-builds.sh +++ b/resources/build/run-required-test-builds.sh @@ -1,22 +1,51 @@ #!/usr/bin/env bash # # Determine if we need to do a build based on rules in -# trigger-definitions.inc.sh, rather than calculating changes in -# TeamCity. If a build is needed, then we ask TeamCity to -# start the build. +# trigger-definitions.inc.sh and based on commit trailers and PR body comments, +# rather than calculating changes in TeamCity. If a build is needed, then we ask +# TeamCity / GHA to start the build. # -set -e -set -u +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE -if [[ $# -lt 1 ]]; then - echo 'Usage: run-required-test-build.sh pull-request-number|branch' - echo ' where branch can be master, beta' - exit 1 +. "${THIS_SCRIPT%/*}/trigger-definitions.inc.sh" +. "${THIS_SCRIPT%/*}/trigger-builds.inc.sh" +. "${THIS_SCRIPT%/*}/trigger-build-bot.inc.sh" +. "${THIS_SCRIPT%/*}/jq.inc.sh" + +builder_describe "Run test builds for the given pull request/primary branch" \ + "--dry-run,-n Only report back which builds would be started" \ + "--branch,-b=PRNUM Branch (master,beta) or pull-request to test" + +if [[ $# -eq 1 ]] && [[ "$1" =~ ^[0-9]+$ ]]; then + # For transitional period, when build configuration on TC needs to support + # both forms, we will pass a modified set of params to builder_parse + # + # TODO: remove this condition once all branches have received this change, + # and update TC build configuration to pass -b parameter + # + builder_parse -b "$1" else - PRNUM="$1" + builder_parse "$@" fi +# Validate parameters + +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" +fi + +# +# Debug logging +# function debug_echo() { if [ ! -z ${DEBUG:-} ]; then echo "DEBUG: $1" @@ -25,45 +54,51 @@ function debug_echo() { } # -# This must be run under a TeamCity environment in order to receive all variables -# - -## START STANDARD BUILD SCRIPT INCLUDE -# adjust relative paths as necessary -THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" -. "${THIS_SCRIPT%/*}/../../resources/build/build-utils.sh" -## END STANDARD BUILD SCRIPT INCLUDE - -. "${THIS_SCRIPT%/*}/trigger-definitions.inc.sh" -. "${THIS_SCRIPT%/*}/trigger-builds.inc.sh" -. "${THIS_SCRIPT%/*}/jq.inc.sh" - -# -# Iterate through the platforms 'array' passed in and +# Iterate through the platforms array passed in and # run builds associated with each platform found # - +# Parameters: +# 1: name of associative array of platforms, platform=skip|build|release|[fulltest?] +# 2: branch name or PR number +# function triggerTestBuilds() { - # Note: we always run builds for 'all' platforms - local platforms=( `echo "all $1"` ) + local -n platforms=$1 local branch="$2" - local force="${3:-false}" # Cancel any already running builds for this branch - node "$THIS_SCRIPT_PATH/ci/cancel-builds/cancel-test-builds.mjs" "$branch" "$TEAMCITY_TOKEN" + if builder_has_option --dry-run; then + builder_echo "DRY RUN: cancel current builds for $branch" + else + node "$THIS_SCRIPT_PATH/ci/cancel-builds/cancel-test-builds.mjs" "$branch" "$TEAMCITY_TOKEN" + fi - for platform in "${platforms[@]}"; do - echo "# $platform: checking for changes" + for platform in "${!platforms[@]}"; do + 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'[@]})' for test_build in "${test_builds[@]}"; do if [[ $test_build == "" ]]; then continue; fi if [ "${test_build:(-7)}" == "_GitHub" ]; then local job=${test_build%_GitHub} - echo " -- Triggering GitHub action build $job/$branch" - triggerGitHubActionsBuild true "$job" "$branch" + + if builder_has_option --dry-run; then + 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" + fi else - echo " -- Triggering build configuration $test_build on teamcity" - triggerTeamCityBuild true "$test_build" "$vcs_test" "$branch" + if builder_has_option --dry-run; then + 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" + fi fi done done @@ -76,8 +111,8 @@ function triggerTestBuilds() { if [[ ! "$PRNUM" =~ ^[[:digit:]]+$ ]]; then # branch name is 'master', 'beta' [, or 'stable' -- in the future] - echo ". Branch $PRNUM needs to pass tests on all platforms." - triggerTestBuilds "`echo ${available_platforms[@]}`" "$PRNUM" "true" + builder_echo "Branch $PRNUM needs to pass tests on all platforms." + triggerTestBuilds main_branch_platform_build_levels $PRNUM exit 0 fi @@ -88,7 +123,7 @@ fi # targeted by the PR other than to ask GitHub, anyway.) # -echo ". Get information about pull request #$PRNUM from GitHub" +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'` @@ -111,7 +146,7 @@ fi # # (Ensure we are within the repository before running git calls) -echo ". Ensure our local branch is up to date" +builder_echo grey "# Ensure our local branch is up to date" pushd "$KEYMAN_ROOT" > /dev/null git fetch origin > /dev/null @@ -132,7 +167,7 @@ fi # We work from origin so we don't need the branches in our local copy # -echo ". Get list of changed files in the pull request" +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 # Don't trigger any builds, exit with success @@ -155,41 +190,65 @@ popd > /dev/null # Find the platforms that have changes based on the watch_ variables in trigger-definitions.inc.sh # -echo ". Find platforms that have changes" -build_platforms=() +function find_platform_changes() { + builder_echo grey "# Find platforms that have changes" + declare -gA build_platforms + local platform watch -# Scan the files found -while IFS= read -r line; do - # for each platform - for platform in "${available_platforms[@]}"; do - if [[ ! " ${build_platforms[@]} " =~ " $platform " ]]; then - # Which platform are we watching? - eval watch='$'watch_$platform - # Add common patterns to the watch list - watch="^($platform|(oem/[^/]+/$platform)|resources|$watch)" - if [[ "$line" =~ $watch ]]; then - build_platforms+=($platform) + # Scan the files found + while IFS= read -r line; do + # for each platform + for platform in "${available_platforms[@]}"; do + if [[ ! " ${!build_platforms[@]} " =~ " $platform " ]]; then + # Which platform are we watching? + eval watch='$'watch_$platform + # Add common patterns to the watch list + # TODO: remove 'resources' from this watch? + watch="^($platform|(oem/[^/]+/$platform)|resources|$watch)" + if [[ "$line" =~ $watch ]]; then + # By default, we'll build a 'release' test build for touched platforms + build_platforms[$platform]=$build_level_release + fi fi - fi - done -done <<< "$prfiles" + done + done <<< "$prfiles" -debug_echo "Build platforms: ${build_platforms[*]}" + debug_echo "Default build platforms: ${!build_platforms[@]}" +} + +find_platform_changes + +# +# Now check PR commits for Build-bot: commands +# This will modify the build_platforms array +# + +if [ "$prremote" == "origin" ]; then + # We only accept Build-bot commands on trusted local origin PRs + prcommits=`curl -s -H "User-Agent: @keymanapp" -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/repos/keymanapp/keyman/pulls/$PRNUM/commits` + test_bot_check_pr_body "$PRNUM" "$prinfo" + build_bot_check_messages $PRNUM "$prinfo" "$prcommits" +fi + +# +# Start test builds as required +# if (( ${#build_platforms[@]} > 0)); then - # - # Start the test builds - # - echo ". Start test builds" - triggerTestBuilds "`echo ${build_platforms[@]}`" "$PRNUM" + builder_echo heading "Start test builds" + triggerTestBuilds build_platforms $PRNUM else - echo ". No builds to start" - curl --silent --write-out '\n' \ - --request POST \ - --header "Accept: application/vnd.github+json" \ - --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}" + builder_echo heading "No builds to start" + if builder_has_option --dry-run; then + builder_echo "DRY RUN: write successful status check 'Skipping since no platform builds necessary'" + else + curl --silent --write-out '\n' \ + --request POST \ + --header "Accept: application/vnd.github+json" \ + --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 fi exit 0 diff --git a/resources/build/test/build-bot/pr-14013-commits.txt b/resources/build/test/build-bot/pr-14013-commits.txt new file mode 100644 index 0000000000..abfb04a549 --- /dev/null +++ b/resources/build/test/build-bot/pr-14013-commits.txt @@ -0,0 +1,171 @@ +[ + { + "sha": "b7970d439cdd18e84fd754bf0f06a8878c18bb97", + "node_id": "C_kwDOAY2xT9oAKGI3OTcwZDQzOWNkZDE4ZTg0ZmQ3NTRiZjBmMDZhODg3OGMxOGJiOTc", + "commit": { + "author": { + "name": "Marc Durdin", + "email": "marc@durdin.net", + "date": "2025-04-30T01:59:27Z" + }, + "committer": { + "name": "Marc Durdin", + "email": "marc@durdin.net", + "date": "2025-04-30T01:59:27Z" + }, + "message": "maint(common): consolidate VC++ environment setup for meson\n\nUse the automated environment setup that we built for Windows and\nDeveloper builds in our meson build configurations, so we don't need to\ncall out to vsdevcmd.bat for every build step. This makes the build\nfaster, more consistent across the various ways we call VC++, and\nsimplifies the shell scripts. Removes the complicated build.bat wrappers.\n\nAlso removes the `cleanup_visual_studio_path` function which should no\nlonger be necessary, as our environment builder does the job for us.\n\nMinor complication in that `$TARGET_PATH` variable in kmcmplib/build.sh\nis also used in the Windows environment setup, so eliminated its use in\nkmcmplib/build.sh.\n\nFinally, removes the `$MESON_LOW_VERSION` variable as we know we are\nalways on Meson 1.0 or later now.\n\nBuild-bot: skip developer, windows, foo\nBuild-bot: bash windows", + "tree": { + "sha": "df937f0cb23e2e3736d92cf39b3b7f62ce20bcc1", + "url": "https://api.github.com/repos/keymanapp/keyman/git/trees/df937f0cb23e2e3736d92cf39b3b7f62ce20bcc1" + }, + "url": "https://api.github.com/repos/keymanapp/keyman/git/commits/b7970d439cdd18e84fd754bf0f06a8878c18bb97", + "comment_count": 0, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null, + "verified_at": null + } + }, + "url": "https://api.github.com/repos/keymanapp/keyman/commits/b7970d439cdd18e84fd754bf0f06a8878c18bb97", + "html_url": "https://github.com/keymanapp/keyman/commit/b7970d439cdd18e84fd754bf0f06a8878c18bb97", + "comments_url": "https://api.github.com/repos/keymanapp/keyman/commits/b7970d439cdd18e84fd754bf0f06a8878c18bb97/comments", + "author": { + "login": "mcdurdin", + "id": 4498365, + "node_id": "MDQ6VXNlcjQ0OTgzNjU=", + "avatar_url": "https://avatars.githubusercontent.com/u/4498365?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mcdurdin", + "html_url": "https://github.com/mcdurdin", + "followers_url": "https://api.github.com/users/mcdurdin/followers", + "following_url": "https://api.github.com/users/mcdurdin/following{/other_user}", + "gists_url": "https://api.github.com/users/mcdurdin/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mcdurdin/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mcdurdin/subscriptions", + "organizations_url": "https://api.github.com/users/mcdurdin/orgs", + "repos_url": "https://api.github.com/users/mcdurdin/repos", + "events_url": "https://api.github.com/users/mcdurdin/events{/privacy}", + "received_events_url": "https://api.github.com/users/mcdurdin/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "mcdurdin", + "id": 4498365, + "node_id": "MDQ6VXNlcjQ0OTgzNjU=", + "avatar_url": "https://avatars.githubusercontent.com/u/4498365?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mcdurdin", + "html_url": "https://github.com/mcdurdin", + "followers_url": "https://api.github.com/users/mcdurdin/followers", + "following_url": "https://api.github.com/users/mcdurdin/following{/other_user}", + "gists_url": "https://api.github.com/users/mcdurdin/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mcdurdin/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mcdurdin/subscriptions", + "organizations_url": "https://api.github.com/users/mcdurdin/orgs", + "repos_url": "https://api.github.com/users/mcdurdin/repos", + "events_url": "https://api.github.com/users/mcdurdin/events{/privacy}", + "received_events_url": "https://api.github.com/users/mcdurdin/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "926be6e9463e0497dbf1d32d4ef908ae2aad20db", + "url": "https://api.github.com/repos/keymanapp/keyman/commits/926be6e9463e0497dbf1d32d4ef908ae2aad20db", + "html_url": "https://github.com/keymanapp/keyman/commit/926be6e9463e0497dbf1d32d4ef908ae2aad20db" + } + ] + }, + { + "sha": "0230010a509e317f835d18e1d3be116c30047cf9", + "node_id": "C_kwDOAY2xT9oAKDAyMzAwMTBhNTA5ZTMxN2Y4MzVkMThlMWQzYmUxMTZjMzAwNDdjZjk", + "commit": { + "author": { + "name": "Marc Durdin", + "email": "marc@durdin.net", + "date": "2025-04-30T02:18:26Z" + }, + "committer": { + "name": "GitHub", + "email": "noreply@github.com", + "date": "2025-04-30T02:18:26Z" + }, + "message": "Merge branch 'maint/meson-versioning-improvements' into maint/core-developer/12639-meson-no-batch-wrappers\n\nBuild-bot: skip all", + "tree": { + "sha": "47bb7ca6f52998e8a14e478f5e9f5d7ba415ace3", + "url": "https://api.github.com/repos/keymanapp/keyman/git/trees/47bb7ca6f52998e8a14e478f5e9f5d7ba415ace3" + }, + "url": "https://api.github.com/repos/keymanapp/keyman/git/commits/0230010a509e317f835d18e1d3be116c30047cf9", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid", + "signature": "-----BEGIN PGP SIGNATURE-----\n\nwsFcBAABCAAQBQJoEYhyCRC1aQ7uu5UhlAAAg4MQAFxQGq7WPhHNzlspVIFv5k8W\nJPmC2SP+eUXyDKpO38MqnAPlMqaNrleB071BtE9i2DeWHwYvGwJ87ki95aAtg00F\ne4RbhJYy5sW/F/0fNzHgGNFPNb3EcRyt6I7X3xRB+i1k0PgTSVIpnLzXn3gj9GwU\n7PuLgNO0CUpcgWc6X7a259RAlLInEJ+I8p9XhW827eRalogwgALt4IuJmWyJALFL\nJFeWGlCKdVfbHEgBgEyA98zNnvtoxQ+FlJn1V1jJEBpCY+MPB5tyCXZ4LZbn/n7V\n0+2Pp3c5lnZKmaOyUykwQw7P8s4R2+aQnghRHEHa5Fm6hjQkHMiCOwgTwpIrUV+U\n4AWzPjZSw71cBmAUJBUe7SIBv51r1TUeuNy9x3/0r588znBItR12C91KQD8u9o3/\nMqcWTkpEuGgD2nYbhTdb9PNF4Ik/wbBKLDvjQg180b/ocklVaGAAqjOHdvkyQ4in\nIBRqiaeI3zD5TQ7936Sk1JQPil97PWzk27vde0KAeCR59yL8lCnHr2Rtp8hEpytX\nGrlL14GIaVK6YEvumloPFXrWnbdETu0yRllVa4T2RbWskZUtPG93l/fmOV6tiAr2\np79lgJQ0eEJ1fBNm9UsvjDXU478fSU5Dobg1pwv+wxr10u43tPbqmMI6VLxOjt7i\nz44SZyX8udppGuASFSsw\n=3UIv\n-----END PGP SIGNATURE-----\n", + "payload": "tree 47bb7ca6f52998e8a14e478f5e9f5d7ba415ace3\nparent b7970d439cdd18e84fd754bf0f06a8878c18bb97\nparent e976b5563589a9e38c778f3c06e404e82526358a\nauthor Marc Durdin 1745979506 +0700\ncommitter GitHub 1745979506 +0700\n\nMerge branch 'maint/meson-versioning-improvements' into maint/core-developer/12639-meson-no-batch-wrappers", + "verified_at": "2025-04-30T02:18:29Z" + } + }, + "url": "https://api.github.com/repos/keymanapp/keyman/commits/0230010a509e317f835d18e1d3be116c30047cf9", + "html_url": "https://github.com/keymanapp/keyman/commit/0230010a509e317f835d18e1d3be116c30047cf9", + "comments_url": "https://api.github.com/repos/keymanapp/keyman/commits/0230010a509e317f835d18e1d3be116c30047cf9/comments", + "author": { + "login": "mcdurdin", + "id": 4498365, + "node_id": "MDQ6VXNlcjQ0OTgzNjU=", + "avatar_url": "https://avatars.githubusercontent.com/u/4498365?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mcdurdin", + "html_url": "https://github.com/mcdurdin", + "followers_url": "https://api.github.com/users/mcdurdin/followers", + "following_url": "https://api.github.com/users/mcdurdin/following{/other_user}", + "gists_url": "https://api.github.com/users/mcdurdin/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mcdurdin/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mcdurdin/subscriptions", + "organizations_url": "https://api.github.com/users/mcdurdin/orgs", + "repos_url": "https://api.github.com/users/mcdurdin/repos", + "events_url": "https://api.github.com/users/mcdurdin/events{/privacy}", + "received_events_url": "https://api.github.com/users/mcdurdin/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "committer": { + "login": "web-flow", + "id": 19864447, + "node_id": "MDQ6VXNlcjE5ODY0NDQ3", + "avatar_url": "https://avatars.githubusercontent.com/u/19864447?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/web-flow", + "html_url": "https://github.com/web-flow", + "followers_url": "https://api.github.com/users/web-flow/followers", + "following_url": "https://api.github.com/users/web-flow/following{/other_user}", + "gists_url": "https://api.github.com/users/web-flow/gists{/gist_id}", + "starred_url": "https://api.github.com/users/web-flow/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/web-flow/subscriptions", + "organizations_url": "https://api.github.com/users/web-flow/orgs", + "repos_url": "https://api.github.com/users/web-flow/repos", + "events_url": "https://api.github.com/users/web-flow/events{/privacy}", + "received_events_url": "https://api.github.com/users/web-flow/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "parents": [ + { + "sha": "b7970d439cdd18e84fd754bf0f06a8878c18bb97", + "url": "https://api.github.com/repos/keymanapp/keyman/commits/b7970d439cdd18e84fd754bf0f06a8878c18bb97", + "html_url": "https://github.com/keymanapp/keyman/commit/b7970d439cdd18e84fd754bf0f06a8878c18bb97" + }, + { + "sha": "e976b5563589a9e38c778f3c06e404e82526358a", + "url": "https://api.github.com/repos/keymanapp/keyman/commits/e976b5563589a9e38c778f3c06e404e82526358a", + "html_url": "https://github.com/keymanapp/keyman/commit/e976b5563589a9e38c778f3c06e404e82526358a" + } + ] + } +] diff --git a/resources/build/test/build-bot/pr-14013-data.txt b/resources/build/test/build-bot/pr-14013-data.txt new file mode 100644 index 0000000000..fc3f96a725 --- /dev/null +++ b/resources/build/test/build-bot/pr-14013-data.txt @@ -0,0 +1,534 @@ +{ + "url": "https://api.github.com/repos/keymanapp/keyman/pulls/14013", + "id": 2533896821, + "node_id": "PR_kwDOAY2xT86XCDJ1", + "html_url": "https://github.com/keymanapp/keyman/pull/14013", + "diff_url": "https://github.com/keymanapp/keyman/pull/14013.diff", + "patch_url": "https://github.com/keymanapp/keyman/pull/14013.patch", + "issue_url": "https://api.github.com/repos/keymanapp/keyman/issues/14013", + "number": 14013, + "state": "open", + "locked": false, + "title": "feat(developer): kmc-convert validate keylayout file 😎", + "user": { + "login": "SabineSIL", + "id": 86713187, + "node_id": "MDQ6VXNlcjg2NzEzMTg3", + "avatar_url": "https://avatars.githubusercontent.com/u/86713187?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SabineSIL", + "html_url": "https://github.com/SabineSIL", + "followers_url": "https://api.github.com/users/SabineSIL/followers", + "following_url": "https://api.github.com/users/SabineSIL/following{/other_user}", + "gists_url": "https://api.github.com/users/SabineSIL/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SabineSIL/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SabineSIL/subscriptions", + "organizations_url": "https://api.github.com/users/SabineSIL/orgs", + "repos_url": "https://api.github.com/users/SabineSIL/repos", + "events_url": "https://api.github.com/users/SabineSIL/events{/privacy}", + "received_events_url": "https://api.github.com/users/SabineSIL/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "body": "\r\n\r\n**The setup:** \r\n- The xsd-file : `resources\\standards-data\\keylayout\\dtd\\keylayout.xsd`\r\n- The `resources\\standards-data\\keylayout\\create_keylayout_schema.sh` exists and creates `resources\\standards-data\\keylayout\\keylayout.schema.json`\r\n- The `common/web/types/build.sh configure` creates a `common\\web\\types\\src\\schemas\\keylayout.schema.validator.mjs` file \r\n- The .mjs is then used in validate() function of `keylayout-file-reader.ts` (`...SchemaValidators.default.keylayout(source)`) \r\n\r\n**The problem:** \r\nAll the above works but while using the `validate20()` function of `keylayout.schema.validator.mjs` , **key0 will be \"?xml\"** which then produces the error.\r\n \r\n> \r\n> for (const **key0** in data) {\r\n> if (!(key0 === \"keyboard\")) {\r\n> validate20.errors = [{ instancePath, schemaPath: \"#/additionalProperties\", keyword: \"additionalProperties\", params: { additionalProperty: key0 }, message: \"must NOT have additional properties\" }];\r\n> return false;\r\n> break;\r\n> }\r\n> }\r\n\r\n**The error:** \r\n![image](https://github.com/user-attachments/assets/efa4a0ba-a53a-419f-9d39-15c61ce26fe5)\r\n\r\n**So the question is: Why is `?xml `treated as an element tag and how can we prevent this**\r\n\r\n@keymanapp-test-bot skip\r\n\r\nBuild-bot: skip", + "created_at": "2025-05-21T10:02:29Z", + "updated_at": "2025-05-26T01:30:35Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": null, + "assignee": { + "login": "SabineSIL", + "id": 86713187, + "node_id": "MDQ6VXNlcjg2NzEzMTg3", + "avatar_url": "https://avatars.githubusercontent.com/u/86713187?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SabineSIL", + "html_url": "https://github.com/SabineSIL", + "followers_url": "https://api.github.com/users/SabineSIL/followers", + "following_url": "https://api.github.com/users/SabineSIL/following{/other_user}", + "gists_url": "https://api.github.com/users/SabineSIL/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SabineSIL/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SabineSIL/subscriptions", + "organizations_url": "https://api.github.com/users/SabineSIL/orgs", + "repos_url": "https://api.github.com/users/SabineSIL/repos", + "events_url": "https://api.github.com/users/SabineSIL/events{/privacy}", + "received_events_url": "https://api.github.com/users/SabineSIL/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "assignees": [ + { + "login": "SabineSIL", + "id": 86713187, + "node_id": "MDQ6VXNlcjg2NzEzMTg3", + "avatar_url": "https://avatars.githubusercontent.com/u/86713187?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SabineSIL", + "html_url": "https://github.com/SabineSIL", + "followers_url": "https://api.github.com/users/SabineSIL/followers", + "following_url": "https://api.github.com/users/SabineSIL/following{/other_user}", + "gists_url": "https://api.github.com/users/SabineSIL/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SabineSIL/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SabineSIL/subscriptions", + "organizations_url": "https://api.github.com/users/SabineSIL/orgs", + "repos_url": "https://api.github.com/users/SabineSIL/repos", + "events_url": "https://api.github.com/users/SabineSIL/events{/privacy}", + "received_events_url": "https://api.github.com/users/SabineSIL/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } + ], + "requested_reviewers": [ + + ], + "requested_teams": [ + + ], + "labels": [ + { + "id": 648605999, + "node_id": "MDU6TGFiZWw2NDg2MDU5OTk=", + "url": "https://api.github.com/repos/keymanapp/keyman/labels/developer/", + "name": "developer/", + "color": "f9d0c4", + "default": false, + "description": "" + }, + { + "id": 1095223705, + "node_id": "MDU6TGFiZWwxMDk1MjIzNzA1", + "url": "https://api.github.com/repos/keymanapp/keyman/labels/common/", + "name": "common/", + "color": "006b75", + "default": false, + "description": "" + }, + { + "id": 1298035273, + "node_id": "MDU6TGFiZWwxMjk4MDM1Mjcz", + "url": "https://api.github.com/repos/keymanapp/keyman/labels/developer/compilers/", + "name": "developer/compilers/", + "color": "f9d0c4", + "default": false, + "description": "" + }, + { + "id": 1608627704, + "node_id": "MDU6TGFiZWwxNjA4NjI3NzA0", + "url": "https://api.github.com/repos/keymanapp/keyman/labels/common/resources/", + "name": "common/resources/", + "color": "006b75", + "default": false, + "description": "Build infrastructure" + }, + { + "id": 2058996244, + "node_id": "MDU6TGFiZWwyMDU4OTk2MjQ0", + "url": "https://api.github.com/repos/keymanapp/keyman/labels/feat", + "name": "feat", + "color": "84b6eb", + "default": false, + "description": "" + }, + { + "id": 2073602939, + "node_id": "MDU6TGFiZWwyMDczNjAyOTM5", + "url": "https://api.github.com/repos/keymanapp/keyman/labels/common/web/", + "name": "common/web/", + "color": "006b75", + "default": false, + "description": "" + }, + { + "id": 7333596907, + "node_id": "LA_kwDOAY2xT88AAAABtR3O6w", + "url": "https://api.github.com/repos/keymanapp/keyman/labels/epic-kmc-convert", + "name": "epic-kmc-convert", + "color": "F9C2EE", + "default": false, + "description": "" + } + ], + "milestone": { + "url": "https://api.github.com/repos/keymanapp/keyman/milestones/229", + "html_url": "https://github.com/keymanapp/keyman/milestone/229", + "labels_url": "https://api.github.com/repos/keymanapp/keyman/milestones/229/labels", + "id": 12655812, + "node_id": "MI_kwDOAY2xT84AwRzE", + "number": 229, + "title": "A19S4", + "description": null, + "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 + }, + "open_issues": 108, + "closed_issues": 34, + "state": "open", + "created_at": "2025-04-02T03:26:05Z", + "updated_at": "2025-05-31T00:48:44Z", + "due_on": "2025-06-06T07:00:00Z", + "closed_at": null + }, + "draft": true, + "commits_url": "https://api.github.com/repos/keymanapp/keyman/pulls/14013/commits", + "review_comments_url": "https://api.github.com/repos/keymanapp/keyman/pulls/14013/comments", + "review_comment_url": "https://api.github.com/repos/keymanapp/keyman/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/keymanapp/keyman/issues/14013/comments", + "statuses_url": "https://api.github.com/repos/keymanapp/keyman/statuses/00158fdeeefc98e7d2b507729e91fab845415f6f", + "head": { + "label": "keymanapp:feat/developer/kmc-convert-validateKeylayout", + "ref": "feat/developer/kmc-convert-validateKeylayout", + "sha": "00158fdeeefc98e7d2b507729e91fab845415f6f", + "user": { + "login": "keymanapp", + "id": 12402926, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEyNDAyOTI2", + "avatar_url": "https://avatars.githubusercontent.com/u/12402926?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp", + "html_url": "https://github.com/keymanapp", + "followers_url": "https://api.github.com/users/keymanapp/followers", + "following_url": "https://api.github.com/users/keymanapp/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp/orgs", + "repos_url": "https://api.github.com/users/keymanapp/repos", + "events_url": "https://api.github.com/users/keymanapp/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 26063183, + "node_id": "MDEwOlJlcG9zaXRvcnkyNjA2MzE4Mw==", + "name": "keyman", + "full_name": "keymanapp/keyman", + "private": false, + "owner": { + "login": "keymanapp", + "id": 12402926, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEyNDAyOTI2", + "avatar_url": "https://avatars.githubusercontent.com/u/12402926?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp", + "html_url": "https://github.com/keymanapp", + "followers_url": "https://api.github.com/users/keymanapp/followers", + "following_url": "https://api.github.com/users/keymanapp/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp/orgs", + "repos_url": "https://api.github.com/users/keymanapp/repos", + "events_url": "https://api.github.com/users/keymanapp/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/keymanapp/keyman", + "description": "Keyman cross platform input methods system running on Android, iOS, Linux, macOS, Windows and mobile and desktop web", + "fork": false, + "url": "https://api.github.com/repos/keymanapp/keyman", + "forks_url": "https://api.github.com/repos/keymanapp/keyman/forks", + "keys_url": "https://api.github.com/repos/keymanapp/keyman/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/keymanapp/keyman/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/keymanapp/keyman/teams", + "hooks_url": "https://api.github.com/repos/keymanapp/keyman/hooks", + "issue_events_url": "https://api.github.com/repos/keymanapp/keyman/issues/events{/number}", + "events_url": "https://api.github.com/repos/keymanapp/keyman/events", + "assignees_url": "https://api.github.com/repos/keymanapp/keyman/assignees{/user}", + "branches_url": "https://api.github.com/repos/keymanapp/keyman/branches{/branch}", + "tags_url": "https://api.github.com/repos/keymanapp/keyman/tags", + "blobs_url": "https://api.github.com/repos/keymanapp/keyman/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/keymanapp/keyman/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/keymanapp/keyman/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/keymanapp/keyman/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/keymanapp/keyman/statuses/{sha}", + "languages_url": "https://api.github.com/repos/keymanapp/keyman/languages", + "stargazers_url": "https://api.github.com/repos/keymanapp/keyman/stargazers", + "contributors_url": "https://api.github.com/repos/keymanapp/keyman/contributors", + "subscribers_url": "https://api.github.com/repos/keymanapp/keyman/subscribers", + "subscription_url": "https://api.github.com/repos/keymanapp/keyman/subscription", + "commits_url": "https://api.github.com/repos/keymanapp/keyman/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/keymanapp/keyman/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/keymanapp/keyman/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/keymanapp/keyman/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/keymanapp/keyman/contents/{+path}", + "compare_url": "https://api.github.com/repos/keymanapp/keyman/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/keymanapp/keyman/merges", + "archive_url": "https://api.github.com/repos/keymanapp/keyman/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/keymanapp/keyman/downloads", + "issues_url": "https://api.github.com/repos/keymanapp/keyman/issues{/number}", + "pulls_url": "https://api.github.com/repos/keymanapp/keyman/pulls{/number}", + "milestones_url": "https://api.github.com/repos/keymanapp/keyman/milestones{/number}", + "notifications_url": "https://api.github.com/repos/keymanapp/keyman/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/keymanapp/keyman/labels{/name}", + "releases_url": "https://api.github.com/repos/keymanapp/keyman/releases{/id}", + "deployments_url": "https://api.github.com/repos/keymanapp/keyman/deployments", + "created_at": "2014-11-01T21:01:44Z", + "updated_at": "2025-05-31T18:02:06Z", + "pushed_at": "2025-05-31T18:02:03Z", + "git_url": "git://github.com/keymanapp/keyman.git", + "ssh_url": "git@github.com:keymanapp/keyman.git", + "clone_url": "https://github.com/keymanapp/keyman.git", + "svn_url": "https://github.com/keymanapp/keyman", + "homepage": "https://keyman.com/", + "size": 489713, + "stargazers_count": 456, + "watchers_count": 456, + "language": "Pascal", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 120, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 1043, + "license": { + "key": "other", + "name": "Other", + "spdx_id": "NOASSERTION", + "url": null, + "node_id": "MDc6TGljZW5zZTA=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "android", + "css", + "hacktoberfest", + "ios", + "javascript", + "keyboard", + "keyboard-layouts", + "keyman", + "linux", + "macos", + "unicode", + "web", + "windows" + ], + "visibility": "public", + "forks": 120, + "open_issues": 1043, + "watchers": 456, + "default_branch": "master" + } + }, + "base": { + "label": "keymanapp:feat/developer/kmc-convert", + "ref": "feat/developer/kmc-convert", + "sha": "c7f6d15105920d8df18d8973b4f24846fa14ea27", + "user": { + "login": "keymanapp", + "id": 12402926, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEyNDAyOTI2", + "avatar_url": "https://avatars.githubusercontent.com/u/12402926?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp", + "html_url": "https://github.com/keymanapp", + "followers_url": "https://api.github.com/users/keymanapp/followers", + "following_url": "https://api.github.com/users/keymanapp/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp/orgs", + "repos_url": "https://api.github.com/users/keymanapp/repos", + "events_url": "https://api.github.com/users/keymanapp/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "repo": { + "id": 26063183, + "node_id": "MDEwOlJlcG9zaXRvcnkyNjA2MzE4Mw==", + "name": "keyman", + "full_name": "keymanapp/keyman", + "private": false, + "owner": { + "login": "keymanapp", + "id": 12402926, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjEyNDAyOTI2", + "avatar_url": "https://avatars.githubusercontent.com/u/12402926?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/keymanapp", + "html_url": "https://github.com/keymanapp", + "followers_url": "https://api.github.com/users/keymanapp/followers", + "following_url": "https://api.github.com/users/keymanapp/following{/other_user}", + "gists_url": "https://api.github.com/users/keymanapp/gists{/gist_id}", + "starred_url": "https://api.github.com/users/keymanapp/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/keymanapp/subscriptions", + "organizations_url": "https://api.github.com/users/keymanapp/orgs", + "repos_url": "https://api.github.com/users/keymanapp/repos", + "events_url": "https://api.github.com/users/keymanapp/events{/privacy}", + "received_events_url": "https://api.github.com/users/keymanapp/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/keymanapp/keyman", + "description": "Keyman cross platform input methods system running on Android, iOS, Linux, macOS, Windows and mobile and desktop web", + "fork": false, + "url": "https://api.github.com/repos/keymanapp/keyman", + "forks_url": "https://api.github.com/repos/keymanapp/keyman/forks", + "keys_url": "https://api.github.com/repos/keymanapp/keyman/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/keymanapp/keyman/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/keymanapp/keyman/teams", + "hooks_url": "https://api.github.com/repos/keymanapp/keyman/hooks", + "issue_events_url": "https://api.github.com/repos/keymanapp/keyman/issues/events{/number}", + "events_url": "https://api.github.com/repos/keymanapp/keyman/events", + "assignees_url": "https://api.github.com/repos/keymanapp/keyman/assignees{/user}", + "branches_url": "https://api.github.com/repos/keymanapp/keyman/branches{/branch}", + "tags_url": "https://api.github.com/repos/keymanapp/keyman/tags", + "blobs_url": "https://api.github.com/repos/keymanapp/keyman/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/keymanapp/keyman/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/keymanapp/keyman/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/keymanapp/keyman/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/keymanapp/keyman/statuses/{sha}", + "languages_url": "https://api.github.com/repos/keymanapp/keyman/languages", + "stargazers_url": "https://api.github.com/repos/keymanapp/keyman/stargazers", + "contributors_url": "https://api.github.com/repos/keymanapp/keyman/contributors", + "subscribers_url": "https://api.github.com/repos/keymanapp/keyman/subscribers", + "subscription_url": "https://api.github.com/repos/keymanapp/keyman/subscription", + "commits_url": "https://api.github.com/repos/keymanapp/keyman/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/keymanapp/keyman/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/keymanapp/keyman/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/keymanapp/keyman/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/keymanapp/keyman/contents/{+path}", + "compare_url": "https://api.github.com/repos/keymanapp/keyman/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/keymanapp/keyman/merges", + "archive_url": "https://api.github.com/repos/keymanapp/keyman/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/keymanapp/keyman/downloads", + "issues_url": "https://api.github.com/repos/keymanapp/keyman/issues{/number}", + "pulls_url": "https://api.github.com/repos/keymanapp/keyman/pulls{/number}", + "milestones_url": "https://api.github.com/repos/keymanapp/keyman/milestones{/number}", + "notifications_url": "https://api.github.com/repos/keymanapp/keyman/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/keymanapp/keyman/labels{/name}", + "releases_url": "https://api.github.com/repos/keymanapp/keyman/releases{/id}", + "deployments_url": "https://api.github.com/repos/keymanapp/keyman/deployments", + "created_at": "2014-11-01T21:01:44Z", + "updated_at": "2025-05-31T18:02:06Z", + "pushed_at": "2025-05-31T18:02:03Z", + "git_url": "git://github.com/keymanapp/keyman.git", + "ssh_url": "git@github.com:keymanapp/keyman.git", + "clone_url": "https://github.com/keymanapp/keyman.git", + "svn_url": "https://github.com/keymanapp/keyman", + "homepage": "https://keyman.com/", + "size": 489713, + "stargazers_count": 456, + "watchers_count": 456, + "language": "Pascal", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 120, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 1043, + "license": { + "key": "other", + "name": "Other", + "spdx_id": "NOASSERTION", + "url": null, + "node_id": "MDc6TGljZW5zZTA=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "android", + "css", + "hacktoberfest", + "ios", + "javascript", + "keyboard", + "keyboard-layouts", + "keyman", + "linux", + "macos", + "unicode", + "web", + "windows" + ], + "visibility": "public", + "forks": 120, + "open_issues": 1043, + "watchers": 456, + "default_branch": "master" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/keymanapp/keyman/pulls/14013" + }, + "html": { + "href": "https://github.com/keymanapp/keyman/pull/14013" + }, + "issue": { + "href": "https://api.github.com/repos/keymanapp/keyman/issues/14013" + }, + "comments": { + "href": "https://api.github.com/repos/keymanapp/keyman/issues/14013/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/keymanapp/keyman/pulls/14013/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/keymanapp/keyman/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/keymanapp/keyman/pulls/14013/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/keymanapp/keyman/statuses/00158fdeeefc98e7d2b507729e91fab845415f6f" + } + }, + "author_association": "CONTRIBUTOR", + "auto_merge": null, + "active_lock_reason": null, + "merged": false, + "mergeable": false, + "rebaseable": false, + "mergeable_state": "dirty", + "merged_by": null, + "comments": 1, + "review_comments": 0, + "maintainer_can_modify": false, + "commits": 5, + "additions": 639, + "deletions": 12, + "changed_files": 13 +} \ No newline at end of file diff --git a/resources/build/test/build-bot/pr-9999-commits.txt b/resources/build/test/build-bot/pr-9999-commits.txt new file mode 100644 index 0000000000..ac257dfa8e --- /dev/null +++ b/resources/build/test/build-bot/pr-9999-commits.txt @@ -0,0 +1,17 @@ +[ + { + "commit": { + "message": "maint(common): test\nBuild-bot: build windows\n" + } + }, + { + "commit": { + "message": "maint(common): test\nBuild-bot: release windows\n" + } + }, + { + "commit": { + "message": "maint(common): test\nBuild-bot: skip windows\n" + } + } +] diff --git a/resources/build/test/build-bot/pr-9999-data.txt b/resources/build/test/build-bot/pr-9999-data.txt new file mode 100644 index 0000000000..2894897ccf --- /dev/null +++ b/resources/build/test/build-bot/pr-9999-data.txt @@ -0,0 +1,3 @@ +{ + "body": "Build-bot: release developer\r\nBuild-bot: skip windows\r\n" +} \ No newline at end of file diff --git a/resources/build/test/build-bot/trigger-build-bot.test.sh b/resources/build/test/build-bot/trigger-build-bot.test.sh new file mode 100755 index 0000000000..c0d914a6ce --- /dev/null +++ b/resources/build/test/build-bot/trigger-build-bot.test.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env bash + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../../resources/build/build-utils.sh" +# END STANDARD BUILD SCRIPT INCLUDE + +. "${THIS_SCRIPT_PATH}/../../trigger-definitions.inc.sh" +. "${THIS_SCRIPT_PATH}/../../trigger-build-bot.inc.sh" +. "${THIS_SCRIPT_PATH}/../test-utils.inc.sh" +. "${KEYMAN_ROOT}/resources/build/jq.inc.sh" + +# Initialize builder, no input parameters needed for these tests +builder_parse "" + +readonly ALL_BUILD_PLATFORMS_SKIP_EXPECTED='[ios]="skip" [web]="skip" [linux]="skip" [common_mac]="skip" [common_windows]="skip" [mac]="skip" [windows]="skip" [common_web]="skip" [android]="skip" [developer]="skip" [common_linux]="skip"' +readonly ALL_BUILD_PLATFORMS_BUILD_EXPECTED='[ios]="build" [web]="build" [linux]="build" [common_mac]="build" [common_windows]="build" [mac]="build" [windows]="build" [common_web]="build" [android]="build" [developer]="build" [common_linux]="build"' + +#---------------------------------------------------------------------------------------------------- +# Test build bot message parsing (e2e) +#---------------------------------------------------------------------------------------------------- + +test_build_bot_check_messages() { + builder_echo start test_build_bot_check_messages 'START TEST: build_bot_check_messages' + + # Mostly real data (only Build-bot commands edited for test) + _do_test_build_bot_check_messages_file 14013 "[windows]=release" "$ALL_BUILD_PLATFORMS_SKIP_EXPECTED" + # Simplified data for testing Build-bot command sequences only + _do_test_build_bot_check_messages_file 9999 "[windows]=release" '[windows]="skip" [developer]="release"' + # Test some bad inputs + _do_test_build_bot_check_messages_inline 1 "[windows]=release" '[windows]="release"' '{}' '[{ "commit": { "message": "maint(common): test\nBuild-bot: foo windows\n" }}]' + # empty body, 'foo windows' in commit + _do_test_build_bot_check_messages_inline 2 "[windows]=release" '[windows]="release"' '{ "body": "" }' '[{ "commit": { "message": "maint(common): test\nBuild-bot: foo windows\n" }}]' + # attempts to escape jail + _do_test_build_bot_check_messages_inline 3 "[windows]=release" '[windows]="release"' '{ "body": "Build-bot: '"'"'echo foo" }' '[{ "commit": { "message": "maint(common): test\n" }}]' + _do_test_build_bot_check_messages_inline 4 "[windows]=release" '[windows]="release"' '{ "body": "Build-bot: echo *" }' '[{ "commit": { "message": "maint(common): test\n" }}]' + _do_test_build_bot_check_messages_inline 5 "[windows]=release" '[windows]="release"' '{ "body": "Build-bot: \\0" }' '[{ "commit": { "message": "maint(common): test\n" }}]' + _do_test_build_bot_check_messages_inline 5 "[windows]=release" '[windows]="release"' '{ "body": "Build-bot: `echo escaped`" }' '[{ "commit": { "message": "maint(common): test\n" }}]' + _do_test_build_bot_check_messages_inline 6 "[windows]=release" '[windows]="release"' '{ "body": "Build-bot: ' '[{ "commit": { "message": "maint(common): test\n" }}]' + # incomplete command + _do_test_build_bot_check_messages_inline 7 "[windows]=release" '[windows]="release"' '{ "body": "Build-bot: " }' '[{ "commit": { "message": "maint(common): test\n" }}]' + + builder_echo end test_build_bot_check_messages success 'SUCCESS: build_bot_check_messages' +} + +# +# Test a set of Build-bot commands from a file +# +# Parameters: +# 1: PR number +# 2: Input build platforms based on touched files in PR +# 3: Expected build command +_do_test_build_bot_check_messages_file() { + local prnum=$1 + eval "declare -gA build_platforms=($2)" + eval "declare -A expected_build_platforms=($3)" + + build_bot_check_messages $prnum "$(cat ${THIS_SCRIPT_PATH}/pr-$prnum-data.txt)" "$(cat ${THIS_SCRIPT_PATH}/pr-$prnum-commits.txt)" + + for i in "${!expected_build_platforms[@]}"; do + assert-equal "${build_platforms[$i]}" "${expected_build_platforms[$i]}" "PR #$prnum: build_platforms[$i]" + done + for i in "${!build_platforms[@]}"; do + assert-equal "${build_platforms[$i]}" "${expected_build_platforms[$i]}" "PR #$prnum: build_platforms[$i]" + done +} + +# +# Test a set of Build-bot commands from parameters +# +# Parameters: +# 1: PR number +# 2: Input build platforms based on touched files in PR +# 3: Expected build command +# 4: JSON PR details (GitHub PR format) +# 5: JSON commit messages (GitHub PR format) +_do_test_build_bot_check_messages_inline() { + local prnum=$1 + eval "declare -gA build_platforms=($2)" + eval "declare -A expected_build_platforms=($3)" + + local prinfo="$4" + local prcommits="$5" + + build_bot_check_messages $prnum "$prinfo" "$prcommits" + + for i in "${!expected_build_platforms[@]}"; do + assert-equal "${build_platforms[$i]}" "${expected_build_platforms[$i]}" "PR #$prnum: build_platforms[$i]" + done + for i in "${!build_platforms[@]}"; do + assert-equal "${build_platforms[$i]}" "${expected_build_platforms[$i]}" "PR #$prnum: build_platforms[$i]" + done +} + +#---------------------------------------------------------------------------------------------------- +# Test parsing of a single Build-bot command +#---------------------------------------------------------------------------------------------------- + +test_build_bot_update_commands() { + builder_echo start test_build_bot_update_commands 'START TEST: build_bot_update_commands' + _do_test_build_bot_update_commands '[windows]="release"' "skip windows" '[windows]="skip"' + _do_test_build_bot_update_commands '[windows]="release"' "skip windows,developer" '[windows]="skip" [developer]="skip"' + _do_test_build_bot_update_commands '[windows]="release"' "build developer" '[windows]="release" [developer]="build"' + _do_test_build_bot_update_commands '[windows]="release"' "skip windows" '[windows]="skip"' + _do_test_build_bot_update_commands '[windows]="release"' "access foo" '[windows]="release"' + _do_test_build_bot_update_commands '[windows]="release"' "build foo" '[windows]="release"' + _do_test_build_bot_update_commands '[windows]="release"' "build common" '[common]="build" [windows]="release"' + _do_test_build_bot_update_commands '[windows]="release"' "build all" "$ALL_BUILD_PLATFORMS_BUILD_EXPECTED" + builder_echo end test_build_bot_update_commands success 'SUCCESS: build_bot_update_commands' +} + +# +# Test a command +# +# Parameters: +# 1: starting build array based on PR files changed +# 2: build-bot command +# 3: expected finishing build array +# +_do_test_build_bot_update_commands() { + eval "declare -gA build_platforms=($1)" + local update_command="$2" + eval "declare -A expected_build_platforms=($3)" + + build_bot_update_commands $update_command + + for i in "${!expected_build_platforms[@]}"; do + assert-equal "${build_platforms[$i]}" "${expected_build_platforms[$i]}" "build_platforms[$i]" + done + for i in "${!build_platforms[@]}"; do + assert-equal "${build_platforms[$i]}" "${expected_build_platforms[$i]}" "build_platforms[$i]" + done +} + +#---------------------------------------------------------------------------------------------------- + +test_build_bot_verify_platforms() { + builder_echo start test_build_bot_verify_platforms 'START TEST: build_bot_verify_platforms' + _do_test_build_bot_verify_platforms "windows mac" "windows mac" + _do_test_build_bot_verify_platforms "windows foo" "windows" + _do_test_build_bot_verify_platforms "core" "core" + _do_test_build_bot_verify_platforms "all" "${available_platforms[*]}" + builder_echo end test_build_bot_verify_platforms success 'SUCCESS: build_bot_verify_platforms' +} + +_do_test_build_bot_verify_platforms() { + local platforms=($1) + local expected_platforms=($2) + + build_bot_verify_platforms platforms + + assert-equal ${#platforms[@]} ${#expected_platforms[@]} "#platforms[@]" + for i in "${!expected_platforms[@]}"; do + assert-equal "${platforms[$i]}" "${expected_platforms[$i]}" "platforms[$i]" + done +} + +#---------------------------------------------------------------------------------------------------- + +# If we have a Test-bot: skip, or no Test-bot: command, then we downgrade +# 'release' to a 'build' + +test_test_bot_check_pr_body() { + builder_echo start test_test_bot_check_pr_body 'START TEST: test_bot_check_pr_body' + # Test-bot: skip means that we should only do a build+test, not a release build + _do_test_test_bot_check_pr_body 1 '[windows]="release"' '{ "body": "Test-bot: skip" }' '[windows]="build"' + # Empty body means that we should only do a build+test, not a release build + _do_test_test_bot_check_pr_body 2 '[windows]="release"' '{ "body": "" }' '[windows]="build"' + # '# User Testing' we will assume means there are unit tests + _do_test_test_bot_check_pr_body 3 '[windows]="release"' '{ "body": "Some text\n# User Testing\n" }' '[windows]="release"' + # Invalid test-bot command, treat as a skip + _do_test_test_bot_check_pr_body 4 '[windows]="release"' '{ "body": "Test-bot: " }' '[windows]="build"' + builder_echo end test_test_bot_check_pr_body success 'SUCCESS: test_bot_check_pr_body' +} + +_do_test_test_bot_check_pr_body() { + local prnum=$1 + eval "declare -gA build_platforms=($2)" + local prinfo="$3" + eval "declare -A expected_build_platforms=($4)" + + test_bot_check_pr_body $prnum "$prinfo" + + for i in "${!expected_build_platforms[@]}"; do + assert-equal "${build_platforms[$i]}" "${expected_build_platforms[$i]}" "PR #$prnum: build_platforms[$i]" + done + for i in "${!build_platforms[@]}"; do + assert-equal "${build_platforms[$i]}" "${expected_build_platforms[$i]}" "PR #$prnum: build_platforms[$i]" + done +} + +#---------------------------------------------------------------------------------------------------- + +test_build_bot_verify_platforms +test_build_bot_update_commands +test_build_bot_check_messages +test_test_bot_check_pr_body \ No newline at end of file diff --git a/resources/build/test/test-utils.inc.sh b/resources/build/test/test-utils.inc.sh index 47f0aca33e..a73f46773a 100644 --- a/resources/build/test/test-utils.inc.sh +++ b/resources/build/test/test-utils.inc.sh @@ -7,8 +7,8 @@ function assert-equal() { fi if [[ "$actual" != "$expected" ]]; then - builder_die "FAIL: ${message}expected actual '$actual' to equal expected '$expected'" + builder_die " ✕ FAIL: ${message}actual result '$actual' should equal expected '$expected'" else - builder_echo green "PASS: ${message}expected actual '$actual' to be equal to expected '$expected'" + builder_echo green " ✓ PASS: ${message}result '$actual' is correct" fi } diff --git a/resources/build/trigger-build-bot.inc.sh b/resources/build/trigger-build-bot.inc.sh new file mode 100644 index 0000000000..d55ac473ae --- /dev/null +++ b/resources/build/trigger-build-bot.inc.sh @@ -0,0 +1,163 @@ +#!/usr/bin/env bash + +# set -eu + +# . ./trigger-definitions.inc.sh + +# +# Check the Test-bot command in the PR body for commands relevant to the build +# bot, and update global build_platforms accordingly +# +# If the Test-bot command is 'skip', or there are no test bot instructions, then we +# will default to a 'build' for all default build platforms, instead of +# 'release'; this can be overridden by any Build-bot commands in commits or in +# the PR body. +# +# Parameters: +# 1: PR number (not currently used) +# 2: PR JSON data from api.github.com/repos/keymanapp/keyman/pulls/# +# +function test_bot_check_pr_body() { + local PRNUM=$1 + local prinfo="$2" + + set -o noglob + IFS=$'\n' + local prbody="$(echo "$prinfo" | "${JQ}" -r '.body')" + local prTestCommand="$(echo "$prbody" | grep 'Test-bot:' | cut -d: -f 2 - | cut -d' ' -f 1 -)" + local prTestBody="$(echo "$prbody" | grep -i '# User Testing')" + unset IFS + set +o noglob + + if ([[ "$prTestCommand" == skip ]] || [[ -z "${prTestCommand// }" ]]) && [[ -z "${prTestBody// }" ]]; then + local platform + for platform in "${!build_platforms[@]}"; do + build_platforms[$platform]=build + done + fi +} + +# +# Check PR commit messages for Build-bot commands Later commands override +# earlier ones. Also checks PR body for any overriding commands. Note, only +# checks the first 250 commits, so later build commands will be ignored; in this +# situation, use the PR body command. Also ignores build commands in PR +# comments, for sake of performance +# +# The bot will also check the Test-bot: trailer in the PR body, if it exists, but +# will not check any other commits. If the Test-bot: command is just 'skip', then +# by default, all builds will be set to 'build' instead of 'release' +# +# Parameters: +# 1: PR number to check +# 2: PR JSON data from api.github.com/repos/keymanapp/keyman/pulls/# +# 3: Commit JSON data from api.github.com/repos/keymanapp/keyman/pulls/#/commits +# +function build_bot_check_messages() { + local PRNUM=$1 + local prinfo="$2" + local prcommits="$3" + + # We don't want any globbing happening in our parse + + set -o noglob + + # Extract the Build-bot commands from commit messages and the PR body + + + IFS=$'\n' + local buildBotCommands=($(echo "$prcommits" | "${JQ}" -r '.[].commit.message' | grep 'Build-bot:' | cut -d: -f 2 -)) + local prCommands=($(echo "$prinfo" | "${JQ}" -r '.body' | tr -d '\r' | grep 'Build-bot:' | cut -d: -f 2 -)) + unset IFS + + # The PR body Build-bot comment will be read last, which allows it to override + # all previous commands + + if [[ ${#prCommands[@]} -gt 0 ]]; then + buildBotCommands+=("${prCommands[@]}") + fi + + # We now know that our command has only a-z, comma and space, so we can parse + # without risking escaping our bash jail + + for buildBotCommand in "${buildBotCommands[@]}"; do + # Block illegal Build-bot: commands + if [[ ! "$buildBotCommand" =~ ^[a-z,\ ]+$ ]]; then + builder_echo warning "WARNING[Build-bot]: ignoring invalid command: '${buildBotCommand}'" + continue + fi + + if [[ ! -z "${buildBotCommand// }" ]]; then + build_bot_update_commands $buildBotCommand + fi + done + + set +o noglob +} + +# +# parses a 'Build-bot: [platform...]' command and modifies +# the global build_platforms associative array with new levels +# note that this function assumes that inputs are sanitized, see +# build_bot_check_messages +# +function build_bot_update_commands() { + local level= + local platforms= + local command="$*" + + if [[ $# == 1 ]]; then + level=$1 + platforms=all + else + level=$1 + shift + + # remaining parameters are comma/space separated platforms + IFS=', ' + read -r -a platforms <<< "$*" + unset IFS + fi + + if [[ ! $level =~ ^$valid_build_levels$ ]]; then + # Just skip this build command + builder_echo warning "WARNING[Build-bot]: ignoring invalid build level '$level' in command '$command'" + return 0 + fi + + build_bot_verify_platforms platforms + + # a build command should be [platform[, platform...]] + + local platform + for platform in "${platforms[@]}"; do + builder_echo "Build-bot: Updating build level for $platform to $level" + build_platforms[$platform]=$level + done +} + +# +# Strip any unrecognized platforms and expand 'all' to actual platforms +# +# Parameters: +# 1: name of platforms array parameter (byref) +# +function build_bot_verify_platforms() { + local -n input_platforms=$1 + local output_platforms=() + local platform + for platform in "${input_platforms[@]}"; do + # We'll emit a warning with invalid platforms, then remove them from the array + if [[ ! $platform =~ ^(all|$available_platforms_regex)$ ]]; then + builder_echo warning "WARNING[Build-bot]: ignoring invalid platform '$platform'" + elif [[ $platform == all ]]; then + input_platforms=(${available_platforms[@]}) + return + else + if [[ ! "${output_platforms[@]}" =~ $platform ]]; then + output_platforms+=($platform) + fi + fi + done + input_platforms=("${output_platforms[@]}") +} diff --git a/resources/build/trigger-builds.inc.sh b/resources/build/trigger-builds.inc.sh index 639c3d240b..44427170a3 100644 --- a/resources/build/trigger-builds.inc.sh +++ b/resources/build/trigger-builds.inc.sh @@ -1,10 +1,9 @@ #!/usr/bin/env bash # -# Tell TeamCity to trigger new builds +# Tell TeamCity to trigger new release builds # - -function triggerBuilds() { +function triggerReleaseBuilds() { local base=`git branch --show-current` # convert stable-14.0 to stable_14_0 to fit in with the definitions # in trigger-definitions.inc.sh @@ -19,38 +18,44 @@ function triggerBuilds() { if [[ $build == "" ]]; then continue; fi if [ "${build:(-7)}" == "_GitHub" ]; then local job=${build%_GitHub} - echo Triggering GitHub action build "$job" "$base" - triggerGitHubActionsBuild false "$job" "$base" + echo "Triggering GitHub action release build (isTestBuild=false) (level=release) $job $base" + triggerGitHubActionsBuild false release "$job" "$base" else - echo Triggering TeamCity build false $build $TEAMCITY_VCS_ID $base - triggerTeamCityBuild false $build $TEAMCITY_VCS_ID $base + echo "Triggering TeamCity release build (isTestBuild=false) (level=release) $build $TEAMCITY_VCS_ID $base" + triggerTeamCityBuild false release $build $TEAMCITY_VCS_ID $base fi done done } +# +# Trigger a build on TeamCity +# +# Parameters: +# 1: 'true' if this is for a PR branch, otherwise 'false' (for master/beta/stable-x.y) +# 2: 'skip' - don't run build; 'build' - run build and unit tests; 'release' - run build, unit tests, and deploy +# 3: TeamCity build configuration name +# 4: TeamCity VCS id, which is different for PR test configurations +# 5: branch name in git +# function triggerTeamCityBuild() { local isTestBuild="$1" - local TEAMCITY_BUILDTYPE="$2" - local TEAMCITY_VCS_ID="$3" - - if [[ $# -gt 3 ]]; then - local TEAMCITY_BRANCH_NAME="$4" - #debug echo " Triggering build for: $TEAMCITY_BRANCH_NAME" - TEAMCITY_BRANCH_NAME="branchName='$TEAMCITY_BRANCH_NAME' defaultBranch='false'" - else - local TEAMCITY_BRANCH_NAME= - fi + local buildLevel="$2" + local TEAMCITY_BUILDTYPE="$3" + local TEAMCITY_VCS_ID="$4" + local TEAMCITY_BRANCH_NAME="$5" local GIT_OID=`git rev-parse HEAD` local TEAMCITY_SERVER=https://build.palaso.org local command + # TODO: add buildLevel parameter + if $isTestBuild; then - command="" + command="" else - command="" + command="" fi echo "TeamCity Build Command: $command" @@ -66,10 +71,21 @@ function triggerTeamCityBuild() { echo } +# +# Trigger a build on GitHub Actions +# +# Parameters: +# 1: 'true' if this is for a PR branch, otherwise 'false' (for master/beta/stable-x.y) +# 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 +# function triggerGitHubActionsBuild() { local IS_TEST_BUILD="$1" - local GITHUB_ACTION="$2" - local GIT_BRANCH="${3:-master}" + local BUILD_LEVEL="$2" + local GITHUB_ACTION="$3" + local GIT_BRANCH="$4" + # TODO: support BUILD_LEVEL local GIT_BASE_BRANCH="${GIT_BRANCH}" local GIT_USER="keyman-server" local GIT_BUILD_SHA GIT_BASE_REF JSON diff --git a/resources/build/trigger-definitions.inc.sh b/resources/build/trigger-definitions.inc.sh index e4ed49d90d..dc0338ffb8 100644 --- a/resources/build/trigger-definitions.inc.sh +++ b/resources/build/trigger-definitions.inc.sh @@ -5,9 +5,34 @@ # Maps to ci/cancel-builds/trigger-definitions.mjs and must be kept in sync # +# +# Build levels +# + +readonly build_level_skip=skip +readonly build_level_build=build +readonly build_level_release=release +readonly valid_build_levels="$build_level_skip|$build_level_build|$build_level_release" + +# +# Target platforms +# available_platforms=(android common_web common_windows common_mac common_linux ios linux mac web windows developer) +declare -Ag main_branch_platform_build_levels +for available_platforms_i in "${available_platforms[@]}"; do + main_branch_platform_build_levels[$available_platforms_i]=$build_level_build +done +readonly main_branch_platform_build_levels +readonly available_platforms + + +available_platforms_regex=`echo "${available_platforms[@]}" | sed 's/ /|/g'` + +# We also allow 'common' and 'core' platforms for Build-bot: commands +readonly available_platforms_regex="$available_platforms_regex|common|core" + # the base folder for each pattern does not need to be included, nor oem folders # e.g. android='common/models|common/predictive-text' # will expand into android='^(android|(oem/[^/]+/android)|common/models|common/predictive-text)' @@ -57,6 +82,13 @@ bc_test_common_windows=(Keyman_Test_Common_Windows) bc_test_common_mac=(Keyman_Test_Common_Mac) bc_test_common_linux=(Keyman_Test_Common_Linux) +# These configuration arrays are triggered only by Build-bot commands: + +bc_test_common=(Keyman_Test_Common_Web Keyman_Test_Common_Windows Keyman_Test_Common_Mac Keyman_Test_Common_Linux) +bc_test_core=(Keyman_Common_KPAPI_TestPullRequests_Linux Keyman_Common_KPAPI_TestPullRequests_macOS Keyman_Common_KPAPI_TestPullRequests_Windows) + +# Core is tested directly for target platforms + # Keymanweb_TestPullRequestRegressions : currently this is timing out so disabled until we have # time to investigate further From 0de78e808f9ff419f7c4f68d7b2f06900de1e34d Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 6 Jun 2025 10:29:14 +1000 Subject: [PATCH 009/262] feat(windows): don't send logs with diagnostics --- windows/src/engine/tsysinfo/sysinfo_main.pas | 43 +------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/windows/src/engine/tsysinfo/sysinfo_main.pas b/windows/src/engine/tsysinfo/sysinfo_main.pas index 3f411e36f8..b1916be9a7 100644 --- a/windows/src/engine/tsysinfo/sysinfo_main.pas +++ b/windows/src/engine/tsysinfo/sysinfo_main.pas @@ -343,56 +343,17 @@ end; procedure TfrmDiagnostics.mnuSendToKeymanClick(Sender: TObject); var ffilename: string; - i: Integer; - f: TSearchRec; - FTotalSize: Int64; - FSavedFileList: string; - FDeleteFiles: Boolean; begin ffilename := TempFileName('.tsi'); - - FTotalSize := 0; - - for i := 0 to FSIList.Files.Count - 1 do - begin - if FindFirst(FSIList.Files[i], 0, f) = 0 then - begin - FTotalSize := FTotalSize + f.Size; - FindClose(f); - end - end; - - FSavedFileList := FSIList.Files.Text; - - FDeleteFiles := False; // I2240 - - if FTotalSize > 1024 * 1024 * 8 then // 8MB - begin - case MessageDlg('The attached files are very large (' + - FileSizeKB(FTotalSize) + - '). Do you want to send them - this may take some time?', mtConfirmation, - mbYesNoCancel, 0) of - mrYes: - FDeleteFiles := True; - mrNo: - FSIList.Files.Text := ''; - mrCancel: - Exit; - end; - end - else - FDeleteFiles := True; // I2240 + // No longer sending log files to Keyman via this upload method + FSIList.Files.Clear; FSIList.Save(ffilename); - FSIList.Files.Text := FSavedFileList; - with TfrmEmail.Create(Self) do try AttachFile := ffilename; if ShowModal = mrOk then - if FDeleteFiles then - DeleteDiagFiles; // I2240 finally DeleteFile(ffilename); Free; From 770506c331c760329f8a2b975106f45eb3633670 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 6 Jun 2025 11:14:26 +1000 Subject: [PATCH 010/262] feat(windows): tweak diagnositc error message --- windows/src/engine/tsysinfo/UfrmEmail.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/engine/tsysinfo/UfrmEmail.pas b/windows/src/engine/tsysinfo/UfrmEmail.pas index 6be1ba5ccc..6044fd73ce 100644 --- a/windows/src/engine/tsysinfo/UfrmEmail.pas +++ b/windows/src/engine/tsysinfo/UfrmEmail.pas @@ -143,7 +143,7 @@ begin end; if Response.StatusCode <> 413 then begin - ShowMessage('The diagnostic report file was to large: '+IntToStr(Response.StatusCode)+ 'Contact us via the Keyman Community Forum for assistance.'); + ShowMessage('The diagnostic report file was too large: '+IntToStr(Response.StatusCode)+ 'Contact us via the Keyman Community Forum for help.'); Exit; end; s := Trim(string(Response.MessageBodyAsString)); From 7ad4ba85275b6045eb72ba07f12413d36ec97720 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 6 Jun 2025 15:54:32 +1000 Subject: [PATCH 011/262] feat(windows): Fix error checking order --- windows/src/engine/tsysinfo/UfrmEmail.pas | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/windows/src/engine/tsysinfo/UfrmEmail.pas b/windows/src/engine/tsysinfo/UfrmEmail.pas index 6044fd73ce..d3768c5b2b 100644 --- a/windows/src/engine/tsysinfo/UfrmEmail.pas +++ b/windows/src/engine/tsysinfo/UfrmEmail.pas @@ -136,16 +136,16 @@ begin Request.UrlPath := API_Path_SubmitDiag; Upload; + if Response.StatusCode = 413 then + begin + ShowMessage('The diagnostic report file was too large: '+IntToStr(Response.StatusCode)+ 'Contact us via the Keyman Community Forum for help.'); + Exit; + end; if Response.StatusCode <> 200 then begin ShowMessage('The report was not successfully sent. An error was returned: '+IntToStr(Response.StatusCode)); Exit; end; - if Response.StatusCode <> 413 then - begin - ShowMessage('The diagnostic report file was too large: '+IntToStr(Response.StatusCode)+ 'Contact us via the Keyman Community Forum for help.'); - Exit; - end; s := Trim(string(Response.MessageBodyAsString)); if Copy(s,1,7) = '' then begin From e4ca0f7283fa8f60cd1e0ca088a44ca4ac293dd5 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 11 Jun 2025 18:19:26 +0800 Subject: [PATCH 012/262] maint(common): consolidate 7Z_HOME, SEVENZ_HOME, SEVEN_Z_HOME env vars Using SEVENZ_HOME. Fixes: #14149 --- .../developer/publish-to-downloads-keyman-com.ps1 | 8 ++++---- resources/teamcity/web/zip-and-upload-artifacts.ps1 | 5 +---- web/ci.sh | 8 ++++---- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 b/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 index 1899e694cb..776ede5340 100644 --- a/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 +++ b/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 @@ -27,7 +27,7 @@ $setup_exe = "setup.exe" # Debug files $debug_zip = "debug-$build_number.zip" -$7Z_HOME = $env:7Z_HOME +$SEVENZ_HOME = $env:SEVENZ_HOME $RSYNC_HOME = $env:RSYNC_HOME $RSYNC_PATH = $env:RSYNC_PATH $RSYNC_USER = $env:RSYNC_USER @@ -61,17 +61,17 @@ if((Test-Path ..\release\$kmcomp_zip) -ne 0) { # Keyman versions through -16.0 copy ..\..\common\schemas\keyboard_info\keyboard_info.source.json . copy ..\..\common\schemas\keyboard_info\keyboard_info.distribution.json . - & "$7Z_HOME\7z.exe" a -bd -bb0 ..\$upload_path\$kmcomp_zip kmcomp.exe kmcmpdll.dll kmcomp.x64.exe kmcmpdll.x64.dll kmconvert.exe keyboard_info.source.json keyboard_info.distribution.json xml\layoutbuilder\*.keyman-touch-layout projects\ + & "$SEVENZ_HOME\7z.exe" a -bd -bb0 ..\$upload_path\$kmcomp_zip kmcomp.exe kmcmpdll.dll kmcomp.x64.exe kmcmpdll.x64.dll kmconvert.exe keyboard_info.source.json keyboard_info.distribution.json xml\layoutbuilder\*.keyman-touch-layout projects\ # Add Keyman Developer Server to the archive (15.0 late alpha - after 171) if((Test-Path ..\src\server\build) -ne 0) { copy ..\src\server\build\ server\ -Recurse - & "$7Z_HOME\7z.exe" a -bd -bb0 ..\$upload_path\$kmcomp_zip server\ + & "$SEVENZ_HOME\7z.exe" a -bd -bb0 ..\$upload_path\$kmcomp_zip server\ } } else { # Keyman versions 17.0+; note, use npm install for most modules copy ..\..\common\schemas\keyboard_info\keyboard_info.schema.json . - & "$7Z_HOME\7z.exe" a -bd -bb0 ..\$upload_path\$kmcomp_zip kmconvert.exe keyboard_info.schema.json xml\layoutbuilder\*.keyman-touch-layout projects\ server\ + & "$SEVENZ_HOME\7z.exe" a -bd -bb0 ..\$upload_path\$kmcomp_zip kmconvert.exe keyboard_info.schema.json xml\layoutbuilder\*.keyman-touch-layout projects\ server\ } cd .. } diff --git a/resources/teamcity/web/zip-and-upload-artifacts.ps1 b/resources/teamcity/web/zip-and-upload-artifacts.ps1 index 6b29bf7612..f4ed260fd0 100755 --- a/resources/teamcity/web/zip-and-upload-artifacts.ps1 +++ b/resources/teamcity/web/zip-and-upload-artifacts.ps1 @@ -13,13 +13,10 @@ $build_counter = $build_number -replace "^\d+\.\d+\.(\d+)$", '$1' $upload_path = "build\upload\$build_number" $zip = "$upload_path\keymanweb-$build_number.zip" -$7Z_HOME = $env:7Z_HOME +$SEVENZ_HOME = $env:SEVENZ_HOME $RSYNC_HOME = $env:RSYNC_HOME $USERPROFILE = $env:USERPROFILE -# Since shell-scripting doesn't like number-initial variables, we convert it to a friendlier name. -$env:SEVEN_Z_HOME=$7Z_HOME - # PowerShell fun: `--` parameters will break a PowerShell command when not escaped in some form. # We can build the command in string form first and then execute the string, fortunately. $cmd = '"C:\Program Files\Git\bin\bash.exe" --init-file "c:\Program Files\Git\etc\profile" -l ./ci.sh prepare:downloads.keyman.com' diff --git a/web/ci.sh b/web/ci.sh index f3f70eef93..58c5ade3d3 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -154,14 +154,14 @@ function prepare_downloads_keyman_com_action() { mkdir -p "${UPLOAD_PATH}" - # On Windows, we use 7-zip (SEVEN_Z_HOME env var). On other platforms, we use zip. + # On Windows, we use 7-zip (SEVENZ_HOME env var). On other platforms, we use zip. COMPRESS_CMD= COMPRESS_ADD= - # Marc's preference; use $SEVEN_Z_HOME and have the BAs set up with THAT as an env var. - if [[ ! -z "${SEVEN_Z_HOME+x}" ]]; then - COMPRESS_CMD="${SEVEN_Z_HOME}/7z" + # Marc's preference; use $SEVENZ_HOME and have the BAs set up with THAT as an env var. + if [[ ! -z "${SEVENZ_HOME+x}" ]]; then + COMPRESS_CMD="${SEVENZ_HOME}/7z" COMPRESS_ADD="a -bd -bb0 -r" # add, hide progress, log level 0, recursive fi From 4b2b9c1a84beef45054882d4ea347f44ae27fcda Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Wed, 11 Jun 2025 22:09:48 +1000 Subject: [PATCH 013/262] fix(windows): early return when download meta data invalid is invalid or the version is not newer then currently installed --- .../desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas | 1 + 1 file changed, 1 insertion(+) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index a8291ee325..6892098212 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -783,6 +783,7 @@ begin // Return to Idle state bucStateContext.RemoveCachedFiles; ChangeState(IdleState); + Exit; end; DownloadResult := DownloadUpdatesBackground; From 0d23efeae31b5714e7b2dbe53b28705b99989035 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Thu, 12 Jun 2025 15:31:22 +1000 Subject: [PATCH 014/262] fix(windows): if only keyboards return to idle There will be no `firstrun` for kmshell.exe if it is a keyboard only install. We can therefore return to the idle state in the current process --- .../kmshell/main/Keyman.System.UpdateStateMachine.pas | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas index 4e2afbe89f..97fc4b0a48 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -1196,6 +1196,12 @@ begin if hasKeymanInstall then begin DoInstallKeyman; + end + else + begin + // There is no Keyman installer we can return to the IdleState + bucStateContext.RemoveCachedFiles; + ChangeState(IdleState); end; end; From 3754cac89b70c462e124779364ee598cbe09689f Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 13 Jun 2025 09:32:04 +1000 Subject: [PATCH 015/262] maint(resources): only automatically merge keyman-server PRs with automerge label Prevents PRs from keyman-server from being merged unless they already have the 'automerge' label. This supports the upcoming epic-master-merge infrastructure, where we would like to automatically approve the PRs but allow maintainers to merge when ready. We cannot specify required status checks at this point, because we don't necessarily know which status checks will be required for a given PR. A future improvement would be to define a new status check which verifies that all the expected builds for a given PR have passed, and then make that into a required status check for all master,beta,stable-x.y,epic/ branches. This status check would need to be updated automatically whenever another status check completes. Probably should be implemented as a GHA. In the future, we could move to using GitHub's auto-merge functionality, and not use the current auto-merge action but that's a much bigger change. Also, upgraded auto-approve to v4.0.0 to remove node deprecation warnings. --- .github/workflows/auto-merge-keyman-server-pr.yml | 4 ++-- resources/build/ci/pull-requests.inc.sh | 2 +- resources/build/increment-version.sh | 8 +++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/auto-merge-keyman-server-pr.yml b/.github/workflows/auto-merge-keyman-server-pr.yml index 6b93e716fa..0c920c2650 100644 --- a/.github/workflows/auto-merge-keyman-server-pr.yml +++ b/.github/workflows/auto-merge-keyman-server-pr.yml @@ -17,12 +17,12 @@ jobs: if: ${{ github.repository == 'keymanapp/keyman' && github.actor == 'keyman-server' && startsWith(github.event.pull_request.title, 'auto:') }} steps: - name: auto approve PR from keyman-server - uses: hmarr/auto-approve-action@v3 + uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: auto merge PR from keyman-server uses: "pascalgn/automerge-action@7854d3bd607dccdaf0b2c134b699a812c8960213" env: GITHUB_TOKEN: "${{ secrets.AUTOINC_GITHUB_TOKEN }}" - MERGE_LABELS: "" + MERGE_LABELS: "automerge" MERGE_FORKS: false diff --git a/resources/build/ci/pull-requests.inc.sh b/resources/build/ci/pull-requests.inc.sh index 6959f826d3..2e2f3a587f 100644 --- a/resources/build/ci/pull-requests.inc.sh +++ b/resources/build/ci/pull-requests.inc.sh @@ -107,7 +107,7 @@ function ci_open_pull_request { git push origin "$branch" builder_echo "Push complete" - hub pull-request --force --message "$commit_message" --labels auto + hub pull-request --force --message "$commit_message" --labels auto,automerge builder_echo "Pull request created" git switch "$current_branch" diff --git a/resources/build/increment-version.sh b/resources/build/increment-version.sh index f1d15c6a7d..228499d4ae 100755 --- a/resources/build/increment-version.sh +++ b/resources/build/increment-version.sh @@ -166,7 +166,7 @@ if [ "$action" == "commit" ]; then cd "$KEYMAN_ROOT" git checkout "$branch" - hub pull-request -f --no-edit -b $base -l auto + hub pull-request -f --no-edit -b $base -l auto,automerge # # If we are on a stable-x.y branch, then we also want to merge changes to @@ -174,6 +174,8 @@ if [ "$action" == "commit" ]; then # beta changes will be merged to master periodically anyway. # if [[ "$base" =~ ^stable-[0-9]+\.[0-9]+$ ]]; then + # TODO: why is this not happening? + git switch master # In order to avoid potential git conflicts, we run the history collater @@ -190,8 +192,8 @@ if [ "$action" == "commit" ]; then git switch -c "chore/version-$base-$NEWVERSION-master-history" master git add HISTORY.md git commit -m "$message (history cherry-pick to master)" - # TODO: once we are sure this is stable, add `-l auto` to get the "auto:" - # label + # TODO: once we are sure this is stable, add `-l auto,automerge` to get + # the "auto" label hub pull-request -fp --no-edit fi From 26c39fd3bbc11618beac93b5736e36f5fb722ddf Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Fri, 13 Jun 2025 10:57:06 +1000 Subject: [PATCH 016/262] feat(windows): review comments Co-authored-by: Eberhard Beilharz --- .../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 4e2afbe89f..c305bfc17a 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -722,7 +722,7 @@ begin finally CheckForUpdates.Free; end; - if Result <> wucFailure then + if Result = wucFailure then begin KL.Log('UpdateAvailableState.HandleCheck CheckForUpdates not successful: '+ GetEnumName(TypeInfo(TUpdateState), Ord(Result))); From 895883cae01001f4c420215b37d0a67d6b890474 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 13 Jun 2025 12:45:06 +0200 Subject: [PATCH 017/262] maint(developer,web): use braces for variables in powershell scripts This changes variable references in the powershell scripts to use braces (`${FOO}` instead of `$FOO`). This solves the problem where powershell considers the colon in the rsync command as being part of a variable. This should fix release builds for Web and Developer. Also commented some unused variables. Follow-up-of: #14167,#14168 Part-of: #13399 Test-bot: skip --- .../download-symbol-server-index.ps1 | 30 ++--- .../developer/publish-new-symbols.ps1 | 10 +- .../publish-to-downloads-keyman-com.ps1 | 122 +++++++++--------- .../teamcity/web/zip-and-upload-artifacts.ps1 | 40 +++--- 4 files changed, 101 insertions(+), 101 deletions(-) diff --git a/resources/teamcity/developer/download-symbol-server-index.ps1 b/resources/teamcity/developer/download-symbol-server-index.ps1 index ffa0d97ba1..0f2f948bb5 100644 --- a/resources/teamcity/developer/download-symbol-server-index.ps1 +++ b/resources/teamcity/developer/download-symbol-server-index.ps1 @@ -25,39 +25,39 @@ $rsync_args = @( '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security '--stats', # show statistics for log - "--rsync-path='$RSYNC_PATH'", # path on remote server - "--rsh=$RSYNC_HOME\ssh -i $USERPROFILE\.ssh\id_rsa -o UserKnownHostsFile=$USERPROFILE\.ssh\known_hosts", # use ssh - "$RSYNC_USER@$RSYNC_HOST:$RSYNC_ROOT/windows/symbols/000admin/lastid.txt", # target server + path + "--rsync-path='${RSYNC_PATH}'", # path on remote server + "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh + "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/symbols/000admin/lastid.txt", # target server + path "." # download the whole symbols 000Admin folder ) -& $RSYNC_HOME\rsync.exe $rsync_args -if ($LASTEXITCODE -ne 0) { throw "Exit code is $LASTEXITCODE" } +& ${RSYNC_HOME}\rsync.exe ${rsync_args} +if (${LASTEXITCODE} -ne 0) { throw "Exit code is ${LASTEXITCODE}" } $rsync_args = @( '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security '--stats', # show statistics for log - "--rsync-path='$RSYNC_PATH'", # path on remote server - "--rsh=$RSYNC_HOME\ssh -i $USERPROFILE\.ssh\id_rsa -o UserKnownHostsFile=$USERPROFILE\.ssh\known_hosts", # use ssh - "$RSYNC_USER@$RSYNC_HOST:$RSYNC_ROOT/windows/symbols/000admin/history.txt", # target server + path + "--rsync-path='${RSYNC_PATH}'", # path on remote server + "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh + "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/symbols/000admin/history.txt", # target server + path "." # download the whole symbols 000Admin folder ) -& $RSYNC_HOME\rsync.exe $rsync_args -if ($LASTEXITCODE -ne 0) { throw "Exit code is $LASTEXITCODE" } +& ${RSYNC_HOME}\rsync.exe ${rsync_args} +if (${LASTEXITCODE} -ne 0) { throw "Exit code is ${LASTEXITCODE}" } $rsync_args = @( '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security '--stats', # show statistics for log - "--rsync-path='$RSYNC_PATH'", # path on remote server - "--rsh=$RSYNC_HOME\ssh -i $USERPROFILE\.ssh\id_rsa -o UserKnownHostsFile=$USERPROFILE\.ssh\known_hosts", # use ssh - "$RSYNC_USER@$RSYNC_HOST:$RSYNC_ROOT/windows/symbols/000admin/server.txt", # target server + path + "--rsync-path='${RSYNC_PATH}'", # path on remote server + "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh + "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/symbols/000admin/server.txt", # target server + path "." # download the whole symbols 000Admin folder ) -& $RSYNC_HOME\rsync.exe $rsync_args -if ($LASTEXITCODE -ne 0) { throw "Exit code is $LASTEXITCODE" } +& ${RSYNC_HOME}\rsync.exe ${rsync_args} +if (${LASTEXITCODE} -ne 0) { throw "Exit code is ${LASTEXITCODE}" } # EOF diff --git a/resources/teamcity/developer/publish-new-symbols.ps1 b/resources/teamcity/developer/publish-new-symbols.ps1 index 40c43df1c8..23da29869d 100644 --- a/resources/teamcity/developer/publish-new-symbols.ps1 +++ b/resources/teamcity/developer/publish-new-symbols.ps1 @@ -27,13 +27,13 @@ $rsync_args = @( '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security '--stats', # show statistics for log - "--rsync-path='$RSYNC_PATH'", # path on remote server - "--rsh=$RSYNC_HOME\ssh -i $USERPROFILE\.ssh\id_rsa -o UserKnownHostsFile=$USERPROFILE\.ssh\known_hosts", # use ssh + "--rsync-path='${RSYNC_PATH}'", # path on remote server + "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh ".", # upload the whole symbols folder - "$RSYNC_USER@$RSYNC_HOST:$RSYNC_ROOT/windows/symbols/" # target server + path + "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/symbols/" # target server + path ) -& $RSYNC_HOME\rsync.exe $rsync_args -if ($LASTEXITCODE -ne 0) { throw "Exit code is $LASTEXITCODE" } +& ${RSYNC_HOME}\rsync.exe ${rsync_args} +if (${LASTEXITCODE} -ne 0) { throw "Exit code is ${LASTEXITCODE}" } # EOF diff --git a/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 b/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 index 884e098afa..a2d9c00b6c 100644 --- a/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 +++ b/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 @@ -4,28 +4,28 @@ $ErrorActionPreference = "Stop" $tier = Get-Content ..\TIER.md $build_number = Get-Content ..\VERSION.md -$build_counter = $build_number -replace "^\d+\.\d+\.(\d+)$", '$1' -$msi_major_version = $build_number -replace "^(\d+)\.(\d+)\.(\d+)$", '$1$2' +$build_counter = ${build_number} -replace "^\d+\.\d+\.(\d+)$", '$1' +#$msi_major_version = ${build_number} -replace "^(\d+)\.(\d+)\.(\d+)$", '$1$2' -if ( $tier -eq "alpha" -or $tier -eq "beta") { - $version_tag = $tier - $version_with_tag = $build_number # we may go with $build_number + "-" + $version_tag in the future one day +if ( ${tier} -eq "alpha" -or ${tier} -eq "beta") { + $version_tag = ${tier} + $version_with_tag = ${build_number} # we may go with ${build_number} + "-" + ${version_tag} in the future one day } else { $version_tag = "" - $version_with_tag = $build_number + $version_with_tag = ${build_number} } -$upload_path = "upload\$build_number" -$keyboards_path = "$upload_path\keyboards" +$upload_path = "upload\${build_number}" +$keyboards_path = "${upload_path}\keyboards" # Keyman Developer installers -$kmcomp_zip = "kmcomp-$version_with_tag.zip" -$keyman_developer_exe = "keymandeveloper-$version_with_tag.exe" +$kmcomp_zip = "kmcomp-${version_with_tag}.zip" +$keyman_developer_exe = "keymandeveloper-${version_with_tag}.exe" -$setup_exe = "setup.exe" +#$setup_exe = "setup.exe" # Debug files -$debug_zip = "debug-$build_number.zip" +$debug_zip = "debug-${build_number}.zip" $7Z_HOME = $env:7Z_HOME $RSYNC_HOME = $env:RSYNC_HOME @@ -43,35 +43,35 @@ $USERPROFILE = $env:USERPROFILE # Preparation # -if((Test-Path $upload_path) -ne 0) { - Remove-Item -Path $upload_path -Recurse +if((Test-Path ${upload_path}) -ne 0) { + Remove-Item -Path ${upload_path} -Recurse } -mkdir $upload_path +mkdir ${upload_path} # # Build Keyman Compiler WINE archive # -if((Test-Path ..\release\$kmcomp_zip) -ne 0) { - copy ..\release\$kmcomp_zip ..\$upload_path\$kmcomp_zip +if((Test-Path ..\release\${kmcomp_zip}) -ne 0) { + copy ..\release\${kmcomp_zip} ..\${upload_path}\${kmcomp_zip} } else { cd bin if((Test-Path ..\..\common\schemas\keyboard_info\keyboard_info.source.json) -ne 0) { # Keyman versions through -16.0 copy ..\..\common\schemas\keyboard_info\keyboard_info.source.json . copy ..\..\common\schemas\keyboard_info\keyboard_info.distribution.json . - & "$7Z_HOME\7z.exe" a -bd -bb0 ..\$upload_path\$kmcomp_zip kmcomp.exe kmcmpdll.dll kmcomp.x64.exe kmcmpdll.x64.dll kmconvert.exe keyboard_info.source.json keyboard_info.distribution.json xml\layoutbuilder\*.keyman-touch-layout projects\ + & "${7Z_HOME}\7z.exe" a -bd -bb0 ..\${upload_path}\${kmcomp_zip} kmcomp.exe kmcmpdll.dll kmcomp.x64.exe kmcmpdll.x64.dll kmconvert.exe keyboard_info.source.json keyboard_info.distribution.json xml\layoutbuilder\*.keyman-touch-layout projects\ # Add Keyman Developer Server to the archive (15.0 late alpha - after 171) if((Test-Path ..\src\server\build) -ne 0) { copy ..\src\server\build\ server\ -Recurse - & "$7Z_HOME\7z.exe" a -bd -bb0 ..\$upload_path\$kmcomp_zip server\ + & "${7Z_HOME}\7z.exe" a -bd -bb0 ..\${upload_path}\${kmcomp_zip} server\ } } else { # Keyman versions 17.0+; note, use npm install for most modules copy ..\..\common\schemas\keyboard_info\keyboard_info.schema.json . - & "$7Z_HOME\7z.exe" a -bd -bb0 ..\$upload_path\$kmcomp_zip kmconvert.exe keyboard_info.schema.json xml\layoutbuilder\*.keyman-touch-layout projects\ server\ + & "${7Z_HOME}\7z.exe" a -bd -bb0 ..\${upload_path}\${kmcomp_zip} kmconvert.exe keyboard_info.schema.json xml\layoutbuilder\*.keyman-touch-layout projects\ server\ } cd .. } @@ -80,58 +80,58 @@ if((Test-Path ..\release\$kmcomp_zip) -ne 0) { # Copy source files # -copy release\$build_number\$keyman_developer_exe $upload_path\$keyman_developer_exe +copy release\${build_number}\${keyman_developer_exe} ${upload_path}\${keyman_developer_exe} # # Construct keyman_developer.download_info # -$hash = get-filehash $upload_path\$keyman_developer_exe -Algorithm MD5 +$hash = get-filehash ${upload_path}\${keyman_developer_exe} -Algorithm MD5 $keyman_developer_download_info = @" { "name": "Keyman Developer", - "version": "$version_with_tag", + "version": "${version_with_tag}", "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", "platform": "win", - "stability": "$tier", - "file": "keymandeveloper-$version_with_tag.exe", - "md5": "$($hash.Hash)", + "stability": "${tier}", + "file": "keymandeveloper-${version_with_tag}.exe", + "md5": "$(${hash}.Hash)", "type": "exe", - "build": "$build_counter" + "build": "${build_counter}" } "@ -[System.IO.File]::WriteAllLines("$pwd\$upload_path\$keyman_developer_exe.download_info", $keyman_developer_download_info) +[System.IO.File]::WriteAllLines("${pwd}\${upload_path}\${keyman_developer_exe}.download_info", ${keyman_developer_download_info}) # # Construct kmcomp.download_info # -$hash = get-filehash $upload_path\$kmcomp_zip -Algorithm MD5 +$hash = get-filehash ${upload_path}\${kmcomp_zip} -Algorithm MD5 $kmcomp_download_info = @" { "name": "Keyman Developer Command-Line Compiler", - "version": "$version_with_tag", + "version": "${version_with_tag}", "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", "platform": "win", - "stability": "$tier", - "file": "kmcomp-$version_with_tag.zip", - "md5": "$($hash.Hash)", + "stability": "${tier}", + "file": "kmcomp-${version_with_tag}.zip", + "md5": "$(${hash}.Hash)", "type": "zip", - "build": "$build_counter" + "build": "${build_counter}" } "@ -[System.IO.File]::WriteAllLines("$pwd\$upload_path\$kmcomp_zip.download_info", $kmcomp_download_info) +[System.IO.File]::WriteAllLines("${pwd}\${upload_path}\${kmcomp_zip}.download_info", ${kmcomp_download_info}) # # Copy common/test/keyboards/*/build/*.kmp to keyboards/ # if((Test-Path ..\common\test\keyboards) -ne 0) { - mkdir $keyboards_path - Copy-Item ..\common\test\keyboards\*\build\*.kmp $keyboards_path\ + mkdir ${keyboards_path} + Copy-Item ..\common\test\keyboards\*\build\*.kmp ${keyboards_path}\ } # @@ -144,17 +144,17 @@ $rsync_args = @( '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security '--stats', # show statistics for log - "--rsync-path='$RSYNC_PATH'", # path on remote server - "--rsh=$RSYNC_HOME\ssh -i $USERPROFILE\.ssh\id_rsa -o UserKnownHostsFile=$USERPROFILE\.ssh\known_hosts", # use ssh - "$build_number", # upload the whole build folder - "$RSYNC_USER@$RSYNC_HOST:$RSYNC_ROOT/developer/$tier/" # target server + path + "--rsync-path='${RSYNC_PATH}'", # path on remote server + "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh + "${build_number}", # upload the whole build folder + "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/developer/${tier}/" # target server + path ) -# Write-Output "rsync parameters:" $rsync_args +# Write-Output "rsync parameters:" ${rsync_args} cd upload -& $RSYNC_HOME\rsync.exe $rsync_args -if( $LASTEXITCODE -ne 0 ) { +& ${RSYNC_HOME}\rsync.exe ${rsync_args} +if( ${LASTEXITCODE} -ne 0 ) { Write-Output "rsync failed." exit 99 } @@ -164,34 +164,34 @@ cd .. # DEBUG.ZIP ################# -if((Test-Path release\$build_number\$debug_zip) -ne 0) { +if((Test-Path release\${build_number}\${debug_zip}) -ne 0) { # # Copy source files # - copy release\$build_number\$debug_zip $upload_path\$debug_zip + copy release\${build_number}\${debug_zip} ${upload_path}\${debug_zip} # # Construct debug-.download_info # - $hash = get-filehash $upload_path\$debug_zip -Algorithm MD5 + $hash = get-filehash ${upload_path}\${debug_zip} -Algorithm MD5 - $debug_zip_download_info = @" + ${debug_zip}_download_info = @" { "name": "Keyman Developer debug files", - "version": "$version_with_tag", + "version": "${version_with_tag}", "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", "platform": "win", - "stability": "$tier", - "file": "debug-$build_number.zip", - "md5": "$($hash.Hash)", + "stability": "${tier}", + "file": "debug-${build_number}.zip", + "md5": "$(${hash}.Hash)", "type": "zip", - "build": "$build_counter" + "build": "${build_counter}" } "@ - [System.IO.File]::WriteAllLines("$pwd\$upload_path\$debug_zip.download_info", $debug_zip_download_info) + [System.IO.File]::WriteAllLines("${pwd}\${upload_path}\${debug_zip}.download_info", ${debug_zip}_download_info) } # @@ -204,17 +204,17 @@ $rsync_args = @( '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security '--stats', # show statistics for log - "--rsync-path='$RSYNC_PATH'", # path on remote server - "--rsh=$RSYNC_HOME\ssh -i $USERPROFILE\.ssh\id_rsa -o UserKnownHostsFile=$USERPROFILE\.ssh\known_hosts", # use ssh - "$build_number", # upload the whole build folder - "$RSYNC_USER@$RSYNC_HOST:$RSYNC_ROOT/developer/$tier/" # target server + path + "--rsync-path='${RSYNC_PATH}'", # path on remote server + "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh + "${build_number}", # upload the whole build folder + "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/developer/${tier}/" # target server + path ) -# Write-Output "rsync parameters:" $rsync_args +# Write-Output "rsync parameters:" ${rsync_args} cd upload -& $RSYNC_HOME\rsync.exe $rsync_args -if( $LASTEXITCODE -ne 0 ) { +& ${RSYNC_HOME}\rsync.exe ${rsync_args} +if( ${LASTEXITCODE} -ne 0 ) { Write-Output "rsync failed." exit 99 } diff --git a/resources/teamcity/web/zip-and-upload-artifacts.ps1 b/resources/teamcity/web/zip-and-upload-artifacts.ps1 index 91d1cb9667..942f051e99 100755 --- a/resources/teamcity/web/zip-and-upload-artifacts.ps1 +++ b/resources/teamcity/web/zip-and-upload-artifacts.ps1 @@ -8,10 +8,10 @@ $ErrorActionPreference = "Stop"; $tier = Get-Content ..\TIER.md $build_number = Get-Content ..\VERSION.md -$build_counter = $build_number -replace "^\d+\.\d+\.(\d+)$", '$1' +$build_counter = ${build_number} -replace "^\d+\.\d+\.(\d+)$", '$1' -$upload_path = "build\upload\$build_number" -$zip = "$upload_path\keymanweb-$build_number.zip" +$upload_path = "build\upload\${build_number}" +$zip = "${upload_path}\keymanweb-${build_number}.zip" $7Z_HOME = $env:7Z_HOME $RSYNC_HOME = $env:RSYNC_HOME @@ -27,9 +27,9 @@ $env:SEVEN_Z_HOME=$7Z_HOME # PowerShell fun: `--` parameters will break a PowerShell command when not escaped in some form. # We can build the command in string form first and then execute the string, fortunately. $cmd = '"C:\Program Files\Git\bin\bash.exe" --init-file "c:\Program Files\Git\etc\profile" -l ./ci.sh prepare:downloads.keyman.com' -cmd /c $cmd +cmd /c ${cmd} -$hash = get-filehash $zip -Algorithm MD5 +$hash = get-filehash ${zip} -Algorithm MD5 # # Construct .build_info @@ -38,20 +38,20 @@ $hash = get-filehash $zip -Algorithm MD5 $download_info = @" { "name": "KeymanWeb", - "version": "$build_number", + "version": "${build_number}", "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", "platform": "web", - "stability": "$tier", - "file": "keymanweb-$build_number.zip", - "md5": "$($hash.Hash)", + "stability": "${tier}", + "file": "keymanweb-${build_number}.zip", + "md5": "$(${hash}.Hash)", "type": "zip", - "build": "$build_counter" + "build": "${build_counter}" } "@ # WriteAllLines is needed to avoid BOM -[System.IO.File]::WriteAllLines("$pwd\$upload_path\keymanweb-$build_number.zip.download_info", $download_info) -# $download_info | Out-File $upload_path\keymanweb-$build_number.zip.download_info -Encoding utf8 +[System.IO.File]::WriteAllLines("${pwd}\${upload_path}\keymanweb-${build_number}.zip.download_info", ${download_info}) +# ${download_info} | Out-File ${upload_path}\keymanweb-${build_number}.zip.download_info -Encoding utf8 # # Upload with rsync to downloads.keyman.com @@ -63,17 +63,17 @@ $rsync_args = @( '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security '--stats', # show statistics for log - "--rsync-path='$RSYNC_PATH'", # path on remote server - "--rsh=$RSYNC_HOME\ssh -i $USERPROFILE\.ssh\id_rsa -o UserKnownHostsFile=$USERPROFILE\.ssh\known_hosts", # use ssh - "$build_number", # upload the whole build folder - "$RSYNC_USER@$RSYNC_HOST:$RSYNC_ROOT/web/$tier/" # target server + path + "--rsync-path='${RSYNC_PATH}'", # path on remote server + "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh + "${build_number}", # upload the whole build folder + "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/web/${tier}/" # target server + path ) -# Write-Output "rsync parameters:" $rsync_args +# Write-Output "rsync parameters:" ${rsync_args} -cd "$upload_path\.." -& $RSYNC_HOME\rsync.exe $rsync_args -if ($LASTEXITCODE -ne 0) { throw "Exit code is $LASTEXITCODE" } +cd "${upload_path}\.." +& ${RSYNC_HOME}\rsync.exe ${rsync_args} +if (${LASTEXITCODE} -ne 0) { throw "Exit code is ${LASTEXITCODE}" } cd .. # EOF From 7868bec04c665914ab56a1c6fd8c5786b23e91d6 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 13 Jun 2025 18:39:39 +0200 Subject: [PATCH 018/262] maint(developer): fix developer release build Follow-up-of: #14184 Part-of: #13399 Test-bot: skip --- .../teamcity/developer/publish-to-downloads-keyman-com.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 b/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 index a2d9c00b6c..4c9b0a7cb9 100644 --- a/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 +++ b/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 @@ -177,7 +177,7 @@ if((Test-Path release\${build_number}\${debug_zip}) -ne 0) { $hash = get-filehash ${upload_path}\${debug_zip} -Algorithm MD5 - ${debug_zip}_download_info = @" + $debug_zip_download_info = @" { "name": "Keyman Developer debug files", "version": "${version_with_tag}", @@ -191,7 +191,7 @@ if((Test-Path release\${build_number}\${debug_zip}) -ne 0) { } "@ - [System.IO.File]::WriteAllLines("${pwd}\${upload_path}\${debug_zip}.download_info", ${debug_zip}_download_info) + [System.IO.File]::WriteAllLines("${pwd}\${upload_path}\${debug_zip}.download_info", ${debug_zip_download_info}) } # From bc485856c9e4c1fccdaad6cfdc46833fc23f32df Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 13 Jun 2025 19:13:11 +0200 Subject: [PATCH 019/262] maint(windows): move TC configuration for Keyman Windows to scripts Part-of: #13399 Test-bot: skip --- .../developer/keyman-developer-release.sh | 31 +- ...ish-developer-to-downloads-keyman-com.ps1} | 27 +- .../download-symbol-server-index.ps1 | 0 .../publish-new-symbols.ps1 | 0 resources/teamcity/includes/tc-helpers.inc.sh | 16 + resources/teamcity/includes/tc-windows.inc.sh | 29 ++ resources/teamcity/web/keyman-web-release.sh | 15 +- .../windows/keyman-windows-release.sh | 83 ++++++ .../teamcity/windows/keyman-windows-test.sh | 54 ++++ ...ublish-windows-to-downloads-keyman-com.ps1 | 279 ++++++++++++++++++ .../teamcity/windows/windows-actions.inc.sh | 22 ++ 11 files changed, 495 insertions(+), 61 deletions(-) rename resources/teamcity/developer/{publish-to-downloads-keyman-com.ps1 => publish-developer-to-downloads-keyman-com.ps1} (82%) rename resources/teamcity/{developer => includes}/download-symbol-server-index.ps1 (100%) rename resources/teamcity/{developer => includes}/publish-new-symbols.ps1 (100%) create mode 100644 resources/teamcity/includes/tc-windows.inc.sh create mode 100755 resources/teamcity/windows/keyman-windows-release.sh create mode 100755 resources/teamcity/windows/keyman-windows-test.sh create mode 100644 resources/teamcity/windows/publish-windows-to-downloads-keyman-com.ps1 create mode 100755 resources/teamcity/windows/windows-actions.inc.sh diff --git a/resources/teamcity/developer/keyman-developer-release.sh b/resources/teamcity/developer/keyman-developer-release.sh index 5aa8608a23..5e969bddf6 100755 --- a/resources/teamcity/developer/keyman-developer-release.sh +++ b/resources/teamcity/developer/keyman-developer-release.sh @@ -14,6 +14,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" # shellcheck disable=SC2154 . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-windows.inc.sh" ################################ Main script ################################ @@ -57,37 +58,13 @@ function _publish_sentry() { builder_echo end "publish sentry" success "Finished publishing debug information files to Sentry" } -function _download_symbol_server_index() { - # Download symbol server index from symbol server - builder_echo start "download symbol server index" "Downloading symbol server index" - - cd "${KEYMAN_ROOT}/.." - # shellcheck disable=SC2154 - powershell -NonInteractive -ExecutionPolicy Bypass -File "${THIS_SCRIPT_PATH}/download-symbol-server-index.ps1" - cd "${KEYMAN_ROOT}/developer/src" - - builder_echo end "download symbol server index" success "Finished downloading symbol server index" -} - -function _publish_new_symbols() { - # Publish new symbols to symbol server - builder_echo start "publish new symbols" "Publishing new symbols to symbol server" - - cd "${KEYMAN_ROOT}/../symbols" - # shellcheck disable=SC2154 - powershell -NonInteractive -ExecutionPolicy Bypass -File "${THIS_SCRIPT_PATH}/publish-new-symbols.ps1" - cd "${KEYMAN_ROOT}/developer/src" - - builder_echo end "publish new symbols" success "Finished publishing new symbols to symbol server" -} - function _publish_to_downloads_keyman_com() { # Publish to downloads.keyman.com builder_echo start "publish to downloads.keyman.com" "Publishing release to downloads.keyman.com" cd "${KEYMAN_ROOT}/developer" # shellcheck disable=SC2154 - powershell -NonInteractive -ExecutionPolicy Bypass -File "${THIS_SCRIPT_PATH}/publish-to-downloads-keyman-com.ps1" + powershell -NonInteractive -ExecutionPolicy Bypass -File "${THIS_SCRIPT_PATH}/publish-developer-to-downloads-keyman-com.ps1" cd "${KEYMAN_ROOT}/developer/src" builder_echo end "publish to downloads.keyman.com" success "Finished publishing release to downloads.keyman.com" @@ -121,8 +98,8 @@ function publish_action() { export RSYNC_ROOT _publish_sentry - _download_symbol_server_index - _publish_new_symbols + download_symbol_server_index + publish_new_symbols _publish_to_downloads_keyman_com _publish_api_documentation } diff --git a/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 b/resources/teamcity/developer/publish-developer-to-downloads-keyman-com.ps1 similarity index 82% rename from resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 rename to resources/teamcity/developer/publish-developer-to-downloads-keyman-com.ps1 index 4c9b0a7cb9..af97495a1b 100644 --- a/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 +++ b/resources/teamcity/developer/publish-developer-to-downloads-keyman-com.ps1 @@ -5,13 +5,11 @@ $ErrorActionPreference = "Stop" $tier = Get-Content ..\TIER.md $build_number = Get-Content ..\VERSION.md $build_counter = ${build_number} -replace "^\d+\.\d+\.(\d+)$", '$1' -#$msi_major_version = ${build_number} -replace "^(\d+)\.(\d+)\.(\d+)$", '$1$2' if ( ${tier} -eq "alpha" -or ${tier} -eq "beta") { - $version_tag = ${tier} + # $version_tag = ${tier} $version_with_tag = ${build_number} # we may go with ${build_number} + "-" + ${version_tag} in the future one day } else { - $version_tag = "" $version_with_tag = ${build_number} } @@ -22,8 +20,6 @@ $keyboards_path = "${upload_path}\keyboards" $kmcomp_zip = "kmcomp-${version_with_tag}.zip" $keyman_developer_exe = "keymandeveloper-${version_with_tag}.exe" -#$setup_exe = "setup.exe" - # Debug files $debug_zip = "debug-${build_number}.zip" @@ -54,25 +50,14 @@ mkdir ${upload_path} # if((Test-Path ..\release\${kmcomp_zip}) -ne 0) { + # REVIEW: I guess this is the path for Keyman 18+. In which case we + # can remove the else block copy ..\release\${kmcomp_zip} ..\${upload_path}\${kmcomp_zip} } else { cd bin - if((Test-Path ..\..\common\schemas\keyboard_info\keyboard_info.source.json) -ne 0) { - # Keyman versions through -16.0 - copy ..\..\common\schemas\keyboard_info\keyboard_info.source.json . - copy ..\..\common\schemas\keyboard_info\keyboard_info.distribution.json . - & "${7Z_HOME}\7z.exe" a -bd -bb0 ..\${upload_path}\${kmcomp_zip} kmcomp.exe kmcmpdll.dll kmcomp.x64.exe kmcmpdll.x64.dll kmconvert.exe keyboard_info.source.json keyboard_info.distribution.json xml\layoutbuilder\*.keyman-touch-layout projects\ - - # Add Keyman Developer Server to the archive (15.0 late alpha - after 171) - if((Test-Path ..\src\server\build) -ne 0) { - copy ..\src\server\build\ server\ -Recurse - & "${7Z_HOME}\7z.exe" a -bd -bb0 ..\${upload_path}\${kmcomp_zip} server\ - } - } else { - # Keyman versions 17.0+; note, use npm install for most modules - copy ..\..\common\schemas\keyboard_info\keyboard_info.schema.json . - & "${7Z_HOME}\7z.exe" a -bd -bb0 ..\${upload_path}\${kmcomp_zip} kmconvert.exe keyboard_info.schema.json xml\layoutbuilder\*.keyman-touch-layout projects\ server\ - } + # Keyman versions 17.0+; note, use npm install for most modules + copy ..\..\common\schemas\keyboard_info\keyboard_info.schema.json . + & "${7Z_HOME}\7z.exe" a -bd -bb0 ..\${upload_path}\${kmcomp_zip} kmconvert.exe keyboard_info.schema.json xml\layoutbuilder\*.keyman-touch-layout projects\ server\ cd .. } diff --git a/resources/teamcity/developer/download-symbol-server-index.ps1 b/resources/teamcity/includes/download-symbol-server-index.ps1 similarity index 100% rename from resources/teamcity/developer/download-symbol-server-index.ps1 rename to resources/teamcity/includes/download-symbol-server-index.ps1 diff --git a/resources/teamcity/developer/publish-new-symbols.ps1 b/resources/teamcity/includes/publish-new-symbols.ps1 similarity index 100% rename from resources/teamcity/developer/publish-new-symbols.ps1 rename to resources/teamcity/includes/publish-new-symbols.ps1 diff --git a/resources/teamcity/includes/tc-helpers.inc.sh b/resources/teamcity/includes/tc-helpers.inc.sh index 5170f3c25e..5ea9c7e70f 100644 --- a/resources/teamcity/includes/tc-helpers.inc.sh +++ b/resources/teamcity/includes/tc-helpers.inc.sh @@ -66,3 +66,19 @@ set_variables_for_emscripten() { export EMSCRIPTEN_BASE="${EMSCRIPTEN_BASE:-${HOME}/emsdk/upstream/emscripten}" export KEYMAN_USE_EMSDK=1 } + +upload_help() { + local PRODUCT=$1 + local PRODUCT_PATH=$2 + builder_echo start "upload help" "Uploading new ${PRODUCT} help to help.keyman.com" + + export HELP_KEYMAN_COM="${HELP_KEYMAN_COM:-${KEYMAN_ROOT}/../help.keyman.com}" + + ( + # shellcheck disable=SC2164 + cd "${KEYMAN_ROOT}/resources/build" + "${KEYMAN_ROOT}/resources/build/help-keyman-com.sh" "${PRODUCT_PATH}" + ) + + builder_echo end "upload help" success "Finished uploading new ${PRODUCT} help to help.keyman.com" +} diff --git a/resources/teamcity/includes/tc-windows.inc.sh b/resources/teamcity/includes/tc-windows.inc.sh new file mode 100644 index 0000000000..6f2cb8b337 --- /dev/null +++ b/resources/teamcity/includes/tc-windows.inc.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. + +download_symbol_server_index() { + # Download symbol server index from symbol server + builder_echo start "download symbol server index" "Downloading symbol server index" + + ( + # shellcheck disable=SC2164 + cd "${KEYMAN_ROOT}/.." + # shellcheck disable=SC2154 + powershell -NonInteractive -ExecutionPolicy Bypass -File "${THIS_SCRIPT_PATH}/../includes/download-symbol-server-index.ps1" + ) + + builder_echo end "download symbol server index" success "Finished downloading symbol server index" +} + +publish_new_symbols() { + # Publish new symbols to symbol server + builder_echo start "publish new symbols" "Publishing new symbols to symbol server" + + ( + cd "${KEYMAN_ROOT}/../symbols" + # shellcheck disable=SC2154 + powershell -NonInteractive -ExecutionPolicy Bypass -File "${THIS_SCRIPT_PATH}/../includes/publish-new-symbols.ps1" + ) + + builder_echo end "publish new symbols" success "Finished publishing new symbols to symbol server" +} diff --git a/resources/teamcity/web/keyman-web-release.sh b/resources/teamcity/web/keyman-web-release.sh index d2aa1c378b..bd1accb786 100755 --- a/resources/teamcity/web/keyman-web-release.sh +++ b/resources/teamcity/web/keyman-web-release.sh @@ -21,7 +21,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ################################ Main script ################################ builder_describe \ - "Run tests for native KeymanWeb" \ + "Build and publish KeymanWeb" \ "all run all actions" \ "configure install dependencies" \ "build build Web + embedded" \ @@ -59,17 +59,6 @@ function _zip_and_upload_artifacts() { builder_echo end "zip and upload artifacts" success "Finished zipping and uploading artifacts" } -function _upload_help() { - builder_echo start "upload help" "Uploading new Keyman for Web help to help.keyman.com" - - export HELP_KEYMAN_COM="${HELP_KEYMAN_COM:-${KEYMAN_ROOT}/../help.keyman.com}" - cd "${KEYMAN_ROOT}/resources/build" - "${KEYMAN_ROOT}/resources/build/help-keyman-com.sh" web - cd "${KEYMAN_ROOT}/web" - - builder_echo end "upload help" success "Finished uploading new Keyman for Web help to help.keyman.com" -} - function publish_web_action() { builder_echo start publish "Publishing KeymanWeb release" @@ -89,7 +78,7 @@ function publish_web_action() { _push_release_to_skeymancom _zip_and_upload_artifacts - _upload_help + upload_help "Keyman for Web" web builder_echo end publish success "Finished publishing KeymanWeb release" } diff --git a/resources/teamcity/windows/keyman-windows-release.sh b/resources/teamcity/windows/keyman-windows-release.sh new file mode 100755 index 0000000000..8cc5d62a0b --- /dev/null +++ b/resources/teamcity/windows/keyman-windows-release.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. +# +# TC build script to build release of Keyman for Windows. + +# shellcheck disable=SC2164 +# shellcheck disable=SC1091 + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +# shellcheck disable=SC2154 +. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-windows.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/windows/windows-actions.inc.sh" + +################################ Main script ################################ + +builder_describe \ + "Build and publish Keyman for Windows" \ + "all run all actions" \ + "build build Keyman for Windows Windows" \ + "test run Keyman for Windows tests" \ + "publish publish release" \ + "--rsync-path=RSYNC_PATH rsync path on remote server" \ + "--rsync-user=RSYNC_USER rsync user on remote server" \ + "--rsync-host=RSYNC_HOST rsync host on remote server" \ + "--rsync-root=RSYNC_ROOT rsync root on remote server" \ + "--help.keyman.com=HELP_KEYMAN_COM path to help.keyman.com repository" + +builder_parse "$@" + +cd "${KEYMAN_ROOT}/windows/src" + +if ! is_windows; then + builder_echo error "This script is intended to be run on Windows only." + exit 1 +fi + +function _publish_to_downloads_keyman_com() { + # Publish to downloads.keyman.com + builder_echo start "publish to downloads.keyman.com" "Publishing release to downloads.keyman.com" + + ( + cd "${KEYMAN_ROOT}/windows" + # shellcheck disable=SC2154 + powershell -NonInteractive -ExecutionPolicy Bypass -File "${THIS_SCRIPT_PATH}/publish-windows-to-downloads-keyman-com.ps1" + ) + + builder_echo end "publish to downloads.keyman.com" success "Finished publishing release to downloads.keyman.com" +} + +function windows_publish_action() { + builder_echo start "publish windows" "Publishing Keyman for Windows" + + export RSYNC_PATH + export RSYNC_USER + export RSYNC_HOST + export RSYNC_ROOT + + call_windows_publish + windows_upload_symbols_to_sentry + download_symbol_server_index + publish_new_symbols + _publish_to_downloads_keyman_com + upload_help "Keyman for Windows" windows + builder_echo end "publish windows" success "Finished publishing Keyman for Windows" +} + +if builder_has_action all; then + windows_build_action + windows_test_action + windows_publish_action +else + builder_run_action build windows_build_action + builder_run_action test windows_test_action + builder_run_action publish windows_publish_action +fi + diff --git a/resources/teamcity/windows/keyman-windows-test.sh b/resources/teamcity/windows/keyman-windows-test.sh new file mode 100755 index 0000000000..86664224f0 --- /dev/null +++ b/resources/teamcity/windows/keyman-windows-test.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. +# +# TC build script for Keyman Windows/Test + +# shellcheck disable=SC2164 +# shellcheck disable=SC1091 + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +# shellcheck disable=SC2154 +. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/windows/windows-actions.inc.sh" + +################################ Main script ################################ + +builder_describe \ + "Run tests for Keyman for Windows" \ + "all run all actions" \ + "build build Keyman for Windows" \ + "test run Keyman for Windows tests" \ + "publish prepare files for distribution, publish symbols, and build installer module" + +builder_parse "$@" + +cd "${KEYMAN_ROOT}/windows/src" + +if ! is_windows; then + builder_echo error "This script is intended to be run on Windows only." + exit 1 +fi + +function windows_publish_action() { + builder_echo start "publish windows" "Publishing Keyman for Windows" + call_windows_publish + windows_upload_symbols_to_sentry + builder_echo end "publish windows" success "Finished publishing Keyman for Windows" +} + +if builder_has_action all; then + windows_build_action + windows_test_action + windows_publish_action +else + builder_run_action build windows_build_action + builder_run_action test windows_test_action + builder_run_action publish windows_publish_action +fi + diff --git a/resources/teamcity/windows/publish-windows-to-downloads-keyman-com.ps1 b/resources/teamcity/windows/publish-windows-to-downloads-keyman-com.ps1 new file mode 100644 index 0000000000..3703557b7a --- /dev/null +++ b/resources/teamcity/windows/publish-windows-to-downloads-keyman-com.ps1 @@ -0,0 +1,279 @@ +# Keyman is copyright (C) SIL Global. MIT License. + +$ErrorActionPreference = "Stop" + +$tier = Get-Content ..\TIER.md +$build_number = Get-Content ..\VERSION.md +$build_counter = ${build_number} -replace "^\d+\.\d+\.(\d+)$", '$1' +$msi_major_version = ${build_number} -replace "^(\d+)\.(\d+)\.(\d+)$", '$1$2' + +if ( ${tier} -eq "alpha" -or ${tier} -eq "beta") { + # $version_tag = ${tier} + $version_with_tag = ${build_number} # we may go with ${build_number} + "-" + $version_tag in the future one day +} +else { + $version_with_tag = ${build_number} +} + +$upload_path = "upload\${build_number}" + +# Keyman Desktop installer +$keyman_desktop_exe = "keyman-${version_with_tag}.exe" + +# Files used for building bundled keyboard + Keyman installers (placed in Desktop folder) +if (${msi_major_version} -eq "100") { + $keyman_desktop_msi = "keymandesktop${msi_major_version}.msi" +} +else { + $keyman_desktop_msi = "keymandesktop.msi" +} +$setup_exe = "setup.exe" +$setup_redist_zip = "setup-redist.zip" + +$firstvoices_msi = "firstvoices.msi" +$firstvoices_exe = "firstvoices-${version_with_tag}.exe" + +# Debug files +$debug_zip = "debug-${build_number}.zip" + +$7Z_HOME = $env:7Z_HOME +$RSYNC_HOME = $env:RSYNC_HOME +$RSYNC_PATH = $env:RSYNC_PATH +$RSYNC_USER = $env:RSYNC_USER +$RSYNC_HOST = $env:RSYNC_HOST +$RSYNC_ROOT = $env:RSYNC_ROOT +$USERPROFILE = $env:USERPROFILE + +########################################################################################### +## Desktop upload +########################################################################################### + +# +# Preparation +# + +if ((Test-Path ${upload_path}) -ne 0) { + Remove-Item -Path ${upload_path} -Recurse +} + +mkdir ${upload_path} + +# +# Copy source files +# + +copy release\${build_number}\${keyman_desktop_exe} ${upload_path}\${keyman_desktop_exe} +copy release\${build_number}\${keyman_desktop_msi} ${upload_path}\${keyman_desktop_msi} +copy release\${build_number}\${setup_exe} ${upload_path}\${setup_exe} +if ((Test-Path release\${build_number}\${setup_redist_zip}) -ne 0) { + # Keyman 16+ setup-redist.zip is an archive of the setup.exe installer for + # Keyman for Windows, unsigned for use in bundling scenarios. + copy release\${build_number}\${setup_redist_zip} ${upload_path}\${setup_redist_zip} +} +# +# Construct keyman_desktop.download_info +# + +$hash = get-filehash ${upload_path}\${keyman_desktop_exe} -Algorithm MD5 + +$keyman_desktop_download_info = @" +{ + "name": "Keyman for Windows", + "version": "${version_with_tag}", + "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", + "platform": "win", + "stability": "${tier}", + "file": "keyman-${version_with_tag}.exe", + "md5": "$(${hash}.Hash)", + "type": "exe", + "build": "${build_counter}" +} +"@ + +[System.IO.File]::WriteAllLines("$pwd\${upload_path}\${keyman_desktop_exe}.download_info", ${keyman_desktop_download_info}) + +# +# Construct keyman_desktop_msi.download_info +# + +$hash = get-filehash ${upload_path}\${keyman_desktop_msi} -Algorithm MD5 + +$keyman_desktop_msi_download_info = @" +{ + "name": "Keyman for Windows MSI installer", + "version": "${version_with_tag}", + "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", + "platform": "win", + "stability": "${tier}", + "file": "${keyman_desktop_msi}", + "md5": "$(${hash}.Hash)", + "type": "msi", + "build": "${build_counter}" +} +"@ + +[System.IO.File]::WriteAllLines("$pwd\${upload_path}\${keyman_desktop_msi}.download_info", ${keyman_desktop_msi_download_info}) + +# +# Construct setup.exe.download_info +# + +$hash = get-filehash ${upload_path}\${setup_exe} -Algorithm MD5 + +$setup_exe_download_info = @" +{ + "name": "Keyman for Windows setup bootstrap", + "version": "${version_with_tag}", + "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", + "platform": "win", + "stability": "${tier}", + "file": "${setup_exe}", + "md5": "$(${hash}.Hash)", + "type": "exe", + "build": "${build_counter}" +} +"@ + +[System.IO.File]::WriteAllLines("$pwd\${upload_path}\${setup_exe}.download_info", ${setup_exe_download_info}) + +if ((Test-Path release\${build_number}\${setup_redist_zip}) -ne 0) { + + # + # Construct setup-redist.zip.download_info + # + + $hash = get-filehash ${upload_path}\${setup_redist_zip} -Algorithm MD5 + + $setup_redist_zip_download_info = @" +{ + "name": "Keyman for Windows setup bootstrap (unsigned for bundling)", + "version": "${version_with_tag}", + "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", + "platform": "win", + "stability": "${tier}", + "file": "${setup_redist_zip}", + "md5": "$(${hash}.Hash)", + "type": "zip", + "build": "${build_counter}" +} +"@ + + [System.IO.File]::WriteAllLines("$pwd\${upload_path}\${setup_redist_zip}.download_info", ${setup_redist_zip_download_info}) +} + +########################################################################################### +## FirstVoices upload +########################################################################################### + +# +# Copy source files +# + +copy release\${build_number}\${firstvoices_exe} ${upload_path}\${firstvoices_exe} +copy release\${build_number}\${firstvoices_msi} ${upload_path}\${firstvoices_msi} + +# +# Construct firstvoices.download_info +# + +$hash = get-filehash ${upload_path}\${firstvoices_exe} -Algorithm MD5 + +$firstvoices_download_info = @" +{ + "name": "FirstVoices Keyboards", + "version": "${version_with_tag}", + "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", + "platform": "win", + "stability": "${tier}", + "file": "${firstvoices_exe}", + "md5": "$(${hash}.Hash)", + "type": "exe", + "build": "${build_counter}" +} +"@ + +[System.IO.File]::WriteAllLines("$pwd\${upload_path}\${firstvoices_exe}.download_info", ${firstvoices_download_info}) + +# +# Construct firstvoices_msi.download_info +# + +$hash = get-filehash ${upload_path}\${firstvoices_msi} -Algorithm MD5 + +$firstvoices_msi_download_info = @" +{ + "name": "FirstVoices Keyboards MSI installer", + "version": "${version_with_tag}", + "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", + "platform": "win", + "stability": "${tier}", + "file": "${firstvoices_msi}", + "md5": "$(${hash}.Hash)", + "type": "msi", + "build": "${build_counter}" +} +"@ + +[System.IO.File]::WriteAllLines("$pwd\${upload_path}\${firstvoices_msi}.download_info", ${firstvoices_msi_download_info}) + +################# +# DEBUG.ZIP +################# + +if ((Test-Path release\${build_number}\${debug_zip}) -ne 0) { + # + # Copy source files + # + + copy release\${build_number}\${debug_zip} ${upload_path}\${debug_zip} + + # + # Construct debug-.download_info + # + + $hash = get-filehash ${upload_path}\${debug_zip} -Algorithm MD5 + + $debug_zip_download_info = @" +{ + "name": "Keyman Desktop and Keyman Developer debug files", + "version": "${version_with_tag}", + "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", + "platform": "win", + "stability": "${tier}", + "file": "debug-${build_number}.zip", + "md5": "$(${hash}.Hash)", + "type": "zip", + "build": "${build_counter}" +} +"@ + + [System.IO.File]::WriteAllLines("$pwd\${upload_path}\${debug_zip}.download_info", ${debug_zip_download_info}) +} + +# +# Upload with rsync to downloads.keyman.com +# (rsync requires that we are in the upload folder to get folders in +# sync correctly; it is possible to resolve this but easier to just cd.) +# + +$rsync_args = @( + '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions + '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security + '--stats', # show statistics for log + "--rsync-path='${RSYNC_PATH}'", # path on remote server + "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh + "${build_number}", # upload the whole build folder + "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/${tier}/" # target server + path +) + +# Write-Output "rsync parameters:" ${rsync_args} + +cd upload +& ${RSYNC_HOME}\rsync.exe ${rsync_args} +if ( ${LASTEXITCODE} -ne 0 ) { + Write-Output "rsync failed." + exit 99 +} +cd .. + +# EOF diff --git a/resources/teamcity/windows/windows-actions.inc.sh b/resources/teamcity/windows/windows-actions.inc.sh new file mode 100755 index 0000000000..1bb3fb7c86 --- /dev/null +++ b/resources/teamcity/windows/windows-actions.inc.sh @@ -0,0 +1,22 @@ +# Keyman is copyright (C) SIL Global. MIT License. + +windows_build_action() { + builder_echo start "build windows" "Building Keyman for Windows" + # shellcheck disable=SC2154 + "${KEYMAN_ROOT}/windows/build.sh" configure build + builder_echo end "build windows" success "Finished building Keyman for Windows" +} + +windows_test_action() { + builder_echo start "test windows" "Running Keyman for Windows tests" + "${KEYMAN_ROOT}/windows/build.sh" test + builder_echo end "test windows" success "Finished running Keyman for Windows tests" +} + +call_windows_publish() { + "${KEYMAN_ROOT}/windows/build.sh" publish +} + +windows_upload_symbols_to_sentry() { + "${KEYMAN_ROOT}/windows/src/buildtools/sentry-upload-difs.sh" +} From 5004db9625e49c067c940a74003eef394f7d4147 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Fri, 13 Jun 2025 14:01:44 -0400 Subject: [PATCH 020/262] auto: increment master version to 19.0.65 --- HISTORY.md | 6 ++++++ VERSION.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index daf9a20257..efd38c4375 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,11 @@ # Keyman Version History +## 19.0.64 alpha 2025-06-13 + +* fix(windows): fix download failing when cache.json missing (#14095) +* maint(resources): only automatically merge keyman-server PRs with automerge label (#14171) +* maint(developer,web): use braces for variables in powershell scripts (#14184) + ## 19.0.63 alpha 2025-06-12 * maint(web): add some more arguments for web release build script (#14167) diff --git a/VERSION.md b/VERSION.md index efcfb4b252..976addf6eb 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.64 \ No newline at end of file +19.0.65 \ No newline at end of file From 860a1c1aff8a330b2b289af5b4873f326075653a Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Fri, 13 Jun 2025 22:01:52 +0200 Subject: [PATCH 021/262] maint(developer,web): use double quotes inside the string again However, we have to use double quotes inside of the string as well so that it gets treated as a command with parameters instead of a single command with spaces. Follow-up-of: #14168 Part-of: #13399 Test-bot: skip --- .../teamcity/developer/download-symbol-server-index.ps1 | 6 +++--- resources/teamcity/developer/publish-new-symbols.ps1 | 2 +- .../teamcity/developer/publish-to-downloads-keyman-com.ps1 | 4 ++-- resources/teamcity/web/zip-and-upload-artifacts.ps1 | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/resources/teamcity/developer/download-symbol-server-index.ps1 b/resources/teamcity/developer/download-symbol-server-index.ps1 index 0f2f948bb5..5e84be2c05 100644 --- a/resources/teamcity/developer/download-symbol-server-index.ps1 +++ b/resources/teamcity/developer/download-symbol-server-index.ps1 @@ -25,7 +25,7 @@ $rsync_args = @( '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security '--stats', # show statistics for log - "--rsync-path='${RSYNC_PATH}'", # path on remote server + "--rsync-path=""${RSYNC_PATH}""", # path on remote server "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/symbols/000admin/lastid.txt", # target server + path "." # download the whole symbols 000Admin folder @@ -38,7 +38,7 @@ $rsync_args = @( '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security '--stats', # show statistics for log - "--rsync-path='${RSYNC_PATH}'", # path on remote server + "--rsync-path=""${RSYNC_PATH}""", # path on remote server "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/symbols/000admin/history.txt", # target server + path "." # download the whole symbols 000Admin folder @@ -51,7 +51,7 @@ $rsync_args = @( '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security '--stats', # show statistics for log - "--rsync-path='${RSYNC_PATH}'", # path on remote server + "--rsync-path=""${RSYNC_PATH}""", # path on remote server "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/symbols/000admin/server.txt", # target server + path "." # download the whole symbols 000Admin folder diff --git a/resources/teamcity/developer/publish-new-symbols.ps1 b/resources/teamcity/developer/publish-new-symbols.ps1 index 23da29869d..7fab9e1280 100644 --- a/resources/teamcity/developer/publish-new-symbols.ps1 +++ b/resources/teamcity/developer/publish-new-symbols.ps1 @@ -27,7 +27,7 @@ $rsync_args = @( '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security '--stats', # show statistics for log - "--rsync-path='${RSYNC_PATH}'", # path on remote server + "--rsync-path=""${RSYNC_PATH}""", # path on remote server "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh ".", # upload the whole symbols folder "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/symbols/" # target server + path diff --git a/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 b/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 index a2d9c00b6c..ef9b3dec19 100644 --- a/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 +++ b/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 @@ -144,7 +144,7 @@ $rsync_args = @( '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security '--stats', # show statistics for log - "--rsync-path='${RSYNC_PATH}'", # path on remote server + "--rsync-path=""${RSYNC_PATH}""", # path on remote server "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh "${build_number}", # upload the whole build folder "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/developer/${tier}/" # target server + path @@ -204,7 +204,7 @@ $rsync_args = @( '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security '--stats', # show statistics for log - "--rsync-path='${RSYNC_PATH}'", # path on remote server + "--rsync-path=""${RSYNC_PATH}""", # path on remote server "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh "${build_number}", # upload the whole build folder "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/developer/${tier}/" # target server + path diff --git a/resources/teamcity/web/zip-and-upload-artifacts.ps1 b/resources/teamcity/web/zip-and-upload-artifacts.ps1 index 942f051e99..2506f5658a 100755 --- a/resources/teamcity/web/zip-and-upload-artifacts.ps1 +++ b/resources/teamcity/web/zip-and-upload-artifacts.ps1 @@ -63,7 +63,7 @@ $rsync_args = @( '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security '--stats', # show statistics for log - "--rsync-path='${RSYNC_PATH}'", # path on remote server + "--rsync-path=""${RSYNC_PATH}""", # path on remote server "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh "${build_number}", # upload the whole build folder "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/web/${tier}/" # target server + path From ee6958a8963589ae9b106fe54743ed6d09e3c76c Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 13 Jun 2025 16:10:15 -0500 Subject: [PATCH 022/262] feat(developer): test fixes for line numbers in layr compiler Fixes #13940 --- .../test/fixtures/sections/layr/invalid-missing-layer.xml | 2 +- developer/src/kmc-ldml/test/layr.tests.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/developer/src/kmc-ldml/test/fixtures/sections/layr/invalid-missing-layer.xml b/developer/src/kmc-ldml/test/fixtures/sections/layr/invalid-missing-layer.xml index 211961d616..4525675fe9 100644 --- a/developer/src/kmc-ldml/test/fixtures/sections/layr/invalid-missing-layer.xml +++ b/developer/src/kmc-ldml/test/fixtures/sections/layr/invalid-missing-layer.xml @@ -8,7 +8,7 @@ - + diff --git a/developer/src/kmc-ldml/test/layr.tests.ts b/developer/src/kmc-ldml/test/layr.tests.ts index a1f3c5e184..64e4a3d373 100644 --- a/developer/src/kmc-ldml/test/layr.tests.ts +++ b/developer/src/kmc-ldml/test/layr.tests.ts @@ -108,13 +108,13 @@ describe('layr', function () { { // missing layer element subpath: 'sections/layr/invalid-missing-layer.xml', - errors: [LdmlCompilerMessages.Error_MustBeAtLeastOneLayerElement(withOffset(258))], + errors: [LdmlCompilerMessages.Error_MustBeAtLeastOneLayerElement(withOffset(40))], retainOffsetInMessages: true, }, { // missing layer element subpath: 'sections/layr/invalid-missing-layer2.xml', - errors: [LdmlCompilerMessages.Error_MustBeAtLeastOneLayerElement()], + errors: [LdmlCompilerMessages.Error_MustBeAtLeastOneLayerElement(withOffset(40))], retainOffsetInMessages: true, }, { From 9896fb878fc4f8d9a4bebf55c4d09a65ca6b5bfd Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 14 Jun 2025 07:29:31 +1000 Subject: [PATCH 023/262] maint(common): pass buildLevel to TeamCity / GHA --- resources/build/trigger-builds.inc.sh | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/resources/build/trigger-builds.inc.sh b/resources/build/trigger-builds.inc.sh index 44427170a3..aa07a30089 100644 --- a/resources/build/trigger-builds.inc.sh +++ b/resources/build/trigger-builds.inc.sh @@ -45,18 +45,18 @@ function triggerTeamCityBuild() { local TEAMCITY_VCS_ID="$4" local TEAMCITY_BRANCH_NAME="$5" - local GIT_OID=`git rev-parse HEAD` local TEAMCITY_SERVER=https://build.palaso.org - local command + local commandProperties="" + local commandLastChanges= - # TODO: add buildLevel parameter - - if $isTestBuild; then - command="" - else - command="" + if ! $isTestBuild; then + local GIT_OID=`git rev-parse HEAD` + commandLastChanges="" fi + + local command="$commandLastChanges$commandProperties" + echo "TeamCity Build Command: $command" # adjust indentation for output of curl @@ -85,7 +85,6 @@ function triggerGitHubActionsBuild() { local BUILD_LEVEL="$2" local GITHUB_ACTION="$3" local GIT_BRANCH="$4" - # TODO: support BUILD_LEVEL local GIT_BASE_BRANCH="${GIT_BRANCH}" local GIT_USER="keyman-server" local GIT_BUILD_SHA GIT_BASE_REF JSON @@ -97,7 +96,8 @@ function triggerGitHubActionsBuild() { 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 != stable-* ]] && [[ $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')" GIT_EVENT_TYPE="${GITHUB_ACTION}: PR #${GIT_BRANCH}" @@ -106,6 +106,7 @@ function triggerGitHubActionsBuild() { GIT_BASE_REF="$(echo "$JSON" | $JQ -r '.base.sha')" GIT_BRANCH="PR-${GIT_BRANCH}" else + # another branch: stable-x.y, beta, or master GIT_BUILD_SHA="$(git rev-parse "refs/heads/${GIT_BRANCH}")" GIT_BASE_REF="$(git rev-parse "${GIT_BUILD_SHA}^")" GIT_EVENT_TYPE="${GITHUB_ACTION}: ${GIT_BRANCH}" @@ -119,7 +120,8 @@ function triggerGitHubActionsBuild() { \"baseBranch\": \"$GIT_BASE_BRANCH\", \ \"baseRef\": \"$GIT_BASE_REF\", \ \"user\": \"$GIT_USER\", \ - \"isTestBuild\": \"$IS_TEST_BUILD\" \ + \"isTestBuild\": \"$IS_TEST_BUILD\", \ + \"buildLevel\": \"$BUILD_LEVEL\" \ }}" echo "GitHub Action Data: $DATA" From d4fd05f4707a1a2dbc2bc45b3646d63d0e248fd6 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 14 Jun 2025 07:29:49 +1000 Subject: [PATCH 024/262] docs(common): add build-bot documentation --- docs/build-bot.md | 136 +++++++++++++++++++++ resources/build/trigger-definitions.inc.sh | 7 +- 2 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 docs/build-bot.md diff --git a/docs/build-bot.md b/docs/build-bot.md new file mode 100644 index 0000000000..8f06e7d75f --- /dev/null +++ b/docs/build-bot.md @@ -0,0 +1,136 @@ +# Controlling builds in CI with the Keyman build bot + +The Keyman CI test infrastructure can be controlled by use of a `Build-bot:` +commit trailer, or a PR body `Build-bot:` trailer. This allows you to specify +what is built for any given platform. The primary purpose of the build bot is to +reduce the build agent load, but it can also be used to ensure that specific +artifacts are available for the purposes of testing. + +The Keyman build bot is only available for test builds on pull requests. It is +not used for any other builds, either test builds on target branches (master, +beta, stable-x.y), or for release builds. + +The default set of platform builds is determined by the files touched in the +pull request; see /resources/build/trigger-definitions.inc.sh. The default build +level for this set is 'build' (see [Build Level] section). This is known as the +'build set'. + +You may choose to increase or decrease what is built -- for example, for a +documentation-only change, you may decide that nothing needs to be built at all, +with: + +``` +Build-bot: skip +``` + +Or you may want to ensure that an artifact is built for Windows: + +``` +Build-bot: release windows +``` + +## Controlling the build bot with trailers + +The build bot respects commit trailers and trailers in the PR body. The commands +are cumulative and applied in order; the Build-bot trailer in the PR body is +applied after any trailers in commit messages. + +If no platform is specified, then the command applies to all platforms in the +current build set, overriding any previous Build-bot commands. Any platforms not +in the current build set will remain 'skipped'. + +If a platform is specified, then the command will apply only to that platform, +and the platform will be added to the build set if not already present (and +thus, subsequent Build-bot commands will be applied to this new platform as +well). + +It is important to note that the Build-bot trailers are read when builds are +triggered, which happens within 2 minutes of a PR being opened or commits being +pushed. Editing the PR body after the build trigger has run will have no effect +on existing builds, until another commit is pushed, or the test build trigger is +run manually from TeamCity. Thus, do not rely on editing the PR body after PR +creation to control the build bot; either include your build bot trailers in +commit messages, or include them in the PR body submitted when creating the PR. + +## Interactions with Test Bot + +The Build-bot has limited interactions with the Keyman test bot (aka +keymanapp-test-bot): if a 'User Testing' section is found in the PR body, the +default build level will be upgraded from 'build' to 'release'. + +Build bot trailers found in the commits or in the PR body are applied after the +test bot command. + +WARNING: The build bot does not check PR comments for Test-bot commands. + +## Example Build-bot interactions + +Say we have a PR that touches `/android/build.sh`. The default build set will be +`(android:build)`. + +The PR body has a User Testing section: `# User Testing`. The build set is +upgraded to: `(android:release)`. + +The first commit includes a Build-bot command: `Build-bot: build ios`. The build +set is now `(android:release ios:build)`. + +In a subsequent commit, the PR author decides that nothing needs to be built, +after all: `Build-bot: skip`. The build set is now `(android:skip ios:skip)`. +Note that other platforms are still 'skip' but not included in the build set. + +Finally, the PR author pushes another commit, with `Build-bot: release windows`. +The build set is now: `(android:skip ios:skip windows:release)`. + +## Build Level + +The build level specifies what we want to be run for a test build on a PR. + +There is a bit of nomenclature overlap with a 'release build'. A 'release' +buildLevel for a 'test build' is roughly equivalent to what is performed in a +'release build', however, a test build is only ever uploaded to test endpoints +(i.e. TestFlight, Play Store 'test' streams), and never to *.keyman.com, or to +other release distribution endpoints. + +The build level is controlled by the Build-bot commit trailer and PR body +Build-bot/Test-bot trailers. The default build level will be 'build'. + +For target branch builds, the build level will always be 'build', and Build-bot: +commit trailers are ignored. + +### 'skip' build level + +Don't do a build at all. This would be appropriate for documentation PRs, for +example, or changes only to comments in source files. + +### 'build' build level + +Build the code and run unit tests, but don't create artifacts. What this looks +like will vary from platform to platform, but there are some common things we +won't do: +* we won't upload artifacts to TeamCity or *.keyman.com +* we won't upload artifacts to any endpoint such as Play Store +* we won't upload symbols to Sentry +* we won't codesign + +However, we _will_ still build an installer (skipping codesigning), as this is +part of the 'build' buildLevel rather than the 'release' buildLevel. + +For a platform-specific example, on macOS we will also skip notarizing, as this +is costly and depends on external network resources, making it fragile. + +### 'release' build level + +A full test build will be run, roughly equivalent to a release build. We will do +the following steps: +* codesign (many platforms) and notarization (macOS) +* upload artifacts to TeamCity (all platforms) +* upload builds to TestFlight / Play Store 'test' endpoints (iOS/Android) +* upload symbols to Sentry + +For a 'release' build level: +* we won't upload artifacts to *.keyman.com +* we won't upload artifacts to any release endpoint such as Debian, + packages.sil.org, etc, or to the release areas for Play Store or App Store + + +[Build Level]: #Build_Level \ No newline at end of file diff --git a/resources/build/trigger-definitions.inc.sh b/resources/build/trigger-definitions.inc.sh index dc0338ffb8..db879b95e8 100644 --- a/resources/build/trigger-definitions.inc.sh +++ b/resources/build/trigger-definitions.inc.sh @@ -6,12 +6,15 @@ # # -# Build levels +# # Build levels +# +# See /docs/build-bot.md # readonly build_level_skip=skip readonly build_level_build=build readonly build_level_release=release +# TODO: future build_level_fulltest=fulltest --> do all expensive e2e tests as well as producing release artifacts readonly valid_build_levels="$build_level_skip|$build_level_build|$build_level_release" # @@ -20,6 +23,8 @@ readonly valid_build_levels="$build_level_skip|$build_level_build|$build_level_r available_platforms=(android common_web common_windows common_mac common_linux ios linux mac web windows developer) +# For test builds on master, beta, and stable-x.y branches, we always "build", +# not "release" -- that is, we don't produce artifacts declare -Ag main_branch_platform_build_levels for available_platforms_i in "${available_platforms[@]}"; do main_branch_platform_build_levels[$available_platforms_i]=$build_level_build From 00cc373a821534588c74dac14d0af94237b82528 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Fri, 13 Jun 2025 18:22:44 -0400 Subject: [PATCH 025/262] auto: increment master version to 19.0.66 --- HISTORY.md | 5 +++++ VERSION.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index efd38c4375..fb76100f0f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # Keyman Version History +## 19.0.65 alpha 2025-06-13 + +* maint(developer): fix developer release build (#14185) +* maint(developer,web): use double quotes inside `--rsync-path` again (#14194) + ## 19.0.64 alpha 2025-06-13 * fix(windows): fix download failing when cache.json missing (#14095) diff --git a/VERSION.md b/VERSION.md index 976addf6eb..31b9a57002 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.65 \ No newline at end of file +19.0.66 \ No newline at end of file From 11a558e926370253db37026c95b7ff5a6aa1e8fc Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 14 Jun 2025 15:22:15 +1000 Subject: [PATCH 026/262] maint(developer): support buildLevel Relates-to: 14125 Build-bot: build Test-bot: skip --- developer/src/kmc/build.sh | 13 ++++---- resources/build/win/environment.inc.sh | 20 +++++++++++++ resources/builder.inc.sh | 29 ++++++++++++++++++ .../keyman-developer-test-windows.sh | 30 +++++++++++-------- 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/developer/src/kmc/build.sh b/developer/src/kmc/build.sh index 15e8e18302..b7f06852c4 100755 --- a/developer/src/kmc/build.sh +++ b/developer/src/kmc/build.sh @@ -84,11 +84,14 @@ function do_bundle() { mkdir -p build/dist node build-bundler.js - sentry-cli sourcemaps inject \ - --org keyman \ - --project keyman-developer \ - --release "$KEYMAN_VERSION_GIT_TAG" \ - build/dist/ "${SOURCEMAP_PATHS[@]}" + if builder_is_ci_build && builder_is_ci_build_level_release; then + # Only inject sourcemaps for release builds + sentry-cli sourcemaps inject \ + --org keyman \ + --project keyman-developer \ + --release "$KEYMAN_VERSION_GIT_TAG" \ + build/dist/ "${SOURCEMAP_PATHS[@]}" + fi # Manually copy over kmcmplib module cp ../kmc-kmn/build/src/import/kmcmplib/wasm-host.wasm build/dist/ diff --git a/resources/build/win/environment.inc.sh b/resources/build/win/environment.inc.sh index 6da5ad039f..3ae6bb9269 100644 --- a/resources/build/win/environment.inc.sh +++ b/resources/build/win/environment.inc.sh @@ -73,6 +73,11 @@ run_in_delphi_env() { } sentrytool_delphiprep() { + if builder_is_ci_build && builder_is_ci_build_level_build; then + builder_echo "Skipping sentrytool_delphiprep - buildLevel=build: $@" + return 0 + fi + local EXE_PATH="$1" local DPR_PATH="$2" ( @@ -83,6 +88,11 @@ sentrytool_delphiprep() { } tds2dbg() { + if builder_is_ci_build && builder_is_ci_build_level_build; then + builder_echo "Skipping tds2dbg - buildLevel=build: $@" + return 0 + fi + "$TDS2DBG" "$@" } @@ -108,6 +118,11 @@ clean_windows_project_files() { } wrap-signcode() { + if builder_is_ci_build && builder_is_ci_build_level_build; then + builder_echo "Skipping code signing - buildLevel=build: $@" + return 0 + fi + # CI will usually pass in a full path for signtool.exe; for local builds we # will hopefully find what we want on the path already if [[ -z "${SIGNTOOL+x}" ]]; then @@ -117,6 +132,11 @@ wrap-signcode() { } wrap-symstore() { + if builder_is_ci_build && builder_is_ci_build_level_build; then + builder_echo "Skipping symstore - buildLevel=build" + return 0 + fi + if [[ -z "${KEYMAN_SYMSTOREPATH+x}" ]]; then builder_warn "\$KEYMAN_SYMSTOREPATH is not set. Skipping symstore for $@" return 0 diff --git a/resources/builder.inc.sh b/resources/builder.inc.sh index 25c9b5994c..7798eb2484 100755 --- a/resources/builder.inc.sh +++ b/resources/builder.inc.sh @@ -2221,6 +2221,35 @@ builder_is_ci_test_build() { return 1 } +# +# Returns 0 if current ci build is a release-level build. Do not use for non-ci +# builds. +# +builder_is_ci_build_level_release() { + if builder_is_ci_release_build; then + return 0 + fi + if [[ "$KEYMAN_BUILD_LEVEL" == release ]]; then + return 0 + fi + return 1 +} + +# +# Returns 0 if current ci build is a build-level build. Do not use for non-ci +# builds. +# +builder_is_ci_build_level_build() { + if builder_is_ci_release_build; then + return 1 + fi + if builder_is_ci_build_level_release; then + # KEYMAN_BUILD_LEVEL == release, i.e. not build + return 1 + fi + return 0 +} + ################################################################################ # Final initialization ################################################################################ diff --git a/resources/teamcity/developer/keyman-developer-test-windows.sh b/resources/teamcity/developer/keyman-developer-test-windows.sh index 75bb60b4ac..604acb86b6 100755 --- a/resources/teamcity/developer/keyman-developer-test-windows.sh +++ b/resources/teamcity/developer/keyman-developer-test-windows.sh @@ -19,12 +19,17 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" builder_describe \ "Build Keyman Developer on Windows" \ - "all run all actions" \ + "all run all actions (used by TeamCity build configuration)" \ "build build Keyman Developer and test keyboards" \ "publish publish debug information files to sentry" builder_parse "$@" +if ! is_windows; then + builder_echo error "This script is intended to be run on Windows only." + exit 1 +fi + # shellcheck disable=SC2154 cd "${KEYMAN_ROOT}/developer/src" @@ -34,11 +39,11 @@ function build_developer_action() { } function _build_developer() { - builder_echo start "build developer" "Building Keyman Developer" + builder_echo start "build developer" "Building and testing Keyman Developer" - ./build.sh configure build test api publish --dry-run + ./build.sh configure build test - builder_echo end "build developer" success "Finished building Keyman Developer" + builder_echo end "build developer" success "Finished building and testing Keyman Developer" } function _build_testkeyboards() { @@ -50,21 +55,22 @@ function _build_testkeyboards() { } function publish_sentry_action() { + builder_echo start "publish" "Dry-run publish and api" + ./build.sh api publish --dry-run + builder_echo end "publish" "Dry-run publish and api" + builder_echo start "publish sentry" "Publishing debug information files to Sentry" - + # TODO: move this into build.sh publish? re-scope --dry-run into build.sh? "${KEYMAN_ROOT}/developer/src/tools/sentry-upload-difs.sh" - builder_echo end "publish sentry" success "Finished publishing debug information files to Sentry" } -if ! is_windows; then - builder_echo error "This script is intended to be run on Windows only." - exit 1 -fi - if builder_has_action all; then + # TODO: control codesign by KEYMAN_BUILD_LEVEL build_developer_action - publish_sentry_action + if builder_is_ci_build_level_release; then + publish_sentry_action + fi else builder_run_action build build_developer_action builder_run_action publish publish_sentry_action From 870d37f56b489ef5067b4352a06505369d9038e0 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 16 Jun 2025 05:40:30 +1000 Subject: [PATCH 027/262] maint(developer): only copy .dbg/.pdb on release buildLevel --- developer/src/kmanalyze/build.sh | 2 +- developer/src/kmconvert/build.sh | 2 +- developer/src/kmdbrowserhost/build.sh | 4 +--- developer/src/kmdecomp/build.sh | 2 +- developer/src/samples/imsample/build.sh | 4 ++-- developer/src/setup/build.sh | 9 ++------- developer/src/tike/build.sh | 7 +++---- resources/build/win/environment.inc.sh | 8 ++++++++ windows/src/desktop/insthelp/build.sh | 2 +- windows/src/desktop/kmbrowserhost/build.sh | 2 +- windows/src/desktop/kmconfig/build.sh | 2 +- windows/src/desktop/kmshell/build.sh | 2 +- windows/src/desktop/setup/build.sh | 2 +- windows/src/engine/insthelper/build.sh | 2 +- windows/src/engine/keyman/build.sh | 2 +- windows/src/engine/keyman32/build.sh | 4 ++-- windows/src/engine/keymanx64/build.sh | 2 +- windows/src/engine/kmcomapi/build.sh | 2 +- windows/src/engine/kmrefresh/build.sh | 4 ++-- windows/src/engine/kmtip/build.sh | 4 ++-- windows/src/engine/mcompile/build.sh | 2 +- windows/src/engine/tsysinfo/build.sh | 2 +- windows/src/support/etl2log/build.sh | 2 +- windows/src/support/texteditor/build.sh | 4 ++-- 24 files changed, 39 insertions(+), 39 deletions(-) diff --git a/developer/src/kmanalyze/build.sh b/developer/src/kmanalyze/build.sh index 0e07044ffc..6f77aee381 100755 --- a/developer/src/kmanalyze/build.sh +++ b/developer/src/kmanalyze/build.sh @@ -32,7 +32,7 @@ function do_build() { build_version.res vs_msbuild kmanalyze.vcxproj //t:Build "//p:Platform=Win32" cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" - cp "$WIN32_TARGET_PATH/kmanalyze.pdb" "$DEVELOPER_DEBUGPATH" + if_release_build_level cp "$WIN32_TARGET_PATH/kmanalyze.pdb" "$DEVELOPER_DEBUGPATH" } function do_publish() { diff --git a/developer/src/kmconvert/build.sh b/developer/src/kmconvert/build.sh index 599379565f..4bd68423f1 100755 --- a/developer/src/kmconvert/build.sh +++ b/developer/src/kmconvert/build.sh @@ -33,7 +33,7 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" - cp "$WIN32_TARGET_PATH/kmconvert.dbg" "$DEVELOPER_DEBUGPATH" + if_release_build_level cp "$WIN32_TARGET_PATH/kmconvert.dbg" "$DEVELOPER_DEBUGPATH" rm -rf "$DEVELOPER_PROGRAM/projects/templates" mkdir -p "$DEVELOPER_PROGRAM/projects/templates" diff --git a/developer/src/kmdbrowserhost/build.sh b/developer/src/kmdbrowserhost/build.sh index b00a60735a..a7bb2d4449 100755 --- a/developer/src/kmdbrowserhost/build.sh +++ b/developer/src/kmdbrowserhost/build.sh @@ -32,9 +32,7 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" - if [[ -f "$WIN32_TARGET_PATH/kmdbrowserhost.dbg" ]]; then - cp "$WIN32_TARGET_PATH/kmdbrowserhost.dbg" "$DEVELOPER_DEBUGPATH" - fi + if_release_build_level cp "$WIN32_TARGET_PATH/kmdbrowserhost.dbg" "$DEVELOPER_DEBUGPATH" } function do_publish() { diff --git a/developer/src/kmdecomp/build.sh b/developer/src/kmdecomp/build.sh index a6b903fbc8..9922add612 100755 --- a/developer/src/kmdecomp/build.sh +++ b/developer/src/kmdecomp/build.sh @@ -33,7 +33,7 @@ function do_build() { build_version.res vs_msbuild kmdecomp.sln //t:Build "//p:Platform=Win32" cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" - cp "$WIN32_TARGET_PATH/kmdecomp.pdb" "$DEVELOPER_DEBUGPATH" + if_release_build_level cp "$WIN32_TARGET_PATH/kmdecomp.pdb" "$DEVELOPER_DEBUGPATH" } function do_publish() { diff --git a/developer/src/samples/imsample/build.sh b/developer/src/samples/imsample/build.sh index 2c0c402e28..4c7535ec1f 100755 --- a/developer/src/samples/imsample/build.sh +++ b/developer/src/samples/imsample/build.sh @@ -32,11 +32,11 @@ function do_build() { build_version.res vs_msbuild imsample.sln //t:Build "//p:Platform=Win32" cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" - cp "$WIN32_TARGET_PATH/imsample.pdb" "$DEVELOPER_DEBUGPATH" + if_release_build_level cp "$WIN32_TARGET_PATH/imsample.pdb" "$DEVELOPER_DEBUGPATH" vs_msbuild imsample.sln //t:Build "//p:Platform=x64" cp "$X64_TARGET" "$DEVELOPER_PROGRAM" - cp "$X64_TARGET_PATH/imsample.x64.pdb" "$DEVELOPER_DEBUGPATH" + if_release_build_level cp "$X64_TARGET_PATH/imsample.x64.pdb" "$DEVELOPER_DEBUGPATH" } # TODO diff --git a/developer/src/setup/build.sh b/developer/src/setup/build.sh index 5dfe102363..45e1816046 100755 --- a/developer/src/setup/build.sh +++ b/developer/src/setup/build.sh @@ -33,13 +33,8 @@ function do_build() { sentrytool_delphiprep "$WIN32_TARGET" setup.dpr tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" - if [[ -f "$WIN32_TARGET_PATH/setup.dbg" ]]; then - cp "$WIN32_TARGET_PATH/setup.dbg" "$DEVELOPER_DEBUGPATH/devsetup.dbg" - fi - rm -f "$WIN32_TARGET_PATH/devsetup.dbg" - if [[ -f "$WIN32_TARGET_PATH/setup.dbg" ]]; then - mv "$WIN32_TARGET_PATH/setup.dbg" "$WIN32_TARGET_PATH/devsetup.dbg" - fi + if_release_build_level cp "$WIN32_TARGET_PATH/setup.dbg" "$DEVELOPER_DEBUGPATH/devsetup.dbg" + if_release_build_level mv "$WIN32_TARGET_PATH/setup.dbg" "$WIN32_TARGET_PATH/devsetup.dbg" } function do_publish() { diff --git a/developer/src/tike/build.sh b/developer/src/tike/build.sh index d0e5800fc3..9dab71a904 100755 --- a/developer/src/tike/build.sh +++ b/developer/src/tike/build.sh @@ -76,11 +76,10 @@ function do_build() { cp kmlmp.cmd "$DEVELOPER_PROGRAM" cp kmc.cmd "$DEVELOPER_PROGRAM" cp "$KEYMAN_ROOT/core/build/x86/$TARGET_PATH/src/$KEYMANCORE_DLL" "$DEVELOPER_PROGRAM" - if [[ -f "$WIN32_TARGET_PATH/tike.dbg" ]]; then - cp "$WIN32_TARGET_PATH/tike.dbg" "$DEVELOPER_DEBUGPATH" - fi + if_release_build_level cp "$WIN32_TARGET_PATH/tike.dbg" "$DEVELOPER_DEBUGPATH" + cp "$KEYMAN_ROOT/core/build/x86/$TARGET_PATH/src/$KEYMANCORE_DLL" "$WIN32_TARGET_PATH" - cp "$KEYMAN_ROOT/core/build/x86/$TARGET_PATH/src/$KEYMANCORE_PDB" "$WIN32_TARGET_PATH" + if_release_build_level cp "$KEYMAN_ROOT/core/build/x86/$TARGET_PATH/src/$KEYMANCORE_PDB" "$WIN32_TARGET_PATH" cp "$KEYMAN_ROOT/common/windows/delphi/ext/sentry/sentry.dll" "$DEVELOPER_PROGRAM/" cp "$KEYMAN_ROOT/common/windows/delphi/ext/sentry/sentry.x64.dll" "$DEVELOPER_PROGRAM/" diff --git a/resources/build/win/environment.inc.sh b/resources/build/win/environment.inc.sh index 3ae6bb9269..5463a1af5e 100644 --- a/resources/build/win/environment.inc.sh +++ b/resources/build/win/environment.inc.sh @@ -87,6 +87,14 @@ sentrytool_delphiprep() { ) } +if_release_build_level() { + if builder_is_ci_build && builder_is_ci_build_level_build; then + builder_echo "Skipping - buildLevel=build: $@" + return 0 + fi + "$@" +} + tds2dbg() { if builder_is_ci_build && builder_is_ci_build_level_build; then builder_echo "Skipping tds2dbg - buildLevel=build: $@" diff --git a/windows/src/desktop/insthelp/build.sh b/windows/src/desktop/insthelp/build.sh index de21cb4014..86d94f9648 100755 --- a/windows/src/desktop/insthelp/build.sh +++ b/windows/src/desktop/insthelp/build.sh @@ -33,7 +33,7 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP" - cp "$WIN32_TARGET_PATH/insthelp.dbg" "$WINDOWS_DEBUGPATH_APP/insthelp.dbg" + if_release_build_level cp "$WIN32_TARGET_PATH/insthelp.dbg" "$WINDOWS_DEBUGPATH_APP/insthelp.dbg" } function do_publish() { diff --git a/windows/src/desktop/kmbrowserhost/build.sh b/windows/src/desktop/kmbrowserhost/build.sh index f716c29b74..56a0de331f 100755 --- a/windows/src/desktop/kmbrowserhost/build.sh +++ b/windows/src/desktop/kmbrowserhost/build.sh @@ -32,7 +32,7 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP" - cp "$WIN32_TARGET_PATH/kmbrowserhost.dbg" "$WINDOWS_DEBUGPATH_APP/kmbrowserhost.dbg" + if_release_build_level cp "$WIN32_TARGET_PATH/kmbrowserhost.dbg" "$WINDOWS_DEBUGPATH_APP/kmbrowserhost.dbg" } function do_publish() { diff --git a/windows/src/desktop/kmconfig/build.sh b/windows/src/desktop/kmconfig/build.sh index 8507f68ac5..704067ddc1 100755 --- a/windows/src/desktop/kmconfig/build.sh +++ b/windows/src/desktop/kmconfig/build.sh @@ -32,7 +32,7 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP" - cp "$WIN32_TARGET_PATH/kmconfig.dbg" "$WINDOWS_DEBUGPATH_APP/kmconfig.dbg" + if_release_build_level cp "$WIN32_TARGET_PATH/kmconfig.dbg" "$WINDOWS_DEBUGPATH_APP/kmconfig.dbg" } function do_publish() { diff --git a/windows/src/desktop/kmshell/build.sh b/windows/src/desktop/kmshell/build.sh index 3ce57b9088..0f3d7c9e69 100755 --- a/windows/src/desktop/kmshell/build.sh +++ b/windows/src/desktop/kmshell/build.sh @@ -39,7 +39,7 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP" - cp "$WIN32_TARGET_PATH/kmshell.dbg" "$WINDOWS_DEBUGPATH_APP/kmshell.dbg" + if_release_build_level cp "$WIN32_TARGET_PATH/kmshell.dbg" "$WINDOWS_DEBUGPATH_APP/kmshell.dbg" do_build_data } diff --git a/windows/src/desktop/setup/build.sh b/windows/src/desktop/setup/build.sh index bb72965c86..8415dd4734 100755 --- a/windows/src/desktop/setup/build.sh +++ b/windows/src/desktop/setup/build.sh @@ -43,7 +43,7 @@ function do_build() { cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP" # setup-redist.exe does not get signed as it is intended for a bundled installer cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP/setup-redist.exe" - cp "$WIN32_TARGET_PATH/setup.dbg" "$WINDOWS_DEBUGPATH_APP/setup.dbg" + if_release_build_level cp "$WIN32_TARGET_PATH/setup.dbg" "$WINDOWS_DEBUGPATH_APP/setup.dbg" } function do_build_debug_manifest() { diff --git a/windows/src/engine/insthelper/build.sh b/windows/src/engine/insthelper/build.sh index a31fc605fc..470b183748 100755 --- a/windows/src/engine/insthelper/build.sh +++ b/windows/src/engine/insthelper/build.sh @@ -31,7 +31,7 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - cp "$WIN32_TARGET_PATH/insthelper.dbg" "$WINDOWS_DEBUGPATH_ENGINE/insthelper.dbg" + if_release_build_level cp "$WIN32_TARGET_PATH/insthelper.dbg" "$WINDOWS_DEBUGPATH_ENGINE/insthelper.dbg" } function do_publish() { diff --git a/windows/src/engine/keyman/build.sh b/windows/src/engine/keyman/build.sh index 5f142c350c..adf924ee2a 100755 --- a/windows/src/engine/keyman/build.sh +++ b/windows/src/engine/keyman/build.sh @@ -42,7 +42,7 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - cp "$WIN32_TARGET_PATH/keyman.dbg" "$WINDOWS_DEBUGPATH_ENGINE/keyman.dbg" + if_release_build_level cp "$WIN32_TARGET_PATH/keyman.dbg" "$WINDOWS_DEBUGPATH_ENGINE/keyman.dbg" # Also copy sentry files here cp "$KEYMAN_ROOT/common/windows/delphi/ext/sentry/sentry.dll" \ diff --git a/windows/src/engine/keyman32/build.sh b/windows/src/engine/keyman32/build.sh index c452809958..47d77b718d 100755 --- a/windows/src/engine/keyman32/build.sh +++ b/windows/src/engine/keyman32/build.sh @@ -41,7 +41,7 @@ function do_build_x86() { build_version.res vs_msbuild keyman32.vcxproj //t:Build "//p:Platform=Win32" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - cp "$WIN32_TARGET_PATH/keyman32.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + if_release_build_level cp "$WIN32_TARGET_PATH/keyman32.pdb" "$WINDOWS_DEBUGPATH_ENGINE" } function do_build_x64() { @@ -49,7 +49,7 @@ function do_build_x64() { run_in_vs_env rc version64.rc vs_msbuild keyman32.vcxproj //t:Build "//p:Platform=x64" cp "$X64_TARGET" "$WINDOWS_PROGRAM_ENGINE" - cp "$X64_TARGET_PATH/keyman64.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + if_release_build_level cp "$X64_TARGET_PATH/keyman64.pdb" "$WINDOWS_DEBUGPATH_ENGINE" } function do_publish_x86() { diff --git a/windows/src/engine/keymanx64/build.sh b/windows/src/engine/keymanx64/build.sh index 35c7ad470b..d4dcbbee17 100755 --- a/windows/src/engine/keymanx64/build.sh +++ b/windows/src/engine/keymanx64/build.sh @@ -33,7 +33,7 @@ function do_build() { build_manifest.res vs_msbuild keymanx64.vcxproj //t:Build "//p:Platform=x64" cp "$X64_TARGET" "$WINDOWS_PROGRAM_ENGINE" - cp "$X64_TARGET_PATH/keymanx64.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + if_release_build_level cp "$X64_TARGET_PATH/keymanx64.pdb" "$WINDOWS_DEBUGPATH_ENGINE" } function do_publish() { diff --git a/windows/src/engine/kmcomapi/build.sh b/windows/src/engine/kmcomapi/build.sh index b808bc4d5b..ca3b0285ce 100755 --- a/windows/src/engine/kmcomapi/build.sh +++ b/windows/src/engine/kmcomapi/build.sh @@ -44,7 +44,7 @@ function do_build() { mv -f "$WIN64_TARGET_PATH/kmcomapi.dll" "$WIN64_TARGET_PATH/kmcomapi.x64.dll" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - cp "$WIN32_TARGET_PATH/kmcomapi.dbg" "$WINDOWS_DEBUGPATH_ENGINE/kmcomapi.dbg" + if_release_build_level cp "$WIN32_TARGET_PATH/kmcomapi.dbg" "$WINDOWS_DEBUGPATH_ENGINE/kmcomapi.dbg" cp "$WIN64_TARGET_PATH/kmcomapi.x64.dll" "$WINDOWS_PROGRAM_ENGINE/kmcomapi.x64.dll" # x64 Delphi symbols not supported: cp "$WIN64_TARGET_PATH/kmcomapi.dbg" "$WINDOWS_PROGRAM_ENGINE/kmcomapi.x64.dbg" diff --git a/windows/src/engine/kmrefresh/build.sh b/windows/src/engine/kmrefresh/build.sh index b04d53f043..7ce32e3f28 100755 --- a/windows/src/engine/kmrefresh/build.sh +++ b/windows/src/engine/kmrefresh/build.sh @@ -34,9 +34,9 @@ function do_build() { vs_msbuild kmrefresh.vcxproj //t:Build "//p:Platform=Win32" vs_msbuild kmrefresh.vcxproj //t:Build "//p:Platform=x64" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - cp "$WIN32_TARGET_PATH/kmrefresh.x86.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + if_release_build_level cp "$WIN32_TARGET_PATH/kmrefresh.x86.pdb" "$WINDOWS_DEBUGPATH_ENGINE" cp "$X64_TARGET" "$WINDOWS_PROGRAM_ENGINE" - cp "$X64_TARGET_PATH/kmrefresh.x64.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + if_release_build_level cp "$X64_TARGET_PATH/kmrefresh.x64.pdb" "$WINDOWS_DEBUGPATH_ENGINE" } function do_publish() { diff --git a/windows/src/engine/kmtip/build.sh b/windows/src/engine/kmtip/build.sh index 346a2a9678..9e7b9b928c 100755 --- a/windows/src/engine/kmtip/build.sh +++ b/windows/src/engine/kmtip/build.sh @@ -35,9 +35,9 @@ function do_build() { vs_msbuild kmtip.vcxproj //t:Build "//p:Platform=Win32" vs_msbuild kmtip.vcxproj //t:Build "//p:Platform=x64" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - cp "$WIN32_TARGET_PATH/kmtip.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + if_release_build_level cp "$WIN32_TARGET_PATH/kmtip.pdb" "$WINDOWS_DEBUGPATH_ENGINE" cp "$X64_TARGET" "$WINDOWS_PROGRAM_ENGINE" - cp "$X64_TARGET_PATH/kmtip64.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + if_release_build_level cp "$X64_TARGET_PATH/kmtip64.pdb" "$WINDOWS_DEBUGPATH_ENGINE" } function do_publish() { diff --git a/windows/src/engine/mcompile/build.sh b/windows/src/engine/mcompile/build.sh index f3a79e0079..e25b87a42e 100755 --- a/windows/src/engine/mcompile/build.sh +++ b/windows/src/engine/mcompile/build.sh @@ -33,7 +33,7 @@ function do_build() { build_manifest.res vs_msbuild mcompile.vcxproj //t:Build "//p:Platform=Win32" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - cp "$WIN32_TARGET_PATH/mcompile.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + if_release_build_level cp "$WIN32_TARGET_PATH/mcompile.pdb" "$WINDOWS_DEBUGPATH_ENGINE" } function do_publish() { diff --git a/windows/src/engine/tsysinfo/build.sh b/windows/src/engine/tsysinfo/build.sh index 01688a6a70..df259a99d9 100755 --- a/windows/src/engine/tsysinfo/build.sh +++ b/windows/src/engine/tsysinfo/build.sh @@ -47,7 +47,7 @@ function do_build() { cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" cp *.xslt "$WINDOWS_PROGRAM_ENGINE/" - cp "$WIN32_TARGET_PATH/tsysinfo.dbg" "$WINDOWS_DEBUGPATH_ENGINE/tsysinfo.dbg" + if_release_build_level cp "$WIN32_TARGET_PATH/tsysinfo.dbg" "$WINDOWS_DEBUGPATH_ENGINE/tsysinfo.dbg" } function do_publish() { diff --git a/windows/src/support/etl2log/build.sh b/windows/src/support/etl2log/build.sh index e2fdee1acd..09f5967d87 100755 --- a/windows/src/support/etl2log/build.sh +++ b/windows/src/support/etl2log/build.sh @@ -31,7 +31,7 @@ function do_build() { build_version.res vs_msbuild etl2log.vcxproj //t:Build "//p:Platform=Win32" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_SUPPORT" - cp "$WIN32_TARGET_PATH/etl2log.pdb" "$WINDOWS_DEBUGPATH_SUPPORT" + if_release_build_level cp "$WIN32_TARGET_PATH/etl2log.pdb" "$WINDOWS_DEBUGPATH_SUPPORT" } builder_run_action clean:project do_clean diff --git a/windows/src/support/texteditor/build.sh b/windows/src/support/texteditor/build.sh index 312ee1d0f4..354144faa6 100755 --- a/windows/src/support/texteditor/build.sh +++ b/windows/src/support/texteditor/build.sh @@ -34,8 +34,8 @@ function do_build() { vs_msbuild Editor.vcxproj //t:Build "//p:Platform=x64" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_SUPPORT" cp "$X64_TARGET" "$WINDOWS_PROGRAM_SUPPORT" - cp "$WIN32_TARGET_PATH/editor32.pdb" "$WINDOWS_DEBUGPATH_SUPPORT" - cp "$X64_TARGET_PATH/editor64.pdb" "$WINDOWS_DEBUGPATH_SUPPORT" + if_release_build_level cp "$WIN32_TARGET_PATH/editor32.pdb" "$WINDOWS_DEBUGPATH_SUPPORT" + if_release_build_level cp "$X64_TARGET_PATH/editor64.pdb" "$WINDOWS_DEBUGPATH_SUPPORT" } builder_run_action clean:project do_clean From f3136d5ab2c939629a0538dc6746b050631ffce3 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 16 Jun 2025 05:54:33 +1000 Subject: [PATCH 028/262] maint(developer): skip signature check on buildLevel build --- developer/src/inst/build.sh | 4 ++-- oem/firstvoices/windows/src/inst/build.sh | 2 +- resources/build/win/environment.inc.sh | 2 +- windows/src/desktop/inst/build.sh | 5 +++-- windows/src/engine/inst/build.sh | 9 +++++++-- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/developer/src/inst/build.sh b/developer/src/inst/build.sh index 8f5b722506..21b43bf765 100755 --- a/developer/src/inst/build.sh +++ b/developer/src/inst/build.sh @@ -51,7 +51,7 @@ function do_clean() { #------------------------------------------------------------------------------------------------------------------- function do_publish() { - verify-program-signatures + if_release_build_level verify-program-signatures verify-node-installer-version "$KEYMAN_ROOT/common/windows/cef-checkout.sh" @@ -80,7 +80,7 @@ function do_publish() { copy-kmdev - verify-installer-signatures + if_release_build_level verify-installer-signatures } function do_test() { diff --git a/oem/firstvoices/windows/src/inst/build.sh b/oem/firstvoices/windows/src/inst/build.sh index f427c0dfc0..f57907f409 100755 --- a/oem/firstvoices/windows/src/inst/build.sh +++ b/oem/firstvoices/windows/src/inst/build.sh @@ -105,7 +105,7 @@ function copy-installer() { cp firstvoices.msi "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/firstvoices.msi" cp firstvoices.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/firstvoices-${KEYMAN_VERSION}.exe" - verify-installer-signatures + if_release_build_level verify-installer-signatures } function verify-installer-signatures() { diff --git a/resources/build/win/environment.inc.sh b/resources/build/win/environment.inc.sh index 5463a1af5e..14ccc1c2ed 100644 --- a/resources/build/win/environment.inc.sh +++ b/resources/build/win/environment.inc.sh @@ -32,7 +32,7 @@ COMMON_ROOT="$KEYMAN_ROOT/common/windows/delphi" OUTLIB="$WINDOWS_ROOT/lib" COMMON_OUTLIB="$KEYMAN_ROOT/common/windows/lib" -if builder_is_debug_build || [[ $KEYMAN_VERSION_ENVIRONMENT == local ]] || [[ ! -z ${TEAMCITY_PR_NUMBER+x} ]]; then +if builder_is_debug_build || [[ $KEYMAN_VERSION_ENVIRONMENT == local ]] || builder_is_ci_test_build; then # We do a fast build for debug builds, local builds, test PR builds but not for master/beta/stable release builds GO_FAST=1 else diff --git a/windows/src/desktop/inst/build.sh b/windows/src/desktop/inst/build.sh index bcedcef95c..f3a5795c6e 100755 --- a/windows/src/desktop/inst/build.sh +++ b/windows/src/desktop/inst/build.sh @@ -53,7 +53,7 @@ function do_build_desktop_resources() { } function do_publish() { - verify-program-signatures + if_release_build_level verify-program-signatures "$KEYMAN_ROOT/common/windows/cef-checkout.sh" @@ -73,6 +73,7 @@ function do_publish() { -sice:ICE82 -sice:ICE80 \ -nologo \ -dWixUILicenseRtf=License.rtf \ + "$WIXLIGHTCOMPRESSION" \ -out keymandesktop.msi -ext WixUIExtension \ keymandesktop.wixobj desktopui.wixobj cef.wixobj locale.wixobj @@ -106,7 +107,7 @@ function copy-installer() { cp keymandesktop.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/keyman-${KEYMAN_VERSION}.exe" cp "$WINDOWS_PROGRAM_APP/setup.exe" "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/setup.exe" - verify-installer-signatures + if_release_build_level verify-installer-signatures # Copy the unsigned setup.exe for use in bundling scenarios; zip it up for clarity wzzip "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/setup-redist.zip" "$WINDOWS_PROGRAM_APP/setup-redist.exe" diff --git a/windows/src/engine/inst/build.sh b/windows/src/engine/inst/build.sh index 22cd5954a6..9891958cdd 100755 --- a/windows/src/engine/inst/build.sh +++ b/windows/src/engine/inst/build.sh @@ -30,7 +30,7 @@ fi #------------------------------------------------------------------------------------------------------------------- function do_publish() { - verify-program-signatures + if_release_build_level verify-program-signatures # # Build the installation archive @@ -38,7 +38,12 @@ function do_publish() { "$WIXCANDLE" -dKEYMAN_VERSION=$KEYMAN_VERSION_WIN -dRELEASE=$KEYMAN_VERSION_RELEASE -ext WixUtilExtension keymanengine.wxs components.wxs # warning 1072 relates to Error table defined by WixUtilExtension. Doesn't really affect us. - "$WIXLIGHT" -sw1072 -ext WixUtilExtension keymanengine.wixobj components.wixobj -o keymanengine.msm + "$WIXLIGHT" \ + -sw1072 \ + -ext WixUtilExtension \ + "$WIXLIGHTCOMPRESSION" \ + keymanengine.wixobj components.wixobj \ + -o keymanengine.msm # # Sign the installation archive From 156bdcdd8ae73ed8c9de18207706974334a303cd Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 16 Jun 2025 06:45:42 +1000 Subject: [PATCH 029/262] maint(common): rename and move builder_if_release_build_level --- developer/src/inst/build.sh | 4 ++-- developer/src/kmanalyze/build.sh | 2 +- developer/src/kmconvert/build.sh | 2 +- developer/src/kmdbrowserhost/build.sh | 2 +- developer/src/kmdecomp/build.sh | 2 +- developer/src/samples/imsample/build.sh | 4 ++-- developer/src/setup/build.sh | 4 ++-- developer/src/tike/build.sh | 4 ++-- oem/firstvoices/windows/src/inst/build.sh | 2 +- resources/build/win/environment.inc.sh | 15 +-------------- resources/builder.inc.sh | 12 ++++++++++++ windows/src/desktop/inst/build.sh | 4 ++-- windows/src/desktop/insthelp/build.sh | 2 +- windows/src/desktop/kmbrowserhost/build.sh | 2 +- windows/src/desktop/kmconfig/build.sh | 2 +- windows/src/desktop/kmshell/build.sh | 2 +- windows/src/desktop/setup/build.sh | 2 +- windows/src/engine/inst/build.sh | 2 +- windows/src/engine/insthelper/build.sh | 2 +- windows/src/engine/keyman/build.sh | 2 +- windows/src/engine/keyman32/build.sh | 4 ++-- windows/src/engine/keymanx64/build.sh | 2 +- windows/src/engine/kmcomapi/build.sh | 2 +- windows/src/engine/kmrefresh/build.sh | 4 ++-- windows/src/engine/kmtip/build.sh | 4 ++-- windows/src/engine/mcompile/build.sh | 2 +- windows/src/engine/tsysinfo/build.sh | 2 +- windows/src/support/etl2log/build.sh | 2 +- windows/src/support/texteditor/build.sh | 4 ++-- 29 files changed, 49 insertions(+), 50 deletions(-) diff --git a/developer/src/inst/build.sh b/developer/src/inst/build.sh index 21b43bf765..a8eb8b849f 100755 --- a/developer/src/inst/build.sh +++ b/developer/src/inst/build.sh @@ -51,7 +51,7 @@ function do_clean() { #------------------------------------------------------------------------------------------------------------------- function do_publish() { - if_release_build_level verify-program-signatures + builder_if_release_build_level verify-program-signatures verify-node-installer-version "$KEYMAN_ROOT/common/windows/cef-checkout.sh" @@ -80,7 +80,7 @@ function do_publish() { copy-kmdev - if_release_build_level verify-installer-signatures + builder_if_release_build_level verify-installer-signatures } function do_test() { diff --git a/developer/src/kmanalyze/build.sh b/developer/src/kmanalyze/build.sh index 6f77aee381..ecd9fd263f 100755 --- a/developer/src/kmanalyze/build.sh +++ b/developer/src/kmanalyze/build.sh @@ -32,7 +32,7 @@ function do_build() { build_version.res vs_msbuild kmanalyze.vcxproj //t:Build "//p:Platform=Win32" cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" - if_release_build_level cp "$WIN32_TARGET_PATH/kmanalyze.pdb" "$DEVELOPER_DEBUGPATH" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmanalyze.pdb" "$DEVELOPER_DEBUGPATH" } function do_publish() { diff --git a/developer/src/kmconvert/build.sh b/developer/src/kmconvert/build.sh index 4bd68423f1..3ba94c21dc 100755 --- a/developer/src/kmconvert/build.sh +++ b/developer/src/kmconvert/build.sh @@ -33,7 +33,7 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" - if_release_build_level cp "$WIN32_TARGET_PATH/kmconvert.dbg" "$DEVELOPER_DEBUGPATH" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmconvert.dbg" "$DEVELOPER_DEBUGPATH" rm -rf "$DEVELOPER_PROGRAM/projects/templates" mkdir -p "$DEVELOPER_PROGRAM/projects/templates" diff --git a/developer/src/kmdbrowserhost/build.sh b/developer/src/kmdbrowserhost/build.sh index a7bb2d4449..55e7ae8d14 100755 --- a/developer/src/kmdbrowserhost/build.sh +++ b/developer/src/kmdbrowserhost/build.sh @@ -32,7 +32,7 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" - if_release_build_level cp "$WIN32_TARGET_PATH/kmdbrowserhost.dbg" "$DEVELOPER_DEBUGPATH" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmdbrowserhost.dbg" "$DEVELOPER_DEBUGPATH" } function do_publish() { diff --git a/developer/src/kmdecomp/build.sh b/developer/src/kmdecomp/build.sh index 9922add612..494b8afde5 100755 --- a/developer/src/kmdecomp/build.sh +++ b/developer/src/kmdecomp/build.sh @@ -33,7 +33,7 @@ function do_build() { build_version.res vs_msbuild kmdecomp.sln //t:Build "//p:Platform=Win32" cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" - if_release_build_level cp "$WIN32_TARGET_PATH/kmdecomp.pdb" "$DEVELOPER_DEBUGPATH" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmdecomp.pdb" "$DEVELOPER_DEBUGPATH" } function do_publish() { diff --git a/developer/src/samples/imsample/build.sh b/developer/src/samples/imsample/build.sh index 4c7535ec1f..2cdd11ac70 100755 --- a/developer/src/samples/imsample/build.sh +++ b/developer/src/samples/imsample/build.sh @@ -32,11 +32,11 @@ function do_build() { build_version.res vs_msbuild imsample.sln //t:Build "//p:Platform=Win32" cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" - if_release_build_level cp "$WIN32_TARGET_PATH/imsample.pdb" "$DEVELOPER_DEBUGPATH" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/imsample.pdb" "$DEVELOPER_DEBUGPATH" vs_msbuild imsample.sln //t:Build "//p:Platform=x64" cp "$X64_TARGET" "$DEVELOPER_PROGRAM" - if_release_build_level cp "$X64_TARGET_PATH/imsample.x64.pdb" "$DEVELOPER_DEBUGPATH" + builder_if_release_build_level cp "$X64_TARGET_PATH/imsample.x64.pdb" "$DEVELOPER_DEBUGPATH" } # TODO diff --git a/developer/src/setup/build.sh b/developer/src/setup/build.sh index 45e1816046..ef84122fdb 100755 --- a/developer/src/setup/build.sh +++ b/developer/src/setup/build.sh @@ -33,8 +33,8 @@ function do_build() { sentrytool_delphiprep "$WIN32_TARGET" setup.dpr tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$DEVELOPER_PROGRAM" - if_release_build_level cp "$WIN32_TARGET_PATH/setup.dbg" "$DEVELOPER_DEBUGPATH/devsetup.dbg" - if_release_build_level mv "$WIN32_TARGET_PATH/setup.dbg" "$WIN32_TARGET_PATH/devsetup.dbg" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/setup.dbg" "$DEVELOPER_DEBUGPATH/devsetup.dbg" + builder_if_release_build_level mv "$WIN32_TARGET_PATH/setup.dbg" "$WIN32_TARGET_PATH/devsetup.dbg" } function do_publish() { diff --git a/developer/src/tike/build.sh b/developer/src/tike/build.sh index 9dab71a904..b06fd1f847 100755 --- a/developer/src/tike/build.sh +++ b/developer/src/tike/build.sh @@ -76,10 +76,10 @@ function do_build() { cp kmlmp.cmd "$DEVELOPER_PROGRAM" cp kmc.cmd "$DEVELOPER_PROGRAM" cp "$KEYMAN_ROOT/core/build/x86/$TARGET_PATH/src/$KEYMANCORE_DLL" "$DEVELOPER_PROGRAM" - if_release_build_level cp "$WIN32_TARGET_PATH/tike.dbg" "$DEVELOPER_DEBUGPATH" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/tike.dbg" "$DEVELOPER_DEBUGPATH" cp "$KEYMAN_ROOT/core/build/x86/$TARGET_PATH/src/$KEYMANCORE_DLL" "$WIN32_TARGET_PATH" - if_release_build_level cp "$KEYMAN_ROOT/core/build/x86/$TARGET_PATH/src/$KEYMANCORE_PDB" "$WIN32_TARGET_PATH" + builder_if_release_build_level cp "$KEYMAN_ROOT/core/build/x86/$TARGET_PATH/src/$KEYMANCORE_PDB" "$WIN32_TARGET_PATH" cp "$KEYMAN_ROOT/common/windows/delphi/ext/sentry/sentry.dll" "$DEVELOPER_PROGRAM/" cp "$KEYMAN_ROOT/common/windows/delphi/ext/sentry/sentry.x64.dll" "$DEVELOPER_PROGRAM/" diff --git a/oem/firstvoices/windows/src/inst/build.sh b/oem/firstvoices/windows/src/inst/build.sh index f57907f409..ac6c309554 100755 --- a/oem/firstvoices/windows/src/inst/build.sh +++ b/oem/firstvoices/windows/src/inst/build.sh @@ -105,7 +105,7 @@ function copy-installer() { cp firstvoices.msi "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/firstvoices.msi" cp firstvoices.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/firstvoices-${KEYMAN_VERSION}.exe" - if_release_build_level verify-installer-signatures + builder_if_release_build_level verify-installer-signatures } function verify-installer-signatures() { diff --git a/resources/build/win/environment.inc.sh b/resources/build/win/environment.inc.sh index 14ccc1c2ed..40c4de6db5 100644 --- a/resources/build/win/environment.inc.sh +++ b/resources/build/win/environment.inc.sh @@ -87,21 +87,8 @@ sentrytool_delphiprep() { ) } -if_release_build_level() { - if builder_is_ci_build && builder_is_ci_build_level_build; then - builder_echo "Skipping - buildLevel=build: $@" - return 0 - fi - "$@" -} - tds2dbg() { - if builder_is_ci_build && builder_is_ci_build_level_build; then - builder_echo "Skipping tds2dbg - buildLevel=build: $@" - return 0 - fi - - "$TDS2DBG" "$@" + builder_if_release_build_level "$TDS2DBG" "$@" } delphi_msbuild() { diff --git a/resources/builder.inc.sh b/resources/builder.inc.sh index 7798eb2484..fc741e4081 100755 --- a/resources/builder.inc.sh +++ b/resources/builder.inc.sh @@ -2250,6 +2250,18 @@ builder_is_ci_build_level_build() { return 0 } +# +# Executes statement if a ci build level of 'release', and for local builds, but +# not for a ci build level of 'build' +# +builder_if_release_build_level() { + if builder_is_ci_build && builder_is_ci_build_level_build; then + builder_echo "Skipping - buildLevel=build: $@" + return 0 + fi + "$@" +} + ################################################################################ # Final initialization ################################################################################ diff --git a/windows/src/desktop/inst/build.sh b/windows/src/desktop/inst/build.sh index f3a5795c6e..ca8df31c65 100755 --- a/windows/src/desktop/inst/build.sh +++ b/windows/src/desktop/inst/build.sh @@ -53,7 +53,7 @@ function do_build_desktop_resources() { } function do_publish() { - if_release_build_level verify-program-signatures + builder_if_release_build_level verify-program-signatures "$KEYMAN_ROOT/common/windows/cef-checkout.sh" @@ -107,7 +107,7 @@ function copy-installer() { cp keymandesktop.exe "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/keyman-${KEYMAN_VERSION}.exe" cp "$WINDOWS_PROGRAM_APP/setup.exe" "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/setup.exe" - if_release_build_level verify-installer-signatures + builder_if_release_build_level verify-installer-signatures # Copy the unsigned setup.exe for use in bundling scenarios; zip it up for clarity wzzip "$KEYMAN_ROOT/windows/release/${KEYMAN_VERSION}/setup-redist.zip" "$WINDOWS_PROGRAM_APP/setup-redist.exe" diff --git a/windows/src/desktop/insthelp/build.sh b/windows/src/desktop/insthelp/build.sh index 86d94f9648..16b2a50e40 100755 --- a/windows/src/desktop/insthelp/build.sh +++ b/windows/src/desktop/insthelp/build.sh @@ -33,7 +33,7 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP" - if_release_build_level cp "$WIN32_TARGET_PATH/insthelp.dbg" "$WINDOWS_DEBUGPATH_APP/insthelp.dbg" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/insthelp.dbg" "$WINDOWS_DEBUGPATH_APP/insthelp.dbg" } function do_publish() { diff --git a/windows/src/desktop/kmbrowserhost/build.sh b/windows/src/desktop/kmbrowserhost/build.sh index 56a0de331f..dd3521f094 100755 --- a/windows/src/desktop/kmbrowserhost/build.sh +++ b/windows/src/desktop/kmbrowserhost/build.sh @@ -32,7 +32,7 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP" - if_release_build_level cp "$WIN32_TARGET_PATH/kmbrowserhost.dbg" "$WINDOWS_DEBUGPATH_APP/kmbrowserhost.dbg" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmbrowserhost.dbg" "$WINDOWS_DEBUGPATH_APP/kmbrowserhost.dbg" } function do_publish() { diff --git a/windows/src/desktop/kmconfig/build.sh b/windows/src/desktop/kmconfig/build.sh index 704067ddc1..a9f69bea21 100755 --- a/windows/src/desktop/kmconfig/build.sh +++ b/windows/src/desktop/kmconfig/build.sh @@ -32,7 +32,7 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP" - if_release_build_level cp "$WIN32_TARGET_PATH/kmconfig.dbg" "$WINDOWS_DEBUGPATH_APP/kmconfig.dbg" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmconfig.dbg" "$WINDOWS_DEBUGPATH_APP/kmconfig.dbg" } function do_publish() { diff --git a/windows/src/desktop/kmshell/build.sh b/windows/src/desktop/kmshell/build.sh index 0f3d7c9e69..6459a67fc6 100755 --- a/windows/src/desktop/kmshell/build.sh +++ b/windows/src/desktop/kmshell/build.sh @@ -39,7 +39,7 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP" - if_release_build_level cp "$WIN32_TARGET_PATH/kmshell.dbg" "$WINDOWS_DEBUGPATH_APP/kmshell.dbg" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmshell.dbg" "$WINDOWS_DEBUGPATH_APP/kmshell.dbg" do_build_data } diff --git a/windows/src/desktop/setup/build.sh b/windows/src/desktop/setup/build.sh index 8415dd4734..a2b3310c3d 100755 --- a/windows/src/desktop/setup/build.sh +++ b/windows/src/desktop/setup/build.sh @@ -43,7 +43,7 @@ function do_build() { cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP" # setup-redist.exe does not get signed as it is intended for a bundled installer cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_APP/setup-redist.exe" - if_release_build_level cp "$WIN32_TARGET_PATH/setup.dbg" "$WINDOWS_DEBUGPATH_APP/setup.dbg" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/setup.dbg" "$WINDOWS_DEBUGPATH_APP/setup.dbg" } function do_build_debug_manifest() { diff --git a/windows/src/engine/inst/build.sh b/windows/src/engine/inst/build.sh index 9891958cdd..0152644941 100755 --- a/windows/src/engine/inst/build.sh +++ b/windows/src/engine/inst/build.sh @@ -30,7 +30,7 @@ fi #------------------------------------------------------------------------------------------------------------------- function do_publish() { - if_release_build_level verify-program-signatures + builder_if_release_build_level verify-program-signatures # # Build the installation archive diff --git a/windows/src/engine/insthelper/build.sh b/windows/src/engine/insthelper/build.sh index 470b183748..85a9be91ea 100755 --- a/windows/src/engine/insthelper/build.sh +++ b/windows/src/engine/insthelper/build.sh @@ -31,7 +31,7 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - if_release_build_level cp "$WIN32_TARGET_PATH/insthelper.dbg" "$WINDOWS_DEBUGPATH_ENGINE/insthelper.dbg" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/insthelper.dbg" "$WINDOWS_DEBUGPATH_ENGINE/insthelper.dbg" } function do_publish() { diff --git a/windows/src/engine/keyman/build.sh b/windows/src/engine/keyman/build.sh index adf924ee2a..561c277b9f 100755 --- a/windows/src/engine/keyman/build.sh +++ b/windows/src/engine/keyman/build.sh @@ -42,7 +42,7 @@ function do_build() { tds2dbg "$WIN32_TARGET" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - if_release_build_level cp "$WIN32_TARGET_PATH/keyman.dbg" "$WINDOWS_DEBUGPATH_ENGINE/keyman.dbg" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/keyman.dbg" "$WINDOWS_DEBUGPATH_ENGINE/keyman.dbg" # Also copy sentry files here cp "$KEYMAN_ROOT/common/windows/delphi/ext/sentry/sentry.dll" \ diff --git a/windows/src/engine/keyman32/build.sh b/windows/src/engine/keyman32/build.sh index 47d77b718d..55da1ef003 100755 --- a/windows/src/engine/keyman32/build.sh +++ b/windows/src/engine/keyman32/build.sh @@ -41,7 +41,7 @@ function do_build_x86() { build_version.res vs_msbuild keyman32.vcxproj //t:Build "//p:Platform=Win32" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - if_release_build_level cp "$WIN32_TARGET_PATH/keyman32.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/keyman32.pdb" "$WINDOWS_DEBUGPATH_ENGINE" } function do_build_x64() { @@ -49,7 +49,7 @@ function do_build_x64() { run_in_vs_env rc version64.rc vs_msbuild keyman32.vcxproj //t:Build "//p:Platform=x64" cp "$X64_TARGET" "$WINDOWS_PROGRAM_ENGINE" - if_release_build_level cp "$X64_TARGET_PATH/keyman64.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + builder_if_release_build_level cp "$X64_TARGET_PATH/keyman64.pdb" "$WINDOWS_DEBUGPATH_ENGINE" } function do_publish_x86() { diff --git a/windows/src/engine/keymanx64/build.sh b/windows/src/engine/keymanx64/build.sh index d4dcbbee17..4398c062fb 100755 --- a/windows/src/engine/keymanx64/build.sh +++ b/windows/src/engine/keymanx64/build.sh @@ -33,7 +33,7 @@ function do_build() { build_manifest.res vs_msbuild keymanx64.vcxproj //t:Build "//p:Platform=x64" cp "$X64_TARGET" "$WINDOWS_PROGRAM_ENGINE" - if_release_build_level cp "$X64_TARGET_PATH/keymanx64.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + builder_if_release_build_level cp "$X64_TARGET_PATH/keymanx64.pdb" "$WINDOWS_DEBUGPATH_ENGINE" } function do_publish() { diff --git a/windows/src/engine/kmcomapi/build.sh b/windows/src/engine/kmcomapi/build.sh index ca3b0285ce..83f0e164a1 100755 --- a/windows/src/engine/kmcomapi/build.sh +++ b/windows/src/engine/kmcomapi/build.sh @@ -44,7 +44,7 @@ function do_build() { mv -f "$WIN64_TARGET_PATH/kmcomapi.dll" "$WIN64_TARGET_PATH/kmcomapi.x64.dll" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - if_release_build_level cp "$WIN32_TARGET_PATH/kmcomapi.dbg" "$WINDOWS_DEBUGPATH_ENGINE/kmcomapi.dbg" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmcomapi.dbg" "$WINDOWS_DEBUGPATH_ENGINE/kmcomapi.dbg" cp "$WIN64_TARGET_PATH/kmcomapi.x64.dll" "$WINDOWS_PROGRAM_ENGINE/kmcomapi.x64.dll" # x64 Delphi symbols not supported: cp "$WIN64_TARGET_PATH/kmcomapi.dbg" "$WINDOWS_PROGRAM_ENGINE/kmcomapi.x64.dbg" diff --git a/windows/src/engine/kmrefresh/build.sh b/windows/src/engine/kmrefresh/build.sh index 7ce32e3f28..257f9dc636 100755 --- a/windows/src/engine/kmrefresh/build.sh +++ b/windows/src/engine/kmrefresh/build.sh @@ -34,9 +34,9 @@ function do_build() { vs_msbuild kmrefresh.vcxproj //t:Build "//p:Platform=Win32" vs_msbuild kmrefresh.vcxproj //t:Build "//p:Platform=x64" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - if_release_build_level cp "$WIN32_TARGET_PATH/kmrefresh.x86.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmrefresh.x86.pdb" "$WINDOWS_DEBUGPATH_ENGINE" cp "$X64_TARGET" "$WINDOWS_PROGRAM_ENGINE" - if_release_build_level cp "$X64_TARGET_PATH/kmrefresh.x64.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + builder_if_release_build_level cp "$X64_TARGET_PATH/kmrefresh.x64.pdb" "$WINDOWS_DEBUGPATH_ENGINE" } function do_publish() { diff --git a/windows/src/engine/kmtip/build.sh b/windows/src/engine/kmtip/build.sh index 9e7b9b928c..3289f5b6ed 100755 --- a/windows/src/engine/kmtip/build.sh +++ b/windows/src/engine/kmtip/build.sh @@ -35,9 +35,9 @@ function do_build() { vs_msbuild kmtip.vcxproj //t:Build "//p:Platform=Win32" vs_msbuild kmtip.vcxproj //t:Build "//p:Platform=x64" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - if_release_build_level cp "$WIN32_TARGET_PATH/kmtip.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/kmtip.pdb" "$WINDOWS_DEBUGPATH_ENGINE" cp "$X64_TARGET" "$WINDOWS_PROGRAM_ENGINE" - if_release_build_level cp "$X64_TARGET_PATH/kmtip64.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + builder_if_release_build_level cp "$X64_TARGET_PATH/kmtip64.pdb" "$WINDOWS_DEBUGPATH_ENGINE" } function do_publish() { diff --git a/windows/src/engine/mcompile/build.sh b/windows/src/engine/mcompile/build.sh index e25b87a42e..d302e740bc 100755 --- a/windows/src/engine/mcompile/build.sh +++ b/windows/src/engine/mcompile/build.sh @@ -33,7 +33,7 @@ function do_build() { build_manifest.res vs_msbuild mcompile.vcxproj //t:Build "//p:Platform=Win32" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" - if_release_build_level cp "$WIN32_TARGET_PATH/mcompile.pdb" "$WINDOWS_DEBUGPATH_ENGINE" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/mcompile.pdb" "$WINDOWS_DEBUGPATH_ENGINE" } function do_publish() { diff --git a/windows/src/engine/tsysinfo/build.sh b/windows/src/engine/tsysinfo/build.sh index df259a99d9..51f233487a 100755 --- a/windows/src/engine/tsysinfo/build.sh +++ b/windows/src/engine/tsysinfo/build.sh @@ -47,7 +47,7 @@ function do_build() { cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_ENGINE" cp *.xslt "$WINDOWS_PROGRAM_ENGINE/" - if_release_build_level cp "$WIN32_TARGET_PATH/tsysinfo.dbg" "$WINDOWS_DEBUGPATH_ENGINE/tsysinfo.dbg" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/tsysinfo.dbg" "$WINDOWS_DEBUGPATH_ENGINE/tsysinfo.dbg" } function do_publish() { diff --git a/windows/src/support/etl2log/build.sh b/windows/src/support/etl2log/build.sh index 09f5967d87..b7fd91c9b9 100755 --- a/windows/src/support/etl2log/build.sh +++ b/windows/src/support/etl2log/build.sh @@ -31,7 +31,7 @@ function do_build() { build_version.res vs_msbuild etl2log.vcxproj //t:Build "//p:Platform=Win32" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_SUPPORT" - if_release_build_level cp "$WIN32_TARGET_PATH/etl2log.pdb" "$WINDOWS_DEBUGPATH_SUPPORT" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/etl2log.pdb" "$WINDOWS_DEBUGPATH_SUPPORT" } builder_run_action clean:project do_clean diff --git a/windows/src/support/texteditor/build.sh b/windows/src/support/texteditor/build.sh index 354144faa6..997d8e6f24 100755 --- a/windows/src/support/texteditor/build.sh +++ b/windows/src/support/texteditor/build.sh @@ -34,8 +34,8 @@ function do_build() { vs_msbuild Editor.vcxproj //t:Build "//p:Platform=x64" cp "$WIN32_TARGET" "$WINDOWS_PROGRAM_SUPPORT" cp "$X64_TARGET" "$WINDOWS_PROGRAM_SUPPORT" - if_release_build_level cp "$WIN32_TARGET_PATH/editor32.pdb" "$WINDOWS_DEBUGPATH_SUPPORT" - if_release_build_level cp "$X64_TARGET_PATH/editor64.pdb" "$WINDOWS_DEBUGPATH_SUPPORT" + builder_if_release_build_level cp "$WIN32_TARGET_PATH/editor32.pdb" "$WINDOWS_DEBUGPATH_SUPPORT" + builder_if_release_build_level cp "$X64_TARGET_PATH/editor64.pdb" "$WINDOWS_DEBUGPATH_SUPPORT" } builder_run_action clean:project do_clean From 145f19b009a546fc9daffb00cac1614e9941a459 Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Mon, 16 Jun 2025 10:12:15 +1000 Subject: [PATCH 030/262] fix(windows): review comments Co-authored-by: Eberhard Beilharz --- .../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 d9e718c557..5c89e795d7 100644 --- a/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas +++ b/windows/src/desktop/kmshell/main/Keyman.System.UpdateStateMachine.pas @@ -921,7 +921,7 @@ begin CheckForUpdates.Free; end; { Response OK and go back to update available so files can be downloaded } - // TODO: This actually needs to check if the updates available are newer then the already downloaded updates + // TODO: This actually needs to check if the updates available are newer than the already downloaded updates if Result = wucUpdateAvailable then begin From c70c854b99c7280e7b634e783fd7fe37dea9d537 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 16 Jun 2025 18:05:55 +0200 Subject: [PATCH 031/262] maint(windows): address code review comments --- ...publish-developer-to-downloads-keyman-com.ps1 | 16 +++++----------- resources/teamcity/includes/tc-helpers.inc.sh | 2 -- resources/teamcity/includes/tc-windows.inc.sh | 4 ++-- .../teamcity/windows/keyman-windows-release.sh | 2 +- .../teamcity/windows/keyman-windows-test.sh | 2 +- .../teamcity/windows/windows-actions.inc.sh | 4 ---- 6 files changed, 9 insertions(+), 21 deletions(-) diff --git a/resources/teamcity/developer/publish-developer-to-downloads-keyman-com.ps1 b/resources/teamcity/developer/publish-developer-to-downloads-keyman-com.ps1 index 7e2770b2b5..79c64e24ae 100644 --- a/resources/teamcity/developer/publish-developer-to-downloads-keyman-com.ps1 +++ b/resources/teamcity/developer/publish-developer-to-downloads-keyman-com.ps1 @@ -49,17 +49,11 @@ mkdir ${upload_path} # Build Keyman Compiler WINE archive # -if((Test-Path ..\release\${kmcomp_zip}) -ne 0) { - # REVIEW: I guess this is the path for Keyman 18+. In which case we - # can remove the else block - copy ..\release\${kmcomp_zip} ..\${upload_path}\${kmcomp_zip} -} else { - cd bin - # Keyman versions 17.0+; note, use npm install for most modules - copy ..\..\common\schemas\keyboard_info\keyboard_info.schema.json . - & "${7Z_HOME}\7z.exe" a -bd -bb0 ..\${upload_path}\${kmcomp_zip} kmconvert.exe keyboard_info.schema.json xml\layoutbuilder\*.keyman-touch-layout projects\ server\ - cd .. -} +cd bin +# Keyman versions 17.0+; note, use npm install for most modules +copy ..\..\common\schemas\keyboard_info\keyboard_info.schema.json . +& "${7Z_HOME}\7z.exe" a -bd -bb0 ..\${upload_path}\${kmcomp_zip} kmconvert.exe keyboard_info.schema.json xml\layoutbuilder\*.keyman-touch-layout projects\ server\ +cd .. # # Copy source files diff --git a/resources/teamcity/includes/tc-helpers.inc.sh b/resources/teamcity/includes/tc-helpers.inc.sh index 5ea9c7e70f..3a47840e29 100644 --- a/resources/teamcity/includes/tc-helpers.inc.sh +++ b/resources/teamcity/includes/tc-helpers.inc.sh @@ -72,8 +72,6 @@ upload_help() { local PRODUCT_PATH=$2 builder_echo start "upload help" "Uploading new ${PRODUCT} help to help.keyman.com" - export HELP_KEYMAN_COM="${HELP_KEYMAN_COM:-${KEYMAN_ROOT}/../help.keyman.com}" - ( # shellcheck disable=SC2164 cd "${KEYMAN_ROOT}/resources/build" diff --git a/resources/teamcity/includes/tc-windows.inc.sh b/resources/teamcity/includes/tc-windows.inc.sh index 6f2cb8b337..689a068327 100644 --- a/resources/teamcity/includes/tc-windows.inc.sh +++ b/resources/teamcity/includes/tc-windows.inc.sh @@ -9,7 +9,7 @@ download_symbol_server_index() { # shellcheck disable=SC2164 cd "${KEYMAN_ROOT}/.." # shellcheck disable=SC2154 - powershell -NonInteractive -ExecutionPolicy Bypass -File "${THIS_SCRIPT_PATH}/../includes/download-symbol-server-index.ps1" + powershell -NonInteractive -ExecutionPolicy Bypass -File "${KEYMAN_ROOT}/resources/teamcity/includes/download-symbol-server-index.ps1" ) builder_echo end "download symbol server index" success "Finished downloading symbol server index" @@ -22,7 +22,7 @@ publish_new_symbols() { ( cd "${KEYMAN_ROOT}/../symbols" # shellcheck disable=SC2154 - powershell -NonInteractive -ExecutionPolicy Bypass -File "${THIS_SCRIPT_PATH}/../includes/publish-new-symbols.ps1" + powershell -NonInteractive -ExecutionPolicy Bypass -File "${KEYMAN_ROOT}/resources/teamcity/includes/publish-new-symbols.ps1" ) builder_echo end "publish new symbols" success "Finished publishing new symbols to symbol server" diff --git a/resources/teamcity/windows/keyman-windows-release.sh b/resources/teamcity/windows/keyman-windows-release.sh index 8cc5d62a0b..0a92044a6f 100755 --- a/resources/teamcity/windows/keyman-windows-release.sh +++ b/resources/teamcity/windows/keyman-windows-release.sh @@ -62,7 +62,7 @@ function windows_publish_action() { export RSYNC_HOST export RSYNC_ROOT - call_windows_publish + "${KEYMAN_ROOT}/windows/build.sh" publish windows_upload_symbols_to_sentry download_symbol_server_index publish_new_symbols diff --git a/resources/teamcity/windows/keyman-windows-test.sh b/resources/teamcity/windows/keyman-windows-test.sh index 86664224f0..67705f20e1 100755 --- a/resources/teamcity/windows/keyman-windows-test.sh +++ b/resources/teamcity/windows/keyman-windows-test.sh @@ -37,7 +37,7 @@ fi function windows_publish_action() { builder_echo start "publish windows" "Publishing Keyman for Windows" - call_windows_publish + "${KEYMAN_ROOT}/windows/build.sh" publish windows_upload_symbols_to_sentry builder_echo end "publish windows" success "Finished publishing Keyman for Windows" } diff --git a/resources/teamcity/windows/windows-actions.inc.sh b/resources/teamcity/windows/windows-actions.inc.sh index 1bb3fb7c86..e8553bef0f 100755 --- a/resources/teamcity/windows/windows-actions.inc.sh +++ b/resources/teamcity/windows/windows-actions.inc.sh @@ -13,10 +13,6 @@ windows_test_action() { builder_echo end "test windows" success "Finished running Keyman for Windows tests" } -call_windows_publish() { - "${KEYMAN_ROOT}/windows/build.sh" publish -} - windows_upload_symbols_to_sentry() { "${KEYMAN_ROOT}/windows/src/buildtools/sentry-upload-difs.sh" } From 3f9bdad93f5a9d5d1b3f94ed035bee6f09b4a7e3 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 16 Jun 2025 14:01:14 -0400 Subject: [PATCH 032/262] auto: increment master version to 19.0.67 --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index fb76100f0f..06cd831c72 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 19.0.66 alpha 2025-06-16 + +* fix(windows): add values wucUpdateAvailable and wucNotChecked to TRemoteUpdateCheckResult enum (#14123) + ## 19.0.65 alpha 2025-06-13 * maint(developer): fix developer release build (#14185) diff --git a/VERSION.md b/VERSION.md index 31b9a57002..f0d2c39d19 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.66 \ No newline at end of file +19.0.67 \ No newline at end of file From 7e60074b9d769db2cbdfa5094911f1e3c5061c9d Mon Sep 17 00:00:00 2001 From: Meng-Heng Date: Tue, 17 Jun 2025 16:53:04 +0700 Subject: [PATCH 033/262] @Meng-Heng --- .../touch-keyboard-tutorial/gestures.md | 26 ++++++++++++++++++ .../touch-layout-gestures.jpg | Bin 0 -> 45050 bytes developer/docs/help/main/credits.md | 2 +- developer/docs/help/main/license.md | 2 +- 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md create mode 100644 developer/docs/help/images/touch-keyboard-tutorial/touch-layout-gestures.jpg diff --git a/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md b/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md new file mode 100644 index 0000000000..d39d86891f --- /dev/null +++ b/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md @@ -0,0 +1,26 @@ +--- +title: Using Gestures in Keyman Developer +--- + +Keyman Developer offers **Longpress** keys, **Flicks**, and **Multitaps** for touch keyboard layout. To allow gestures onto your keyboard, go to the Touch layout tab and define the keys as followed: + +Keyman Developer touch layout gestures + +## Tips + +* We can use both **flicks** and **long presses** in a single keyboard. Generally, we recommend only having flicks in one direction--south (down), and long presses work well with this pattern. See, for example, [sil_euro_latin](../../../../keyboard/sil_euro_latin/3.0.3/sil_euro_latin#toc-mobile-keyboard-layout). + * You’ll also notice on sil_euro_latin that the south-flicks on the base (and shift) layers correspond to the numeric/symbol layer, which is also helpful for remembering and discovering. +* **Flicks** in multiple directions are possible, but currently Keyman does not support hints for multiple directions, so they are not very discoverable. +* **Long presses** are more discoverable, but **flicks** are faster. We suggest using south flicks along with long presses, and including the flick output in the long press for maximum discoverability and optimum ergonomics. + +## Caution + +* **Flicks** near screen edges tend to be problematic--once you drag off the screen, then Keyman can no longer tell that you are flicking (or you may end up activating system controls on the bottom edge): + * On the bottom row, south flicks are not recommended. + * On the top row, we have the banner space reserved in order to be able to support flicks and long presses up into that region of the screen--not an ideal solution, but it works. + * Left/right screen edges, horizontal flicks are not the best practice. + +## See also + +* The [Winchus keyboard](../../../../keyboard/winchus/1.4/winchus#guia-rapida-celular) +* The [GFF Amharic keyboard](../../../../keyboard/gff_amharic/3.2.1/gff_amharic#toc-using-this-keyboard) \ No newline at end of file diff --git a/developer/docs/help/images/touch-keyboard-tutorial/touch-layout-gestures.jpg b/developer/docs/help/images/touch-keyboard-tutorial/touch-layout-gestures.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1d407cc22a61da77eb8305fb0a3a7dc91f91f8f1 GIT binary patch literal 45050 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 literal 0 HcmV?d00001 diff --git a/developer/docs/help/main/credits.md b/developer/docs/help/main/credits.md index f2c8a027c5..a4c2124870 100644 --- a/developer/docs/help/main/credits.md +++ b/developer/docs/help/main/credits.md @@ -2,6 +2,6 @@ title: Additional Credits --- -## Code2000, Code2001 and Code2002 fonts +## Code2000, Code2001 and Code2002 fonts © 2006 James Kass. diff --git a/developer/docs/help/main/license.md b/developer/docs/help/main/license.md index 73b788f900..83f45978e9 100644 --- a/developer/docs/help/main/license.md +++ b/developer/docs/help/main/license.md @@ -4,7 +4,7 @@ title: Keyman® Developer License MIT License -Copyright (c) 1994-2018 SIL International +Copyright (c) 1994-2025 SIL Global Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the From 56476057555a38946f6d8a12f3242bfc81667102 Mon Sep 17 00:00:00 2001 From: Meng-Heng Date: Tue, 17 Jun 2025 16:53:48 +0700 Subject: [PATCH 034/262] Revert "@Meng-Heng" This reverts commit 7e60074b9d769db2cbdfa5094911f1e3c5061c9d. --- .../touch-keyboard-tutorial/gestures.md | 26 ------------------ .../touch-layout-gestures.jpg | Bin 45050 -> 0 bytes developer/docs/help/main/credits.md | 2 +- developer/docs/help/main/license.md | 2 +- 4 files changed, 2 insertions(+), 28 deletions(-) delete mode 100644 developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md delete mode 100644 developer/docs/help/images/touch-keyboard-tutorial/touch-layout-gestures.jpg diff --git a/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md b/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md deleted file mode 100644 index d39d86891f..0000000000 --- a/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Using Gestures in Keyman Developer ---- - -Keyman Developer offers **Longpress** keys, **Flicks**, and **Multitaps** for touch keyboard layout. To allow gestures onto your keyboard, go to the Touch layout tab and define the keys as followed: - -Keyman Developer touch layout gestures - -## Tips - -* We can use both **flicks** and **long presses** in a single keyboard. Generally, we recommend only having flicks in one direction--south (down), and long presses work well with this pattern. See, for example, [sil_euro_latin](../../../../keyboard/sil_euro_latin/3.0.3/sil_euro_latin#toc-mobile-keyboard-layout). - * You’ll also notice on sil_euro_latin that the south-flicks on the base (and shift) layers correspond to the numeric/symbol layer, which is also helpful for remembering and discovering. -* **Flicks** in multiple directions are possible, but currently Keyman does not support hints for multiple directions, so they are not very discoverable. -* **Long presses** are more discoverable, but **flicks** are faster. We suggest using south flicks along with long presses, and including the flick output in the long press for maximum discoverability and optimum ergonomics. - -## Caution - -* **Flicks** near screen edges tend to be problematic--once you drag off the screen, then Keyman can no longer tell that you are flicking (or you may end up activating system controls on the bottom edge): - * On the bottom row, south flicks are not recommended. - * On the top row, we have the banner space reserved in order to be able to support flicks and long presses up into that region of the screen--not an ideal solution, but it works. - * Left/right screen edges, horizontal flicks are not the best practice. - -## See also - -* The [Winchus keyboard](../../../../keyboard/winchus/1.4/winchus#guia-rapida-celular) -* The [GFF Amharic keyboard](../../../../keyboard/gff_amharic/3.2.1/gff_amharic#toc-using-this-keyboard) \ No newline at end of file diff --git a/developer/docs/help/images/touch-keyboard-tutorial/touch-layout-gestures.jpg b/developer/docs/help/images/touch-keyboard-tutorial/touch-layout-gestures.jpg deleted file mode 100644 index 1d407cc22a61da77eb8305fb0a3a7dc91f91f8f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45050 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 diff --git a/developer/docs/help/main/credits.md b/developer/docs/help/main/credits.md index a4c2124870..f2c8a027c5 100644 --- a/developer/docs/help/main/credits.md +++ b/developer/docs/help/main/credits.md @@ -2,6 +2,6 @@ title: Additional Credits --- -## Code2000, Code2001 and Code2002 fonts +## Code2000, Code2001 and Code2002 fonts © 2006 James Kass. diff --git a/developer/docs/help/main/license.md b/developer/docs/help/main/license.md index 83f45978e9..73b788f900 100644 --- a/developer/docs/help/main/license.md +++ b/developer/docs/help/main/license.md @@ -4,7 +4,7 @@ title: Keyman® Developer License MIT License -Copyright (c) 1994-2025 SIL Global +Copyright (c) 1994-2018 SIL International Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the From 28ecc26c9e7723290d7d0ed1f6cd9f2666d7ef33 Mon Sep 17 00:00:00 2001 From: Meng-Heng Date: Tue, 17 Jun 2025 16:54:11 +0700 Subject: [PATCH 035/262] Reapply "@Meng-Heng" This reverts commit 56476057555a38946f6d8a12f3242bfc81667102. --- .../touch-keyboard-tutorial/gestures.md | 26 ++++++++++++++++++ .../touch-layout-gestures.jpg | Bin 0 -> 45050 bytes developer/docs/help/main/credits.md | 2 +- developer/docs/help/main/license.md | 2 +- 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md create mode 100644 developer/docs/help/images/touch-keyboard-tutorial/touch-layout-gestures.jpg diff --git a/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md b/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md new file mode 100644 index 0000000000..d39d86891f --- /dev/null +++ b/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md @@ -0,0 +1,26 @@ +--- +title: Using Gestures in Keyman Developer +--- + +Keyman Developer offers **Longpress** keys, **Flicks**, and **Multitaps** for touch keyboard layout. To allow gestures onto your keyboard, go to the Touch layout tab and define the keys as followed: + +Keyman Developer touch layout gestures + +## Tips + +* We can use both **flicks** and **long presses** in a single keyboard. Generally, we recommend only having flicks in one direction--south (down), and long presses work well with this pattern. See, for example, [sil_euro_latin](../../../../keyboard/sil_euro_latin/3.0.3/sil_euro_latin#toc-mobile-keyboard-layout). + * You’ll also notice on sil_euro_latin that the south-flicks on the base (and shift) layers correspond to the numeric/symbol layer, which is also helpful for remembering and discovering. +* **Flicks** in multiple directions are possible, but currently Keyman does not support hints for multiple directions, so they are not very discoverable. +* **Long presses** are more discoverable, but **flicks** are faster. We suggest using south flicks along with long presses, and including the flick output in the long press for maximum discoverability and optimum ergonomics. + +## Caution + +* **Flicks** near screen edges tend to be problematic--once you drag off the screen, then Keyman can no longer tell that you are flicking (or you may end up activating system controls on the bottom edge): + * On the bottom row, south flicks are not recommended. + * On the top row, we have the banner space reserved in order to be able to support flicks and long presses up into that region of the screen--not an ideal solution, but it works. + * Left/right screen edges, horizontal flicks are not the best practice. + +## See also + +* The [Winchus keyboard](../../../../keyboard/winchus/1.4/winchus#guia-rapida-celular) +* The [GFF Amharic keyboard](../../../../keyboard/gff_amharic/3.2.1/gff_amharic#toc-using-this-keyboard) \ No newline at end of file diff --git a/developer/docs/help/images/touch-keyboard-tutorial/touch-layout-gestures.jpg b/developer/docs/help/images/touch-keyboard-tutorial/touch-layout-gestures.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1d407cc22a61da77eb8305fb0a3a7dc91f91f8f1 GIT binary patch literal 45050 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 literal 0 HcmV?d00001 diff --git a/developer/docs/help/main/credits.md b/developer/docs/help/main/credits.md index f2c8a027c5..a4c2124870 100644 --- a/developer/docs/help/main/credits.md +++ b/developer/docs/help/main/credits.md @@ -2,6 +2,6 @@ title: Additional Credits --- -## Code2000, Code2001 and Code2002 fonts +## Code2000, Code2001 and Code2002 fonts © 2006 James Kass. diff --git a/developer/docs/help/main/license.md b/developer/docs/help/main/license.md index 73b788f900..83f45978e9 100644 --- a/developer/docs/help/main/license.md +++ b/developer/docs/help/main/license.md @@ -4,7 +4,7 @@ title: Keyman® Developer License MIT License -Copyright (c) 1994-2018 SIL International +Copyright (c) 1994-2025 SIL Global Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the From 27c6628230aa9455ec1f7ff674d0a1bfdba79606 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 17 Jun 2025 16:15:43 +0200 Subject: [PATCH 036/262] maint(windows): address code review comments Previous commit used wrong if-branch. --- .../publish-developer-to-downloads-keyman-com.ps1 | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/resources/teamcity/developer/publish-developer-to-downloads-keyman-com.ps1 b/resources/teamcity/developer/publish-developer-to-downloads-keyman-com.ps1 index 79c64e24ae..eb99dfb2e4 100644 --- a/resources/teamcity/developer/publish-developer-to-downloads-keyman-com.ps1 +++ b/resources/teamcity/developer/publish-developer-to-downloads-keyman-com.ps1 @@ -48,12 +48,7 @@ mkdir ${upload_path} # # Build Keyman Compiler WINE archive # - -cd bin -# Keyman versions 17.0+; note, use npm install for most modules -copy ..\..\common\schemas\keyboard_info\keyboard_info.schema.json . -& "${7Z_HOME}\7z.exe" a -bd -bb0 ..\${upload_path}\${kmcomp_zip} kmconvert.exe keyboard_info.schema.json xml\layoutbuilder\*.keyman-touch-layout projects\ server\ -cd .. +copy ..\release\${kmcomp_zip} ..\${upload_path}\${kmcomp_zip} # # Copy source files From ed28a3174fc4293438b8884f5c378f971b15e7f5 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 17 Jun 2025 18:24:48 +0200 Subject: [PATCH 037/262] maint(common): move TC configuration for common tests Fixes: #14187 Part-of: #13399 Test-bot: skip --- .../teamcity/common/common-test-common-web.sh | 31 ++++++++++++ .../teamcity/common/common-test-common.sh | 31 ++++++++++++ .../teamcity/common/common-test-core-wasm.sh | 32 ++++++++++++ resources/teamcity/common/common-test-core.sh | 49 +++++++++++++++++++ 4 files changed, 143 insertions(+) create mode 100755 resources/teamcity/common/common-test-common-web.sh create mode 100755 resources/teamcity/common/common-test-common.sh create mode 100755 resources/teamcity/common/common-test-core-wasm.sh create mode 100755 resources/teamcity/common/common-test-core.sh diff --git a/resources/teamcity/common/common-test-common-web.sh b/resources/teamcity/common/common-test-common-web.sh new file mode 100755 index 0000000000..c11b931c99 --- /dev/null +++ b/resources/teamcity/common/common-test-common-web.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. +# +# TC build script to build and run unit tests in /common/web + +# shellcheck disable=SC2164 +# shellcheck disable=SC1091 + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +################################ Main script ################################ + +builder_describe \ + "Run unit tests in /common/web" \ + "all run all actions" \ + "build build and test common/web" + +builder_parse "$@" + +# shellcheck disable=SC2154 +cd "${KEYMAN_ROOT}/common/web" + +if builder_has_action all; then + "${KEYMAN_ROOT}/common/web/build.sh" configure build test +else + builder_run_action build "${KEYMAN_ROOT}/common/web/build.sh" configure build test +fi diff --git a/resources/teamcity/common/common-test-common.sh b/resources/teamcity/common/common-test-common.sh new file mode 100755 index 0000000000..757e8e99db --- /dev/null +++ b/resources/teamcity/common/common-test-common.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. +# +# TC build script to build and test common modules + +# shellcheck disable=SC2164 +# shellcheck disable=SC1091 + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +################################ Main script ################################ + +builder_describe \ + "Run unit tests in /common" \ + "all run all actions" \ + "build build and test common modules" + +builder_parse "$@" + +# shellcheck disable=SC2154 +cd "${KEYMAN_ROOT}/common" + +if builder_has_action all; then + "${KEYMAN_ROOT}/common/build.sh" configure build test +else + builder_run_action build "${KEYMAN_ROOT}/common/build.sh" configure build test +fi diff --git a/resources/teamcity/common/common-test-core-wasm.sh b/resources/teamcity/common/common-test-core-wasm.sh new file mode 100755 index 0000000000..d7c84ca30a --- /dev/null +++ b/resources/teamcity/common/common-test-core-wasm.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. +# +# TC build script to test Keyman Core Desktop on WASM + +# shellcheck disable=SC2164 +# shellcheck disable=SC1091 + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + + +################################ Main script ################################ + +builder_describe \ + "Test Keyman Core on Linux" \ + "all run all actions" \ + "build full meson build and test of Keyman Core for WASM" + +builder_parse "$@" + +# shellcheck disable=SC2154 +cd "${KEYMAN_ROOT}/core" + +if builder_has_action all; then + "${KEYMAN_ROOT}/core/build.sh" configure:wasm build:wasm test:wasm +else + builder_run_action build "${KEYMAN_ROOT}/core/build.sh" configure:wasm build:wasm test:wasm +fi diff --git a/resources/teamcity/common/common-test-core.sh b/resources/teamcity/common/common-test-core.sh new file mode 100755 index 0000000000..0674969ef7 --- /dev/null +++ b/resources/teamcity/common/common-test-core.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. +# +# TC build script to test Keyman Core + +# shellcheck disable=SC2164 +# shellcheck disable=SC1091 +# shellcheck disable=SC2248 + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +# shellcheck disable=SC2154 +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" + +################################ Main script ################################ + +builder_describe \ + "Test Keyman Core" \ + "all run all actions" \ + "build full meson build and test of Keyman Core" + +builder_parse "$@" + +cd "${KEYMAN_ROOT}/core" + +if is_ubuntu; then + ARCH=arch +elif is_windows; then + ARCH=x86 +elif is_macos; then + ARCH=mac +else + builder_echo error "Unknown architecture" + return 1 +fi + +if builder_has_action all; then + "${KEYMAN_ROOT}/core/build.sh" configure:${ARCH} build:${ARCH} test:${ARCH} +else + builder_run_action build "${KEYMAN_ROOT}/core/build.sh" configure:${ARCH} build:${ARCH} test:${ARCH} +fi + +if is_windows; then + "${KEYMAN_ROOT}/core/build.sh" configure:x64 build:x64 test:x64 +fi From deb18b48119d79ef35ae0f45bbb1620def39ad1e Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 16 Jun 2025 14:40:50 +1000 Subject: [PATCH 038/262] maint(resources): add pr-build-status check run GHA Adds a check run to verify the outcome of all builds on the branch, so that we can add branch protection rules. Fixes: #14172 Build-bot: skip --- .github/workflows/pr-build-status.yml | 183 + resources/build/pr-build-status/build.sh | 16 + ...53db37026c95b7ff5a6aa1e8fc-check-runs.json | 384 +++ ...0253db37026c95b7ff5a6aa1e8fc-statuses.json | 3005 +++++++++++++++++ ...066c9dcf71d25afa4c66efa17f-check-runs.json | 1 + ...c6066c9dcf71d25afa4c66efa17f-statuses.json | 1 + ...a56a8f2a534b42a49ee1f21ca1-check-runs.json | 246 ++ ...9fa56a8f2a534b42a49ee1f21ca1-statuses.json | 2972 ++++++++++++++++ ...f5067b4352a06505369d9038e0-check-runs.json | 246 ++ ...9ef5067b4352a06505369d9038e0-statuses.json | 2906 ++++++++++++++++ ...d6a780d8bcf862380e7a89aefb-check-runs.json | 246 ++ ...85d6a780d8bcf862380e7a89aefb-statuses.json | 35 + .../build/pr-build-status/package-lock.json | 1528 +++++++++ resources/build/pr-build-status/package.json | 20 + .../build/pr-build-status/pr-build-status.mjs | 188 ++ .../pr-build-status/pr-build-status.tests.mjs | 44 + resources/build/run-required-test-builds.sh | 34 +- 17 files changed, 12045 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/pr-build-status.yml create mode 100755 resources/build/pr-build-status/build.sh create mode 100644 resources/build/pr-build-status/fixtures/11a558e926370253db37026c95b7ff5a6aa1e8fc-check-runs.json create mode 100644 resources/build/pr-build-status/fixtures/11a558e926370253db37026c95b7ff5a6aa1e8fc-statuses.json create mode 100644 resources/build/pr-build-status/fixtures/153683cfb007c6066c9dcf71d25afa4c66efa17f-check-runs.json create mode 100644 resources/build/pr-build-status/fixtures/153683cfb007c6066c9dcf71d25afa4c66efa17f-statuses.json create mode 100644 resources/build/pr-build-status/fixtures/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1-check-runs.json create mode 100644 resources/build/pr-build-status/fixtures/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1-statuses.json create mode 100644 resources/build/pr-build-status/fixtures/870d37f56b489ef5067b4352a06505369d9038e0-check-runs.json create mode 100644 resources/build/pr-build-status/fixtures/870d37f56b489ef5067b4352a06505369d9038e0-statuses.json create mode 100644 resources/build/pr-build-status/fixtures/8fd5ccc9026b85d6a780d8bcf862380e7a89aefb-check-runs.json create mode 100644 resources/build/pr-build-status/fixtures/8fd5ccc9026b85d6a780d8bcf862380e7a89aefb-statuses.json create mode 100644 resources/build/pr-build-status/package-lock.json create mode 100644 resources/build/pr-build-status/package.json create mode 100644 resources/build/pr-build-status/pr-build-status.mjs create mode 100644 resources/build/pr-build-status/pr-build-status.tests.mjs diff --git a/.github/workflows/pr-build-status.yml b/.github/workflows/pr-build-status.yml new file mode 100644 index 0000000000..f8de2f7adf --- /dev/null +++ b/.github/workflows/pr-build-status.yml @@ -0,0 +1,183 @@ +name: Keyman Build Summary +on: + # push: + # branches: + # - "maint/resources/14172-pr-build-status-2" + + check_run: + types: [completed] + +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 + // 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 reduceStatuses(statuses) { + 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}; + } 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};; + } else if(status.context == 'API Verification') { + if(!o[status.context]) o[status.context] = {type: 'check', state: status.state}; + } else if(status.context == 'Ubuntu Packaging') { + if(!o[status.context]) o[status.context] = {type: 'build', state: 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 + } 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 + o[status.context] = {type: 'unknown', state: status.state}; + } + return o; + + }, {}); + return filtered_statuses; + } + + // + // Given the collection of status checks we care about, return + // an aggregate status -- error, failed, pending, or success, + // and a summary description + // + function calculateFinalStatus(filtered_statuses) { + 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 + return [ + 'error', `An unknown context ${context} was found, cannot calculate build status.` + ]; + } + 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) { + return ['pending', 'Checks have not yet been triggered ⌛']; + } + + const state = + counts.error ? 'error' : + counts.failed ? 'failed' : + 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.failed, 'failed ❌'); + appendDescription(counts.pending, 'pending ⌛'); + appendDescription(counts.success, 'completed successfully ✅'); + + return [ state, description ]; + } + + async function getCommitStatuses(github, owner, repo, 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) { + 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 ❌']; + } + + const filtered_statuses = reduceStatuses(statuses); + + const result = calculateFinalStatus(filtered_statuses); + return result; + } + + 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) { + // To ensure that + 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 || context.sha; + 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]); diff --git a/resources/build/pr-build-status/build.sh b/resources/build/pr-build-status/build.sh new file mode 100755 index 0000000000..7672bb286c --- /dev/null +++ b/resources/build/pr-build-status/build.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +builder_describe "Test the pr-build-status.yml GHA" test + +builder_parse "$@" + +# TODO: consider generating the .yml from here? + +builder_run_action test npm test + diff --git a/resources/build/pr-build-status/fixtures/11a558e926370253db37026c95b7ff5a6aa1e8fc-check-runs.json b/resources/build/pr-build-status/fixtures/11a558e926370253db37026c95b7ff5a6aa1e8fc-check-runs.json new file mode 100644 index 0000000000..46f4415c17 --- /dev/null +++ b/resources/build/pr-build-status/fixtures/11a558e926370253db37026c95b7ff5a6aa1e8fc-check-runs.json @@ -0,0 +1,384 @@ +[ + { + "id": 44090590145, + "name": "build", + "node_id": "CR_kwDOAY2xT88AAAAKRAEDwQ", + "head_sha": "11a558e926370253db37026c95b7ff5a6aa1e8fc", + "external_id": "389dd74c-044c-5be6-a27d-4b4819c2ff55", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/44090590145", + "html_url": "https://github.com/keymanapp/keyman/actions/runs/15648814574/job/44090590145", + "details_url": "https://github.com/keymanapp/keyman/actions/runs/15648814574/job/44090590145", + "status": "completed", + "conclusion": "skipped", + "started_at": "2025-06-14T05:23:18Z", + "completed_at": "2025-06-14T05:23:18Z", + "output": { + "title": null, + "summary": null, + "text": null, + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/keymanapp/keyman/check-runs/44090590145/annotations" + }, + "check_suite": { + "id": 40118324763 + }, + "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/14196", + "id": 2591938321, + "number": 14196, + "head": { + "ref": "maint/developer/support-buildLevel", + "sha": "825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "maint/common/pr-build-bot", + "sha": "81c941313bcccd8405364731ce61672588285d5d", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + } + } + ] + }, + { + "id": 44090590129, + "name": "triage", + "node_id": "CR_kwDOAY2xT88AAAAKRAEDsQ", + "head_sha": "11a558e926370253db37026c95b7ff5a6aa1e8fc", + "external_id": "d467db85-960e-541c-a8c1-5f09c33904f5", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/44090590129", + "html_url": "https://github.com/keymanapp/keyman/actions/runs/15648814560/job/44090590129", + "details_url": "https://github.com/keymanapp/keyman/actions/runs/15648814560/job/44090590129", + "status": "completed", + "conclusion": "success", + "started_at": "2025-06-14T05:23:21Z", + "completed_at": "2025-06-14T05:23:29Z", + "output": { + "title": null, + "summary": null, + "text": null, + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/keymanapp/keyman/check-runs/44090590129/annotations" + }, + "check_suite": { + "id": 40118324747 + }, + "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/14196", + "id": 2591938321, + "number": 14196, + "head": { + "ref": "maint/developer/support-buildLevel", + "sha": "825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "maint/common/pr-build-bot", + "sha": "81c941313bcccd8405364731ce61672588285d5d", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + } + } + ] + }, + { + "id": 44090589932, + "name": "GitGuardian Security Checks", + "node_id": "CR_kwDOAY2xT88AAAAKRAEC7A", + "head_sha": "11a558e926370253db37026c95b7ff5a6aa1e8fc", + "external_id": "", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/44090589932", + "html_url": "https://github.com/keymanapp/keyman/runs/44090589932", + "details_url": "https://dashboard.gitguardian.com", + "status": "completed", + "conclusion": "success", + "started_at": "2025-06-14T05:23:17Z", + "completed_at": "2025-06-14T05:23:20Z", + "output": { + "title": "No secrets detected ✅", + "summary": "**1** commit was scanned without uncovering any secrets.\n", + "text": "Commit scanned: **1**\n\n\n\n- Pull request #14196: `maint/developer/support-buildLevel` 👉 `maint/common/pr-build-bot`\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/44090589932/annotations" + }, + "check_suite": { + "id": 40118324322 + }, + "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/14196", + "id": 2591938321, + "number": 14196, + "head": { + "ref": "maint/developer/support-buildLevel", + "sha": "825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "maint/common/pr-build-bot", + "sha": "81c941313bcccd8405364731ce61672588285d5d", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc-statuses.json b/resources/build/pr-build-status/fixtures/11a558e926370253db37026c95b7ff5a6aa1e8fc-statuses.json new file mode 100644 index 0000000000..02f629220d --- /dev/null +++ b/resources/build/pr-build-status/fixtures/11a558e926370253db37026c95b7ff5a6aa1e8fc-statuses.json @@ -0,0 +1,3005 @@ +[ + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974475031, + "node_id": "SC_kwDOAY2xT88AAAAIm9m3Fw", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Developer_Test/557042", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-06-14T05:58:17Z", + "updated_at": "2025-06-14T05:58: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974472547, + "node_id": "SC_kwDOAY2xT88AAAAIm9mtYw", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Developer_Test/557042", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-06-14T05:57:36Z", + "updated_at": "2025-06-14T05:57: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974455323, + "node_id": "SC_kwDOAY2xT88AAAAIm9lqGw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:52:43Z", + "updated_at": "2025-06-14T05:52:43Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974455007, + "node_id": "SC_kwDOAY2xT88AAAAIm9lo3w", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Linux_Test_Integration/557030", + "context": "Test:Integration (Keyman - Linux)", + "created_at": "2025-06-14T05:52:37Z", + "updated_at": "2025-06-14T05:52:37Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974447591, + "node_id": "SC_kwDOAY2xT88AAAAIm9lL5w", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:50:28Z", + "updated_at": "2025-06-14T05:50:28Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974447340, + "node_id": "SC_kwDOAY2xT88AAAAIm9lK7A", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Linux_Test_Integration/557030", + "context": "Test:Integration (Keyman - Linux)", + "created_at": "2025-06-14T05:50:24Z", + "updated_at": "2025-06-14T05:50: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974447314, + "node_id": "SC_kwDOAY2xT88AAAAIm9lK0g", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestSamplesAndTestProjects/557041", + "context": "Test: Samples and Test projects (Keyman - Android)", + "created_at": "2025-06-14T05:50:24Z", + "updated_at": "2025-06-14T05:50: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974436093, + "node_id": "SC_kwDOAY2xT88AAAAIm9ke_Q", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:47:10Z", + "updated_at": "2025-06-14T05:47: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974435704, + "node_id": "SC_kwDOAY2xT88AAAAIm9kdeA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestSamplesAndTestProjects/557041", + "context": "Test: Samples and Test projects (Keyman - Android)", + "created_at": "2025-06-14T05:47:04Z", + "updated_at": "2025-06-14T05:47:04Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974435663, + "node_id": "SC_kwDOAY2xT88AAAAIm9kdTw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Mac/557043", + "context": "Test - macOS (Keyman - Developer)", + "created_at": "2025-06-14T05:47:03Z", + "updated_at": "2025-06-14T05:47:03Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974428929, + "node_id": "SC_kwDOAY2xT88AAAAIm9kDAQ", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:45:14Z", + "updated_at": "2025-06-14T05:45: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974428773, + "node_id": "SC_kwDOAY2xT88AAAAIm9kCZQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestPullRequests/557025", + "context": "Test (Keyman - iOS)", + "created_at": "2025-06-14T05:45:12Z", + "updated_at": "2025-06-14T05:45: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974413112, + "node_id": "SC_kwDOAY2xT88AAAAIm9jFOA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:41:03Z", + "updated_at": "2025-06-14T05:41:03Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974413052, + "node_id": "SC_kwDOAY2xT88AAAAIm9jE_A", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:41:02Z", + "updated_at": "2025-06-14T05:41:02Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974412786, + "node_id": "SC_kwDOAY2xT88AAAAIm9jD8g", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Mac/557043", + "context": "Test - macOS (Keyman - Developer)", + "created_at": "2025-06-14T05:40:57Z", + "updated_at": "2025-06-14T05:40:57Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974412759, + "node_id": "SC_kwDOAY2xT88AAAAIm9jD1w", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_macOS/557035", + "context": "Test: Keyman Core - macOS (Common)", + "created_at": "2025-06-14T05:40:57Z", + "updated_at": "2025-06-14T05:40:57Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974412521, + "node_id": "SC_kwDOAY2xT88AAAAIm9jC6Q", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_WASM/557028", + "context": "Test: Keyman Core - WASM (Common)", + "created_at": "2025-06-14T05:40:53Z", + "updated_at": "2025-06-14T05:40: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974397722, + "node_id": "SC_kwDOAY2xT88AAAAIm9iJGg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:36:41Z", + "updated_at": "2025-06-14T05:36: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974397485, + "node_id": "SC_kwDOAY2xT88AAAAIm9iILQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Linux/557044", + "context": "Test - Linux (Keyman - Developer)", + "created_at": "2025-06-14T05:36:37Z", + "updated_at": "2025-06-14T05:36:37Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974394397, + "node_id": "SC_kwDOAY2xT88AAAAIm9h8HQ", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:35:45Z", + "updated_at": "2025-06-14T05:35:45Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974393913, + "node_id": "SC_kwDOAY2xT88AAAAIm9h6OQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_macOS/557035", + "context": "Test: Keyman Core - macOS (Common)", + "created_at": "2025-06-14T05:35:38Z", + "updated_at": "2025-06-14T05:35:38Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974393881, + "node_id": "SC_kwDOAY2xT88AAAAIm9h6GQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_KeymanMac_PullRequests/557034", + "context": "Test (Keyman - macOS)", + "created_at": "2025-06-14T05:35:38Z", + "updated_at": "2025-06-14T05:35:38Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974392050, + "node_id": "SC_kwDOAY2xT88AAAAIm9hy8g", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:35:07Z", + "updated_at": "2025-06-14T05:35:07Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974391695, + "node_id": "SC_kwDOAY2xT88AAAAIm9hxjw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Linux/557045", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-06-14T05:35:02Z", + "updated_at": "2025-06-14T05:35: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974389314, + "node_id": "SC_kwDOAY2xT88AAAAIm9hoQg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:34:21Z", + "updated_at": "2025-06-14T05:34:21Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974389188, + "node_id": "SC_kwDOAY2xT88AAAAIm9hnxA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Linux/557045", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-06-14T05:34:19Z", + "updated_at": "2025-06-14T05:34: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974389160, + "node_id": "SC_kwDOAY2xT88AAAAIm9hnqA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestPullRequests/557040", + "context": "Test (Keyman - Android)", + "created_at": "2025-06-14T05:34:19Z", + "updated_at": "2025-06-14T05:34: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974387372, + "node_id": "SC_kwDOAY2xT88AAAAIm9hgrA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Windows/557038", + "context": "Test: Keyman Core - Windows (Common)", + "created_at": "2025-06-14T05:33:48Z", + "updated_at": "2025-06-14T05:33: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974382289, + "node_id": "SC_kwDOAY2xT88AAAAIm9hM0Q", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:32:25Z", + "updated_at": "2025-06-14T05:32:25Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 36974380961, + "node_id": "SC_kwDOAY2xT88AAAAIm9hHoQ", + "state": "success", + "description": "API verification succeeded", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/15648875895", + "context": "API Verification", + "created_at": "2025-06-14T05:32:07Z", + "updated_at": "2025-06-14T05:32:07Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974379441, + "node_id": "SC_kwDOAY2xT88AAAAIm9hBsQ", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPullRequests/557036", + "context": "Test (Keyman - Windows)", + "created_at": "2025-06-14T05:31:44Z", + "updated_at": "2025-06-14T05:31: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974379408, + "node_id": "SC_kwDOAY2xT88AAAAIm9hBkA", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPullRequests/557036", + "context": "Test (Keyman - Windows)", + "created_at": "2025-06-14T05:31:44Z", + "updated_at": "2025-06-14T05:31: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974377063, + "node_id": "SC_kwDOAY2xT88AAAAIm9g4Zw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:31:10Z", + "updated_at": "2025-06-14T05:31: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974376865, + "node_id": "SC_kwDOAY2xT88AAAAIm9g3oQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_KeymanMac_PullRequests/557034", + "context": "Test (Keyman - macOS)", + "created_at": "2025-06-14T05:31:08Z", + "updated_at": "2025-06-14T05:31:08Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974376840, + "node_id": "SC_kwDOAY2xT88AAAAIm9g3iA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Mac/557032", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-06-14T05:31:08Z", + "updated_at": "2025-06-14T05:31:08Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 36974376415, + "node_id": "SC_kwDOAY2xT88AAAAIm9g13w", + "state": "pending", + "description": "API verification started", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/15648875895", + "context": "API Verification", + "created_at": "2025-06-14T05:31:03Z", + "updated_at": "2025-06-14T05:31:03Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974375823, + "node_id": "SC_kwDOAY2xT88AAAAIm9gzjw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:30:55Z", + "updated_at": "2025-06-14T05:30:55Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 36974375684, + "node_id": "SC_kwDOAY2xT88AAAAIm9gzBA", + "state": "success", + "description": "Package build succeeded", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/15648818384", + "context": "Ubuntu Packaging", + "created_at": "2025-06-14T05:30:53Z", + "updated_at": "2025-06-14T05:30:53Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974375142, + "node_id": "SC_kwDOAY2xT88AAAAIm9gw5g", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:30:46Z", + "updated_at": "2025-06-14T05:30: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 + } + }, + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974374835, + "node_id": "SC_kwDOAY2xT88AAAAIm9gvsw", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Developer_Test/557042", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-06-14T05:30:42Z", + "updated_at": "2025-06-14T05:30:42Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974374814, + "node_id": "SC_kwDOAY2xT88AAAAIm9gvng", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Web/557039", + "context": "Test: Common - Web (Common)", + "created_at": "2025-06-14T05:30:41Z", + "updated_at": "2025-06-14T05:30:41Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974374101, + "node_id": "SC_kwDOAY2xT88AAAAIm9gs1Q", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:30:32Z", + "updated_at": "2025-06-14T05:30:32Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974373619, + "node_id": "SC_kwDOAY2xT88AAAAIm9gq8w", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Linux/557044", + "context": "Test - Linux (Keyman - Developer)", + "created_at": "2025-06-14T05:30:26Z", + "updated_at": "2025-06-14T05:30:26Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974373603, + "node_id": "SC_kwDOAY2xT88AAAAIm9gq4w", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keymanweb_TestPullRequests/557027", + "context": "Test (Keyman - Web)", + "created_at": "2025-06-14T05:30:25Z", + "updated_at": "2025-06-14T05:30: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974373412, + "node_id": "SC_kwDOAY2xT88AAAAIm9gqJA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:30:23Z", + "updated_at": "2025-06-14T05:30:23Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974373339, + "node_id": "SC_kwDOAY2xT88AAAAIm9gp2w", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:30:22Z", + "updated_at": "2025-06-14T05:30:22Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974373229, + "node_id": "SC_kwDOAY2xT88AAAAIm9gpbQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Mac/557032", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-06-14T05:30:20Z", + "updated_at": "2025-06-14T05:30: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974373203, + "node_id": "SC_kwDOAY2xT88AAAAIm9gpUw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestSamplesAndTestProjects/557026", + "context": "Test: Samples and Test Projects (Keyman - iOS)", + "created_at": "2025-06-14T05:30:20Z", + "updated_at": "2025-06-14T05:30: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974373081, + "node_id": "SC_kwDOAY2xT88AAAAIm9go2Q", + "state": "success", + "description": "⚠️ Warning: keymanweb.js is 62 bytes (0%) larger than 19.0.57, now 428,785 bytes", + "target_url": null, + "context": "check/web/file-size", + "created_at": "2025-06-14T05:30:19Z", + "updated_at": "2025-06-14T05:30: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974369712, + "node_id": "SC_kwDOAY2xT88AAAAIm9gbsA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestPullRequests/557040", + "context": "Test (Keyman - Android)", + "created_at": "2025-06-14T05:29:34Z", + "updated_at": "2025-06-14T05:29: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974369686, + "node_id": "SC_kwDOAY2xT88AAAAIm9gblg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Linux/557031", + "context": "Test: Keyman Core - Linux (Common)", + "created_at": "2025-06-14T05:29:33Z", + "updated_at": "2025-06-14T05:29: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974362550, + "node_id": "SC_kwDOAY2xT88AAAAIm9f_tg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:27:38Z", + "updated_at": "2025-06-14T05:27:38Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974361989, + "node_id": "SC_kwDOAY2xT88AAAAIm9f9hQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Web/557039", + "context": "Test: Common - Web (Common)", + "created_at": "2025-06-14T05:27:30Z", + "updated_at": "2025-06-14T05:27:30Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974361954, + "node_id": "SC_kwDOAY2xT88AAAAIm9f9Yg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/557033", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-06-14T05:27:29Z", + "updated_at": "2025-06-14T05:27:29Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974351989, + "node_id": "SC_kwDOAY2xT88AAAAIm9fWdQ", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:24:56Z", + "updated_at": "2025-06-14T05:24:56Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974351881, + "node_id": "SC_kwDOAY2xT88AAAAIm9fWCQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanLinux_TestPullRequests/557029", + "context": "Test (Keyman - Linux)", + "created_at": "2025-06-14T05:24:54Z", + "updated_at": "2025-06-14T05:24: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974351855, + "node_id": "SC_kwDOAY2xT88AAAAIm9fV7w", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Linux/557031", + "context": "Test: Keyman Core - Linux (Common)", + "created_at": "2025-06-14T05:24:54Z", + "updated_at": "2025-06-14T05:24: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974351007, + "node_id": "SC_kwDOAY2xT88AAAAIm9fSnw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:24:41Z", + "updated_at": "2025-06-14T05:24: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974350924, + "node_id": "SC_kwDOAY2xT88AAAAIm9fSTA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Windows/557038", + "context": "Test: Keyman Core - Windows (Common)", + "created_at": "2025-06-14T05:24:39Z", + "updated_at": "2025-06-14T05:24:39Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974350892, + "node_id": "SC_kwDOAY2xT88AAAAIm9fSLA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPrRenderOnScreenKeyboards/557037", + "context": "Test: Render On Screen Keyboards (Keyman - Windows)", + "created_at": "2025-06-14T05:24:39Z", + "updated_at": "2025-06-14T05:24:39Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974350149, + "node_id": "SC_kwDOAY2xT88AAAAIm9fPRQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPullRequests/557036", + "context": "Test (Keyman - Windows)", + "created_at": "2025-06-14T05:24:27Z", + "updated_at": "2025-06-14T05:24: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 36974348090, + "node_id": "SC_kwDOAY2xT88AAAAIm9fHOg", + "state": "pending", + "description": "Ubuntu packaging started", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/15648818384", + "context": "Ubuntu Packaging", + "created_at": "2025-06-14T05:24:00Z", + "updated_at": "2025-06-14T05:24:00Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347889, + "node_id": "SC_kwDOAY2xT88AAAAIm9fGcQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanLinux_TestPullRequests/557029", + "context": "Test (Keyman - Linux)", + "created_at": "2025-06-14T05:23:57Z", + "updated_at": "2025-06-14T05:23:57Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347861, + "node_id": "SC_kwDOAY2xT88AAAAIm9fGVQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557045", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-06-14T05:23:56Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347833, + "node_id": "SC_kwDOAY2xT88AAAAIm9fGOQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557044", + "context": "Test - Linux (Keyman - Developer)", + "created_at": "2025-06-14T05:23:56Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347805, + "node_id": "SC_kwDOAY2xT88AAAAIm9fGHQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557043", + "context": "Test - macOS (Keyman - Developer)", + "created_at": "2025-06-14T05:23:55Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347765, + "node_id": "SC_kwDOAY2xT88AAAAIm9fF9Q", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557042", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-06-14T05:23:55Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347736, + "node_id": "SC_kwDOAY2xT88AAAAIm9fF2A", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557041", + "context": "Test: Samples and Test projects (Keyman - Android)", + "created_at": "2025-06-14T05:23:54Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347706, + "node_id": "SC_kwDOAY2xT88AAAAIm9fFug", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557040", + "context": "Test (Keyman - Android)", + "created_at": "2025-06-14T05:23:54Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347679, + "node_id": "SC_kwDOAY2xT88AAAAIm9fFnw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557039", + "context": "Test: Common - Web (Common)", + "created_at": "2025-06-14T05:23:54Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347658, + "node_id": "SC_kwDOAY2xT88AAAAIm9fFig", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557038", + "context": "Test: Keyman Core - Windows (Common)", + "created_at": "2025-06-14T05:23:53Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347638, + "node_id": "SC_kwDOAY2xT88AAAAIm9fFdg", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557036", + "context": "Test (Keyman - Windows)", + "created_at": "2025-06-14T05:23:53Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347608, + "node_id": "SC_kwDOAY2xT88AAAAIm9fFWA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPrRenderOnScreenKeyboards/557037", + "context": "Test: Render On Screen Keyboards (Keyman - Windows)", + "created_at": "2025-06-14T05:23:52Z", + "updated_at": "2025-06-14T05:23:52Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347589, + "node_id": "SC_kwDOAY2xT88AAAAIm9fFRQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPrRenderOnScreenKeyboards/557037", + "context": "Test: Render On Screen Keyboards (Keyman - Windows)", + "created_at": "2025-06-14T05:23:52Z", + "updated_at": "2025-06-14T05:23:52Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347556, + "node_id": "SC_kwDOAY2xT88AAAAIm9fFJA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557035", + "context": "Test: Keyman Core - macOS (Common)", + "created_at": "2025-06-14T05:23:52Z", + "updated_at": "2025-06-14T05:23:52Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347542, + "node_id": "SC_kwDOAY2xT88AAAAIm9fFFg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/557033", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-06-14T05:23:51Z", + "updated_at": "2025-06-14T05:23:51Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347519, + "node_id": "SC_kwDOAY2xT88AAAAIm9fE_w", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557034", + "context": "Test (Keyman - macOS)", + "created_at": "2025-06-14T05:23:51Z", + "updated_at": "2025-06-14T05:23:51Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347502, + "node_id": "SC_kwDOAY2xT88AAAAIm9fE7g", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/557033", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-06-14T05:23:51Z", + "updated_at": "2025-06-14T05:23:51Z", + "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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347461, + "node_id": "SC_kwDOAY2xT88AAAAIm9fExQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557032", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-06-14T05:23:50Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347432, + "node_id": "SC_kwDOAY2xT88AAAAIm9fEqA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557030", + "context": "Test:Integration (Keyman - Linux)", + "created_at": "2025-06-14T05:23:49Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347412, + "node_id": "SC_kwDOAY2xT88AAAAIm9fElA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557031", + "context": "Test: Keyman Core - Linux (Common)", + "created_at": "2025-06-14T05:23:49Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347397, + "node_id": "SC_kwDOAY2xT88AAAAIm9fEhQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_WASM/557028", + "context": "Test: Keyman Core - WASM (Common)", + "created_at": "2025-06-14T05:23:49Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347378, + "node_id": "SC_kwDOAY2xT88AAAAIm9fEcg", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557029", + "context": "Test (Keyman - Linux)", + "created_at": "2025-06-14T05:23:48Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347351, + "node_id": "SC_kwDOAY2xT88AAAAIm9fEVw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_WASM/557028", + "context": "Test: Keyman Core - WASM (Common)", + "created_at": "2025-06-14T05:23:48Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347323, + "node_id": "SC_kwDOAY2xT88AAAAIm9fEOw", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keymanweb_TestPullRequests/557027", + "context": "Test (Keyman - Web)", + "created_at": "2025-06-14T05:23:47Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347304, + "node_id": "SC_kwDOAY2xT88AAAAIm9fEKA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestSamplesAndTestProjects/557026", + "context": "Test: Samples and Test Projects (Keyman - iOS)", + "created_at": "2025-06-14T05:23:47Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347268, + "node_id": "SC_kwDOAY2xT88AAAAIm9fEBA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keymanweb_TestPullRequests/557027", + "context": "Test (Keyman - Web)", + "created_at": "2025-06-14T05:23:46Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347252, + "node_id": "SC_kwDOAY2xT88AAAAIm9fD9A", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestSamplesAndTestProjects/557026", + "context": "Test: Samples and Test Projects (Keyman - iOS)", + "created_at": "2025-06-14T05:23:46Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347236, + "node_id": "SC_kwDOAY2xT88AAAAIm9fD5A", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestPullRequests/557025", + "context": "Test (Keyman - iOS)", + "created_at": "2025-06-14T05:23:46Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36974347211, + "node_id": "SC_kwDOAY2xT88AAAAIm9fDyw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestPullRequests/557025", + "context": "Test (Keyman - iOS)", + "created_at": "2025-06-14T05:23:45Z", + "updated_at": "2025-06-14T05:23: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/11a558e926370253db37026c95b7ff5a6aa1e8fc", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36974345720, + "node_id": "SC_kwDOAY2xT88AAAAIm9e9-A", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-14T05:23:18Z", + "updated_at": "2025-06-14T05:23: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 + } + } +] \ No newline at end of file diff --git a/resources/build/pr-build-status/fixtures/153683cfb007c6066c9dcf71d25afa4c66efa17f-check-runs.json b/resources/build/pr-build-status/fixtures/153683cfb007c6066c9dcf71d25afa4c66efa17f-check-runs.json new file mode 100644 index 0000000000..0637a088a0 --- /dev/null +++ b/resources/build/pr-build-status/fixtures/153683cfb007c6066c9dcf71d25afa4c66efa17f-check-runs.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/resources/build/pr-build-status/fixtures/153683cfb007c6066c9dcf71d25afa4c66efa17f-statuses.json b/resources/build/pr-build-status/fixtures/153683cfb007c6066c9dcf71d25afa4c66efa17f-statuses.json new file mode 100644 index 0000000000..0637a088a0 --- /dev/null +++ b/resources/build/pr-build-status/fixtures/153683cfb007c6066c9dcf71d25afa4c66efa17f-statuses.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/resources/build/pr-build-status/fixtures/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1-check-runs.json b/resources/build/pr-build-status/fixtures/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1-check-runs.json new file mode 100644 index 0000000000..6333cf0c88 --- /dev/null +++ b/resources/build/pr-build-status/fixtures/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1-check-runs.json @@ -0,0 +1,246 @@ +[ + { + "id": 44134134750, + "name": "triage", + "node_id": "CR_kwDOAY2xT88AAAAKRplz3g", + "head_sha": "825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "external_id": "aece869f-5587-5930-867f-8649375f3d24", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/44134134750", + "html_url": "https://github.com/keymanapp/keyman/actions/runs/15667765443/job/44134134750", + "details_url": "https://github.com/keymanapp/keyman/actions/runs/15667765443/job/44134134750", + "status": "completed", + "conclusion": "success", + "started_at": "2025-06-15T21:54:36Z", + "completed_at": "2025-06-15T21:54:41Z", + "output": { + "title": null, + "summary": null, + "text": null, + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/keymanapp/keyman/check-runs/44134134750/annotations" + }, + "check_suite": { + "id": 40159371625 + }, + "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/14196", + "id": 2591938321, + "number": 14196, + "head": { + "ref": "maint/developer/support-buildLevel", + "sha": "825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "maint/common/pr-build-bot", + "sha": "81c941313bcccd8405364731ce61672588285d5d", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + } + } + ] + }, + { + "id": 44134134737, + "name": "GitGuardian Security Checks", + "node_id": "CR_kwDOAY2xT88AAAAKRplz0Q", + "head_sha": "825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "external_id": "", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/44134134737", + "html_url": "https://github.com/keymanapp/keyman/runs/44134134737", + "details_url": "https://dashboard.gitguardian.com", + "status": "completed", + "conclusion": "success", + "started_at": "2025-06-15T21:54:32Z", + "completed_at": "2025-06-15T21:54:44Z", + "output": { + "title": "No secrets detected ✅", + "summary": "**5** commits were scanned without uncovering any secrets.\n", + "text": "Commits scanned: **5**\n\n\n\n- Pull request #14196: `maint/developer/support-buildLevel` 👉 `maint/common/pr-build-bot`\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/44134134737/annotations" + }, + "check_suite": { + "id": 40159371205 + }, + "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/14196", + "id": 2591938321, + "number": 14196, + "head": { + "ref": "maint/developer/support-buildLevel", + "sha": "825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "maint/common/pr-build-bot", + "sha": "81c941313bcccd8405364731ce61672588285d5d", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1-statuses.json b/resources/build/pr-build-status/fixtures/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1-statuses.json new file mode 100644 index 0000000000..1f4182dc20 --- /dev/null +++ b/resources/build/pr-build-status/fixtures/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1-statuses.json @@ -0,0 +1,2972 @@ +[ + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984353753, + "node_id": "SC_kwDOAY2xT88AAAAInHBz2Q", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:41:11Z", + "updated_at": "2025-06-15T22:41: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984353236, + "node_id": "SC_kwDOAY2xT88AAAAInHBx1A", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Developer_Test/557231", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-06-15T22:41:03Z", + "updated_at": "2025-06-15T22:41:03Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984254223, + "node_id": "SC_kwDOAY2xT88AAAAInG7vDw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:18:11Z", + "updated_at": "2025-06-15T22:18: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984254010, + "node_id": "SC_kwDOAY2xT88AAAAInG7uOg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Mac/557232", + "context": "Test - macOS (Keyman - Developer)", + "created_at": "2025-06-15T22:18:08Z", + "updated_at": "2025-06-15T22:18:08Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984252185, + "node_id": "SC_kwDOAY2xT88AAAAInG7nGQ", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:17:41Z", + "updated_at": "2025-06-15T22:17: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984251922, + "node_id": "SC_kwDOAY2xT88AAAAInG7mEg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestPullRequests/557214", + "context": "Test (Keyman - iOS)", + "created_at": "2025-06-15T22:17:38Z", + "updated_at": "2025-06-15T22:17:38Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984248491, + "node_id": "SC_kwDOAY2xT88AAAAInG7Yqw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:16:51Z", + "updated_at": "2025-06-15T22:16:51Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984247979, + "node_id": "SC_kwDOAY2xT88AAAAInG7Wqw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Windows/557227", + "context": "Test: Keyman Core - Windows (Common)", + "created_at": "2025-06-15T22:16:44Z", + "updated_at": "2025-06-15T22:16: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984233127, + "node_id": "SC_kwDOAY2xT88AAAAInG6cpw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:13:17Z", + "updated_at": "2025-06-15T22:13: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984232856, + "node_id": "SC_kwDOAY2xT88AAAAInG6bmA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_WASM/557217", + "context": "Test: Keyman Core - WASM (Common)", + "created_at": "2025-06-15T22:13:14Z", + "updated_at": "2025-06-15T22:13: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984231138, + "node_id": "SC_kwDOAY2xT88AAAAInG6U4g", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:12:52Z", + "updated_at": "2025-06-15T22:12:52Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984230601, + "node_id": "SC_kwDOAY2xT88AAAAInG6SyQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Mac/557232", + "context": "Test - macOS (Keyman - Developer)", + "created_at": "2025-06-15T22:12:45Z", + "updated_at": "2025-06-15T22:12: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984230570, + "node_id": "SC_kwDOAY2xT88AAAAInG6Sqg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_macOS/557224", + "context": "Test: Keyman Core - macOS (Common)", + "created_at": "2025-06-15T22:12:45Z", + "updated_at": "2025-06-15T22:12: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984220675, + "node_id": "SC_kwDOAY2xT88AAAAInG5sAw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:10:32Z", + "updated_at": "2025-06-15T22:10:32Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984220470, + "node_id": "SC_kwDOAY2xT88AAAAInG5rNg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestSamplesAndTestProjects/557230", + "context": "Test: Samples and Test projects (Keyman - Android)", + "created_at": "2025-06-15T22:10:29Z", + "updated_at": "2025-06-15T22:10:29Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984218600, + "node_id": "SC_kwDOAY2xT88AAAAInG5j6A", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:10:08Z", + "updated_at": "2025-06-15T22:10:08Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984217727, + "node_id": "SC_kwDOAY2xT88AAAAInG5gfw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Linux_Test_Integration/557219", + "context": "Test:Integration (Keyman - Linux)", + "created_at": "2025-06-15T22:09:57Z", + "updated_at": "2025-06-15T22:09:57Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984211579, + "node_id": "SC_kwDOAY2xT88AAAAInG5Iew", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:08:35Z", + "updated_at": "2025-06-15T22:08: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984211442, + "node_id": "SC_kwDOAY2xT88AAAAInG5H8g", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Linux/557233", + "context": "Test - Linux (Keyman - Developer)", + "created_at": "2025-06-15T22:08:33Z", + "updated_at": "2025-06-15T22:08: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984207708, + "node_id": "SC_kwDOAY2xT88AAAAInG45XA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:07:48Z", + "updated_at": "2025-06-15T22:07:48Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984207645, + "node_id": "SC_kwDOAY2xT88AAAAInG45HQ", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:07:47Z", + "updated_at": "2025-06-15T22:07: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984207493, + "node_id": "SC_kwDOAY2xT88AAAAInG44hQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_macOS/557224", + "context": "Test: Keyman Core - macOS (Common)", + "created_at": "2025-06-15T22:07:46Z", + "updated_at": "2025-06-15T22:07: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984207460, + "node_id": "SC_kwDOAY2xT88AAAAInG44ZA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_KeymanMac_PullRequests/557223", + "context": "Test (Keyman - macOS)", + "created_at": "2025-06-15T22:07:46Z", + "updated_at": "2025-06-15T22:07: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984207050, + "node_id": "SC_kwDOAY2xT88AAAAInG42yg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Linux_Test_Integration/557219", + "context": "Test:Integration (Keyman - Linux)", + "created_at": "2025-06-15T22:07:42Z", + "updated_at": "2025-06-15T22:07:42Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984207015, + "node_id": "SC_kwDOAY2xT88AAAAInG42pw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Linux/557234", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-06-15T22:07:42Z", + "updated_at": "2025-06-15T22:07:42Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984204314, + "node_id": "SC_kwDOAY2xT88AAAAInG4sGg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:07:09Z", + "updated_at": "2025-06-15T22:07: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984203436, + "node_id": "SC_kwDOAY2xT88AAAAInG4orA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Linux/557234", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-06-15T22:06:59Z", + "updated_at": "2025-06-15T22:06:59Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984203395, + "node_id": "SC_kwDOAY2xT88AAAAInG4ogw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestPullRequests/557229", + "context": "Test (Keyman - Android)", + "created_at": "2025-06-15T22:06:59Z", + "updated_at": "2025-06-15T22:06:59Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984193091, + "node_id": "SC_kwDOAY2xT88AAAAInG4AQw", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestSamplesAndTestProjects/557230", + "context": "Test: Samples and Test projects (Keyman - Android)", + "created_at": "2025-06-15T22:04:49Z", + "updated_at": "2025-06-15T22:04: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984193063, + "node_id": "SC_kwDOAY2xT88AAAAInG4AJw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPrRenderOnScreenKeyboards/557226", + "context": "Test: Render On Screen Keyboards (Keyman - Windows)", + "created_at": "2025-06-15T22:04:48Z", + "updated_at": "2025-06-15T22:04: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984191748, + "node_id": "SC_kwDOAY2xT88AAAAInG37BA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:04:32Z", + "updated_at": "2025-06-15T22:04:32Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 36984191351, + "node_id": "SC_kwDOAY2xT88AAAAInG35dw", + "state": "success", + "description": "API verification succeeded", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/15667824216", + "context": "API Verification", + "created_at": "2025-06-15T22:04:26Z", + "updated_at": "2025-06-15T22:04: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984190410, + "node_id": "SC_kwDOAY2xT88AAAAInG31yg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:04:14Z", + "updated_at": "2025-06-15T22:04: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984190004, + "node_id": "SC_kwDOAY2xT88AAAAInG30NA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPrRenderOnScreenKeyboards/557226", + "context": "Test: Render On Screen Keyboards (Keyman - Windows)", + "created_at": "2025-06-15T22:04:09Z", + "updated_at": "2025-06-15T22:04: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984189970, + "node_id": "SC_kwDOAY2xT88AAAAInG30Eg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPullRequests/557225", + "context": "Test (Keyman - Windows)", + "created_at": "2025-06-15T22:04:09Z", + "updated_at": "2025-06-15T22:04: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 36984187184, + "node_id": "SC_kwDOAY2xT88AAAAInG3pMA", + "state": "pending", + "description": "API verification started", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/15667824216", + "context": "API Verification", + "created_at": "2025-06-15T22:03:30Z", + "updated_at": "2025-06-15T22:03:30Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984186913, + "node_id": "SC_kwDOAY2xT88AAAAInG3oIQ", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:03:26Z", + "updated_at": "2025-06-15T22:03:26Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984186835, + "node_id": "SC_kwDOAY2xT88AAAAInG3n0w", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:03:25Z", + "updated_at": "2025-06-15T22:03:25Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 36984186600, + "node_id": "SC_kwDOAY2xT88AAAAInG3m6A", + "state": "success", + "description": "Package build succeeded", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/15667775701", + "context": "Ubuntu Packaging", + "created_at": "2025-06-15T22:03:21Z", + "updated_at": "2025-06-15T22:03:21Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984186234, + "node_id": "SC_kwDOAY2xT88AAAAInG3leg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_KeymanMac_PullRequests/557223", + "context": "Test (Keyman - macOS)", + "created_at": "2025-06-15T22:03:17Z", + "updated_at": "2025-06-15T22:03: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984186202, + "node_id": "SC_kwDOAY2xT88AAAAInG3lWg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Mac/557221", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-06-15T22:03:17Z", + "updated_at": "2025-06-15T22:03: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984185678, + "node_id": "SC_kwDOAY2xT88AAAAInG3jTg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:03:11Z", + "updated_at": "2025-06-15T22:03: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984184605, + "node_id": "SC_kwDOAY2xT88AAAAInG3fHQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Linux/557233", + "context": "Test - Linux (Keyman - Developer)", + "created_at": "2025-06-15T22:02:56Z", + "updated_at": "2025-06-15T22:02: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984184566, + "node_id": "SC_kwDOAY2xT88AAAAInG3e9g", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keymanweb_TestPullRequests/557216", + "context": "Test (Keyman - Web)", + "created_at": "2025-06-15T22:02:55Z", + "updated_at": "2025-06-15T22:02: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984184251, + "node_id": "SC_kwDOAY2xT88AAAAInG3duw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:02:51Z", + "updated_at": "2025-06-15T22:02:51Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984184103, + "node_id": "SC_kwDOAY2xT88AAAAInG3dJw", + "state": "success", + "description": "⚠️ Warning: keymanweb.js is 62 bytes (0%) larger than 19.0.57, now 428,785 bytes", + "target_url": null, + "context": "check/web/file-size", + "created_at": "2025-06-15T22:02:49Z", + "updated_at": "2025-06-15T22:02: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984183056, + "node_id": "SC_kwDOAY2xT88AAAAInG3ZEA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:02:35Z", + "updated_at": "2025-06-15T22:02: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984182886, + "node_id": "SC_kwDOAY2xT88AAAAInG3YZg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Mac/557221", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-06-15T22:02:33Z", + "updated_at": "2025-06-15T22:02: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984182860, + "node_id": "SC_kwDOAY2xT88AAAAInG3YTA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestSamplesAndTestProjects/557215", + "context": "Test: Samples and Test Projects (Keyman - iOS)", + "created_at": "2025-06-15T22:02:32Z", + "updated_at": "2025-06-15T22:02:32Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984181495, + "node_id": "SC_kwDOAY2xT88AAAAInG3S9w", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:02:17Z", + "updated_at": "2025-06-15T22:02: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984181448, + "node_id": "SC_kwDOAY2xT88AAAAInG3SyA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Developer_Test/557231", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-06-15T22:02:17Z", + "updated_at": "2025-06-15T22:02: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984181302, + "node_id": "SC_kwDOAY2xT88AAAAInG3SNg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestPullRequests/557229", + "context": "Test (Keyman - Android)", + "created_at": "2025-06-15T22:02:15Z", + "updated_at": "2025-06-15T22:02: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984181257, + "node_id": "SC_kwDOAY2xT88AAAAInG3SCQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Web/557228", + "context": "Test: Common - Web (Common)", + "created_at": "2025-06-15T22:02:15Z", + "updated_at": "2025-06-15T22:02: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984178388, + "node_id": "SC_kwDOAY2xT88AAAAInG3G1A", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Web/557228", + "context": "Test: Common - Web (Common)", + "created_at": "2025-06-15T22:01:38Z", + "updated_at": "2025-06-15T22:01:38Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984178364, + "node_id": "SC_kwDOAY2xT88AAAAInG3GvA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Linux/557220", + "context": "Test: Keyman Core - Linux (Common)", + "created_at": "2025-06-15T22:01:38Z", + "updated_at": "2025-06-15T22:01:38Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984170092, + "node_id": "SC_kwDOAY2xT88AAAAInG2mbA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T22:00:10Z", + "updated_at": "2025-06-15T22:00: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984169654, + "node_id": "SC_kwDOAY2xT88AAAAInG2ktg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Windows/557227", + "context": "Test: Keyman Core - Windows (Common)", + "created_at": "2025-06-15T22:00:06Z", + "updated_at": "2025-06-15T22:00:06Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984169587, + "node_id": "SC_kwDOAY2xT88AAAAInG2kcw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/557222", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-06-15T22:00:06Z", + "updated_at": "2025-06-15T22:00:06Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984159445, + "node_id": "SC_kwDOAY2xT88AAAAInG181Q", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Linux/557220", + "context": "Test: Keyman Core - Linux (Common)", + "created_at": "2025-06-15T21:57:25Z", + "updated_at": "2025-06-15T21:57: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984159423, + "node_id": "SC_kwDOAY2xT88AAAAInG18vw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanLinux_TestPullRequests/557218", + "context": "Test (Keyman - Linux)", + "created_at": "2025-06-15T21:57:25Z", + "updated_at": "2025-06-15T21:57: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 36984156173, + "node_id": "SC_kwDOAY2xT88AAAAInG1wDQ", + "state": "pending", + "description": "Ubuntu packaging started", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/15667775701", + "context": "Ubuntu Packaging", + "created_at": "2025-06-15T21:56:36Z", + "updated_at": "2025-06-15T21:56:36Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155853, + "node_id": "SC_kwDOAY2xT88AAAAInG1uzQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanLinux_TestPullRequests/557218", + "context": "Test (Keyman - Linux)", + "created_at": "2025-06-15T21:56:30Z", + "updated_at": "2025-06-15T21:56:30Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155827, + "node_id": "SC_kwDOAY2xT88AAAAInG1usw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557234", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-06-15T21:56:30Z", + "updated_at": "2025-06-15T21:56:30Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155798, + "node_id": "SC_kwDOAY2xT88AAAAInG1ulg", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557233", + "context": "Test - Linux (Keyman - Developer)", + "created_at": "2025-06-15T21:56:29Z", + "updated_at": "2025-06-15T21:56:29Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155777, + "node_id": "SC_kwDOAY2xT88AAAAInG1ugQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557232", + "context": "Test - macOS (Keyman - Developer)", + "created_at": "2025-06-15T21:56:29Z", + "updated_at": "2025-06-15T21:56:29Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155742, + "node_id": "SC_kwDOAY2xT88AAAAInG1uXg", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557231", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-06-15T21:56:28Z", + "updated_at": "2025-06-15T21:56:28Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155711, + "node_id": "SC_kwDOAY2xT88AAAAInG1uPw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557230", + "context": "Test: Samples and Test projects (Keyman - Android)", + "created_at": "2025-06-15T21:56:28Z", + "updated_at": "2025-06-15T21:56:28Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155689, + "node_id": "SC_kwDOAY2xT88AAAAInG1uKQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557229", + "context": "Test (Keyman - Android)", + "created_at": "2025-06-15T21:56:28Z", + "updated_at": "2025-06-15T21:56:28Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155669, + "node_id": "SC_kwDOAY2xT88AAAAInG1uFQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557228", + "context": "Test: Common - Web (Common)", + "created_at": "2025-06-15T21:56:27Z", + "updated_at": "2025-06-15T21: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155637, + "node_id": "SC_kwDOAY2xT88AAAAInG1t9Q", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557227", + "context": "Test: Keyman Core - Windows (Common)", + "created_at": "2025-06-15T21:56:27Z", + "updated_at": "2025-06-15T21: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155605, + "node_id": "SC_kwDOAY2xT88AAAAInG1t1Q", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557226", + "context": "Test: Render On Screen Keyboards (Keyman - Windows)", + "created_at": "2025-06-15T21:56:26Z", + "updated_at": "2025-06-15T21:56:26Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155570, + "node_id": "SC_kwDOAY2xT88AAAAInG1tsg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPullRequests/557225", + "context": "Test (Keyman - Windows)", + "created_at": "2025-06-15T21:56:26Z", + "updated_at": "2025-06-15T21:56:26Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155549, + "node_id": "SC_kwDOAY2xT88AAAAInG1tnQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPullRequests/557225", + "context": "Test (Keyman - Windows)", + "created_at": "2025-06-15T21:56:26Z", + "updated_at": "2025-06-15T21:56:26Z", + "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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155522, + "node_id": "SC_kwDOAY2xT88AAAAInG1tgg", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557224", + "context": "Test: Keyman Core - macOS (Common)", + "created_at": "2025-06-15T21:56:25Z", + "updated_at": "2025-06-15T21:56: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155500, + "node_id": "SC_kwDOAY2xT88AAAAInG1tbA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/557222", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-06-15T21:56:25Z", + "updated_at": "2025-06-15T21:56: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155480, + "node_id": "SC_kwDOAY2xT88AAAAInG1tWA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557223", + "context": "Test (Keyman - macOS)", + "created_at": "2025-06-15T21:56:24Z", + "updated_at": "2025-06-15T21:56: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155462, + "node_id": "SC_kwDOAY2xT88AAAAInG1tRg", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/557222", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-06-15T21:56:24Z", + "updated_at": "2025-06-15T21:56: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155426, + "node_id": "SC_kwDOAY2xT88AAAAInG1tIg", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557221", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-06-15T21:56:23Z", + "updated_at": "2025-06-15T21:56: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155407, + "node_id": "SC_kwDOAY2xT88AAAAInG1tDw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557220", + "context": "Test: Keyman Core - Linux (Common)", + "created_at": "2025-06-15T21:56:23Z", + "updated_at": "2025-06-15T21:56: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155384, + "node_id": "SC_kwDOAY2xT88AAAAInG1s-A", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557219", + "context": "Test:Integration (Keyman - Linux)", + "created_at": "2025-06-15T21:56:23Z", + "updated_at": "2025-06-15T21:56: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155354, + "node_id": "SC_kwDOAY2xT88AAAAInG1s2g", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557218", + "context": "Test (Keyman - Linux)", + "created_at": "2025-06-15T21:56:22Z", + "updated_at": "2025-06-15T21:56: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155338, + "node_id": "SC_kwDOAY2xT88AAAAInG1syg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_WASM/557217", + "context": "Test: Keyman Core - WASM (Common)", + "created_at": "2025-06-15T21:56:22Z", + "updated_at": "2025-06-15T21:56: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155309, + "node_id": "SC_kwDOAY2xT88AAAAInG1srQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_WASM/557217", + "context": "Test: Keyman Core - WASM (Common)", + "created_at": "2025-06-15T21:56:22Z", + "updated_at": "2025-06-15T21:56: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155280, + "node_id": "SC_kwDOAY2xT88AAAAInG1skA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestSamplesAndTestProjects/557215", + "context": "Test: Samples and Test Projects (Keyman - iOS)", + "created_at": "2025-06-15T21:56:21Z", + "updated_at": "2025-06-15T21:56: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155248, + "node_id": "SC_kwDOAY2xT88AAAAInG1scA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keymanweb_TestPullRequests/557216", + "context": "Test (Keyman - Web)", + "created_at": "2025-06-15T21:56:21Z", + "updated_at": "2025-06-15T21:56: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155224, + "node_id": "SC_kwDOAY2xT88AAAAInG1sWA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keymanweb_TestPullRequests/557216", + "context": "Test (Keyman - Web)", + "created_at": "2025-06-15T21:56:20Z", + "updated_at": "2025-06-15T21:56: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155205, + "node_id": "SC_kwDOAY2xT88AAAAInG1sRQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestSamplesAndTestProjects/557215", + "context": "Test: Samples and Test Projects (Keyman - iOS)", + "created_at": "2025-06-15T21:56:20Z", + "updated_at": "2025-06-15T21:56: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155182, + "node_id": "SC_kwDOAY2xT88AAAAInG1sLg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestPullRequests/557214", + "context": "Test (Keyman - iOS)", + "created_at": "2025-06-15T21:56:19Z", + "updated_at": "2025-06-15T21:56: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36984155164, + "node_id": "SC_kwDOAY2xT88AAAAInG1sHA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557214", + "context": "Test (Keyman - iOS)", + "created_at": "2025-06-15T21:56:19Z", + "updated_at": "2025-06-15T21:56: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/825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36984148130, + "node_id": "SC_kwDOAY2xT88AAAAInG1Qog", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T21:54:33Z", + "updated_at": "2025-06-15T21:54:33Z", + "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/fixtures/870d37f56b489ef5067b4352a06505369d9038e0-check-runs.json b/resources/build/pr-build-status/fixtures/870d37f56b489ef5067b4352a06505369d9038e0-check-runs.json new file mode 100644 index 0000000000..9b31414480 --- /dev/null +++ b/resources/build/pr-build-status/fixtures/870d37f56b489ef5067b4352a06505369d9038e0-check-runs.json @@ -0,0 +1,246 @@ +[ + { + "id": 44131811673, + "name": "GitGuardian Security Checks", + "node_id": "CR_kwDOAY2xT88AAAAKRnYBWQ", + "head_sha": "870d37f56b489ef5067b4352a06505369d9038e0", + "external_id": "", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/44131811673", + "html_url": "https://github.com/keymanapp/keyman/runs/44131811673", + "details_url": "https://dashboard.gitguardian.com", + "status": "completed", + "conclusion": "success", + "started_at": "2025-06-15T19:40:57Z", + "completed_at": "2025-06-15T19:41:28Z", + "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 #14196: `maint/developer/support-buildLevel` 👉 `maint/common/pr-build-bot`\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/44131811673/annotations" + }, + "check_suite": { + "id": 40157100674 + }, + "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/14196", + "id": 2591938321, + "number": 14196, + "head": { + "ref": "maint/developer/support-buildLevel", + "sha": "825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "maint/common/pr-build-bot", + "sha": "81c941313bcccd8405364731ce61672588285d5d", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + } + } + ] + }, + { + "id": 44131810574, + "name": "triage", + "node_id": "CR_kwDOAY2xT88AAAAKRnX9Dg", + "head_sha": "870d37f56b489ef5067b4352a06505369d9038e0", + "external_id": "3d143feb-b3d7-59b2-ae1b-fe551167c2b4", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/44131810574", + "html_url": "https://github.com/keymanapp/keyman/actions/runs/15666730139/job/44131810574", + "details_url": "https://github.com/keymanapp/keyman/actions/runs/15666730139/job/44131810574", + "status": "completed", + "conclusion": "success", + "started_at": "2025-06-15T19:40:57Z", + "completed_at": "2025-06-15T19:41:04Z", + "output": { + "title": null, + "summary": null, + "text": null, + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/keymanapp/keyman/check-runs/44131810574/annotations" + }, + "check_suite": { + "id": 40157101634 + }, + "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/14196", + "id": 2591938321, + "number": 14196, + "head": { + "ref": "maint/developer/support-buildLevel", + "sha": "825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "maint/common/pr-build-bot", + "sha": "81c941313bcccd8405364731ce61672588285d5d", + "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/870d37f56b489ef5067b4352a06505369d9038e0-statuses.json b/resources/build/pr-build-status/fixtures/870d37f56b489ef5067b4352a06505369d9038e0-statuses.json new file mode 100644 index 0000000000..8d1f51ab95 --- /dev/null +++ b/resources/build/pr-build-status/fixtures/870d37f56b489ef5067b4352a06505369d9038e0-statuses.json @@ -0,0 +1,2906 @@ +[ + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983658542, + "node_id": "SC_kwDOAY2xT88AAAAInGXYLg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Mac/557139", + "context": "Test - macOS (Keyman - Developer)", + "created_at": "2025-06-15T20:01:57Z", + "updated_at": "2025-06-15T20:01:57Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983632832, + "node_id": "SC_kwDOAY2xT88AAAAInGVzwA", + "state": "error", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_WASM/557124", + "context": "Test: Keyman Core - WASM (Common)", + "created_at": "2025-06-15T19:56:39Z", + "updated_at": "2025-06-15T19:56:39Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983632813, + "node_id": "SC_kwDOAY2xT88AAAAInGVzrQ", + "state": "error", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestPullRequests/557136", + "context": "Test (Keyman - Android)", + "created_at": "2025-06-15T19:56:39Z", + "updated_at": "2025-06-15T19:56:39Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983632795, + "node_id": "SC_kwDOAY2xT88AAAAInGVzmw", + "state": "error", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Windows/557134", + "context": "Test: Keyman Core - Windows (Common)", + "created_at": "2025-06-15T19:56:38Z", + "updated_at": "2025-06-15T19:56:38Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983632768, + "node_id": "SC_kwDOAY2xT88AAAAInGVzgA", + "state": "error", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Developer_Test/557138", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-06-15T19:56:38Z", + "updated_at": "2025-06-15T19:56:38Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983632744, + "node_id": "SC_kwDOAY2xT88AAAAInGVzaA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Mac/557139", + "context": "Test - macOS (Keyman - Developer)", + "created_at": "2025-06-15T19:56:38Z", + "updated_at": "2025-06-15T19:56:38Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983632727, + "node_id": "SC_kwDOAY2xT88AAAAInGVzVw", + "state": "error", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_macOS/557131", + "context": "Test: Keyman Core - macOS (Common)", + "created_at": "2025-06-15T19:56:37Z", + "updated_at": "2025-06-15T19:56:37Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983632705, + "node_id": "SC_kwDOAY2xT88AAAAInGVzQQ", + "state": "error", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestPullRequests/557121", + "context": "Test (Keyman - iOS)", + "created_at": "2025-06-15T19:56:37Z", + "updated_at": "2025-06-15T19:56:37Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983632677, + "node_id": "SC_kwDOAY2xT88AAAAInGVzJQ", + "state": "error", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_WASM/557124", + "context": "Test: Keyman Core - WASM (Common)", + "created_at": "2025-06-15T19:56:36Z", + "updated_at": "2025-06-15T19:56: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983632642, + "node_id": "SC_kwDOAY2xT88AAAAInGVzAg", + "state": "error", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestPullRequests/557136", + "context": "Test (Keyman - Android)", + "created_at": "2025-06-15T19:56:36Z", + "updated_at": "2025-06-15T19:56: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983632616, + "node_id": "SC_kwDOAY2xT88AAAAInGVy6A", + "state": "error", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Windows/557134", + "context": "Test: Keyman Core - Windows (Common)", + "created_at": "2025-06-15T19:56:36Z", + "updated_at": "2025-06-15T19:56: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983632594, + "node_id": "SC_kwDOAY2xT88AAAAInGVy0g", + "state": "error", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestPullRequests/557121", + "context": "Test (Keyman - iOS)", + "created_at": "2025-06-15T19:56:35Z", + "updated_at": "2025-06-15T19:56: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983632571, + "node_id": "SC_kwDOAY2xT88AAAAInGVyuw", + "state": "error", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Developer_Test/557138", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-06-15T19:56:35Z", + "updated_at": "2025-06-15T19:56: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983632552, + "node_id": "SC_kwDOAY2xT88AAAAInGVyqA", + "state": "error", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_macOS/557131", + "context": "Test: Keyman Core - macOS (Common)", + "created_at": "2025-06-15T19:56:35Z", + "updated_at": "2025-06-15T19:56: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983628513, + "node_id": "SC_kwDOAY2xT88AAAAInGVi4Q", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Linux_Test_Integration/557126", + "context": "Test:Integration (Keyman - Linux)", + "created_at": "2025-06-15T19:55:34Z", + "updated_at": "2025-06-15T19:55: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983625834, + "node_id": "SC_kwDOAY2xT88AAAAInGVYag", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Linux/557140", + "context": "Test - Linux (Keyman - Developer)", + "created_at": "2025-06-15T19:54:56Z", + "updated_at": "2025-06-15T19:54: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36983624327, + "node_id": "SC_kwDOAY2xT88AAAAInGVShw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T19:54:35Z", + "updated_at": "2025-06-15T19:54: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983624184, + "node_id": "SC_kwDOAY2xT88AAAAInGVR-A", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_macOS/557131", + "context": "Test: Keyman Core - macOS (Common)", + "created_at": "2025-06-15T19:54:33Z", + "updated_at": "2025-06-15T19:54: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983624159, + "node_id": "SC_kwDOAY2xT88AAAAInGVR3w", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_KeymanMac_PullRequests/557130", + "context": "Test (Keyman - macOS)", + "created_at": "2025-06-15T19:54:32Z", + "updated_at": "2025-06-15T19:54:32Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36983619684, + "node_id": "SC_kwDOAY2xT88AAAAInGVAZA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T19:53:27Z", + "updated_at": "2025-06-15T19:53:27Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983619407, + "node_id": "SC_kwDOAY2xT88AAAAInGU_Tw", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Linux_Test_Integration/557126", + "context": "Test:Integration (Keyman - Linux)", + "created_at": "2025-06-15T19:53:22Z", + "updated_at": "2025-06-15T19:53: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983619369, + "node_id": "SC_kwDOAY2xT88AAAAInGU_KQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestSamplesAndTestProjects/557137", + "context": "Test: Samples and Test projects (Keyman - Android)", + "created_at": "2025-06-15T19:53:22Z", + "updated_at": "2025-06-15T19:53: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36983616186, + "node_id": "SC_kwDOAY2xT88AAAAInGUyug", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T19:52:33Z", + "updated_at": "2025-06-15T19:52:33Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 36983616063, + "node_id": "SC_kwDOAY2xT88AAAAInGUyPw", + "state": "success", + "description": "API verification succeeded", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/15666798530", + "context": "API Verification", + "created_at": "2025-06-15T19:52:32Z", + "updated_at": "2025-06-15T19:52:32Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 36983610230, + "node_id": "SC_kwDOAY2xT88AAAAInGUbdg", + "state": "pending", + "description": "API verification started", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/15666798530", + "context": "API Verification", + "created_at": "2025-06-15T19:51:16Z", + "updated_at": "2025-06-15T19:51:16Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 36983609435, + "node_id": "SC_kwDOAY2xT88AAAAInGUYWw", + "state": "success", + "description": "Package build succeeded", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/15666742710", + "context": "Ubuntu Packaging", + "created_at": "2025-06-15T19:51:06Z", + "updated_at": "2025-06-15T19:51:06Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36983605706, + "node_id": "SC_kwDOAY2xT88AAAAInGUJyg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T19:50:11Z", + "updated_at": "2025-06-15T19:50: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36983605635, + "node_id": "SC_kwDOAY2xT88AAAAInGUJgw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T19:50:10Z", + "updated_at": "2025-06-15T19:50: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983605090, + "node_id": "SC_kwDOAY2xT88AAAAInGUHYg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestSamplesAndTestProjects/557137", + "context": "Test: Samples and Test projects (Keyman - Android)", + "created_at": "2025-06-15T19:50:02Z", + "updated_at": "2025-06-15T19:50: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983605068, + "node_id": "SC_kwDOAY2xT88AAAAInGUHTA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Linux/557141", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-06-15T19:50:02Z", + "updated_at": "2025-06-15T19:50: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983604926, + "node_id": "SC_kwDOAY2xT88AAAAInGUGvg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_KeymanMac_PullRequests/557130", + "context": "Test (Keyman - macOS)", + "created_at": "2025-06-15T19:49:59Z", + "updated_at": "2025-06-15T19:49:59Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983604910, + "node_id": "SC_kwDOAY2xT88AAAAInGUGrg", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Mac/557128", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-06-15T19:49:58Z", + "updated_at": "2025-06-15T19:49: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36983603034, + "node_id": "SC_kwDOAY2xT88AAAAInGT_Wg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T19:49:30Z", + "updated_at": "2025-06-15T19:49: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36983602960, + "node_id": "SC_kwDOAY2xT88AAAAInGT_EA", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T19:49:29Z", + "updated_at": "2025-06-15T19:49: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36983602862, + "node_id": "SC_kwDOAY2xT88AAAAInGT-rg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T19:49:28Z", + "updated_at": "2025-06-15T19:49:28Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36983602710, + "node_id": "SC_kwDOAY2xT88AAAAInGT-Fg", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T19:49:26Z", + "updated_at": "2025-06-15T19:49:26Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36983602571, + "node_id": "SC_kwDOAY2xT88AAAAInGT9iw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T19:49:24Z", + "updated_at": "2025-06-15T19:49:24Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983602429, + "node_id": "SC_kwDOAY2xT88AAAAInGT8_Q", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Linux/557141", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-06-15T19:49:22Z", + "updated_at": "2025-06-15T19:49: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983602406, + "node_id": "SC_kwDOAY2xT88AAAAInGT85g", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keymanweb_TestPullRequests/557123", + "context": "Test (Keyman - Web)", + "created_at": "2025-06-15T19:49:22Z", + "updated_at": "2025-06-15T19:49: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983602349, + "node_id": "SC_kwDOAY2xT88AAAAInGT8rQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Developer_Linux/557140", + "context": "Test - Linux (Keyman - Developer)", + "created_at": "2025-06-15T19:49:21Z", + "updated_at": "2025-06-15T19:49: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983602329, + "node_id": "SC_kwDOAY2xT88AAAAInGT8mQ", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Linux/557127", + "context": "Test: Keyman Core - Linux (Common)", + "created_at": "2025-06-15T19:49:20Z", + "updated_at": "2025-06-15T19:49: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983601809, + "node_id": "SC_kwDOAY2xT88AAAAInGT6kQ", + "state": "success", + "description": "⚠️ Warning: keymanweb.js is 62 bytes (0%) larger than 19.0.57, now 428,785 bytes", + "target_url": null, + "context": "check/web/file-size", + "created_at": "2025-06-15T19:49:13Z", + "updated_at": "2025-06-15T19:49: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983601796, + "node_id": "SC_kwDOAY2xT88AAAAInGT6hA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Developer_Test/557138", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-06-15T19:49:13Z", + "updated_at": "2025-06-15T19:49: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983601768, + "node_id": "SC_kwDOAY2xT88AAAAInGT6aA", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Web/557135", + "context": "Test: Common - Web (Common)", + "created_at": "2025-06-15T19:49:13Z", + "updated_at": "2025-06-15T19:49: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983601746, + "node_id": "SC_kwDOAY2xT88AAAAInGT6Ug", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Mac/557128", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-06-15T19:49:12Z", + "updated_at": "2025-06-15T19:49: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983601711, + "node_id": "SC_kwDOAY2xT88AAAAInGT6Lw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestSamplesAndTestProjects/557122", + "context": "Test: Samples and Test Projects (Keyman - iOS)", + "created_at": "2025-06-15T19:49:12Z", + "updated_at": "2025-06-15T19:49: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983599703, + "node_id": "SC_kwDOAY2xT88AAAAInGTyVw", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanAndroid_TestPullRequests/557136", + "context": "Test (Keyman - Android)", + "created_at": "2025-06-15T19:48:45Z", + "updated_at": "2025-06-15T19:48: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983599672, + "node_id": "SC_kwDOAY2xT88AAAAInGTyOA", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPullRequests/557132", + "context": "Test (Keyman - Windows)", + "created_at": "2025-06-15T19:48:44Z", + "updated_at": "2025-06-15T19:48: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983599495, + "node_id": "SC_kwDOAY2xT88AAAAInGTxhw", + "state": "failure", + "description": "TeamCity build failed", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPullRequests/557132", + "context": "Test (Keyman - Windows)", + "created_at": "2025-06-15T19:48:42Z", + "updated_at": "2025-06-15T19:48:42Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36983589963, + "node_id": "SC_kwDOAY2xT88AAAAInGTMSw", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T19:46:26Z", + "updated_at": "2025-06-15T19:46:26Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983589728, + "node_id": "SC_kwDOAY2xT88AAAAInGTLYA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Web/557135", + "context": "Test: Common - Web (Common)", + "created_at": "2025-06-15T19:46:22Z", + "updated_at": "2025-06-15T19:46: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983589703, + "node_id": "SC_kwDOAY2xT88AAAAInGTLRw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/557129", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-06-15T19:46:22Z", + "updated_at": "2025-06-15T19:46: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36983585767, + "node_id": "SC_kwDOAY2xT88AAAAInGS75w", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T19:45:21Z", + "updated_at": "2025-06-15T19:45:21Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983585609, + "node_id": "SC_kwDOAY2xT88AAAAInGS7SQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Linux/557127", + "context": "Test: Keyman Core - Linux (Common)", + "created_at": "2025-06-15T19:45:18Z", + "updated_at": "2025-06-15T19:45: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983585591, + "node_id": "SC_kwDOAY2xT88AAAAInGS7Nw", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanLinux_TestPullRequests/557125", + "context": "Test (Keyman - Linux)", + "created_at": "2025-06-15T19:45:18Z", + "updated_at": "2025-06-15T19:45: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983582448, + "node_id": "SC_kwDOAY2xT88AAAAInGSu8A", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_Windows/557134", + "context": "Test: Keyman Core - Windows (Common)", + "created_at": "2025-06-15T19:44:31Z", + "updated_at": "2025-06-15T19:44: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983582422, + "node_id": "SC_kwDOAY2xT88AAAAInGSu1g", + "state": "success", + "description": "TeamCity build finished", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPrRenderOnScreenKeyboards/557133", + "context": "Test: Render On Screen Keyboards (Keyman - Windows)", + "created_at": "2025-06-15T19:44:30Z", + "updated_at": "2025-06-15T19:44:30Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", + "id": 36983576025, + "node_id": "SC_kwDOAY2xT88AAAAInGSV2Q", + "state": "pending", + "description": "Ubuntu packaging started", + "target_url": "https://github.com/keymanapp/keyman/actions/runs/15666742710", + "context": "Ubuntu Packaging", + "created_at": "2025-06-15T19:42:56Z", + "updated_at": "2025-06-15T19:42:56Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575722, + "node_id": "SC_kwDOAY2xT88AAAAInGSUqg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanLinux_TestPullRequests/557125", + "context": "Test (Keyman - Linux)", + "created_at": "2025-06-15T19:42:51Z", + "updated_at": "2025-06-15T19:42:51Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575673, + "node_id": "SC_kwDOAY2xT88AAAAInGSUeQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557141", + "context": "Test: Common - Linux (Common)", + "created_at": "2025-06-15T19:42:50Z", + "updated_at": "2025-06-15T19:42: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575616, + "node_id": "SC_kwDOAY2xT88AAAAInGSUQA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557140", + "context": "Test - Linux (Keyman - Developer)", + "created_at": "2025-06-15T19:42:49Z", + "updated_at": "2025-06-15T19:42: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575600, + "node_id": "SC_kwDOAY2xT88AAAAInGSUMA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557139", + "context": "Test - macOS (Keyman - Developer)", + "created_at": "2025-06-15T19:42:49Z", + "updated_at": "2025-06-15T19:42: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575548, + "node_id": "SC_kwDOAY2xT88AAAAInGST_A", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557138", + "context": "Test - Windows (Keyman - Developer)", + "created_at": "2025-06-15T19:42:49Z", + "updated_at": "2025-06-15T19:42: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575518, + "node_id": "SC_kwDOAY2xT88AAAAInGST3g", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557137", + "context": "Test: Samples and Test projects (Keyman - Android)", + "created_at": "2025-06-15T19:42:48Z", + "updated_at": "2025-06-15T19:42: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575497, + "node_id": "SC_kwDOAY2xT88AAAAInGSTyQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557136", + "context": "Test (Keyman - Android)", + "created_at": "2025-06-15T19:42:48Z", + "updated_at": "2025-06-15T19:42: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575434, + "node_id": "SC_kwDOAY2xT88AAAAInGSTig", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557135", + "context": "Test: Common - Web (Common)", + "created_at": "2025-06-15T19:42:47Z", + "updated_at": "2025-06-15T19:42: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575393, + "node_id": "SC_kwDOAY2xT88AAAAInGSTYQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557134", + "context": "Test: Keyman Core - Windows (Common)", + "created_at": "2025-06-15T19:42:46Z", + "updated_at": "2025-06-15T19:42: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575369, + "node_id": "SC_kwDOAY2xT88AAAAInGSTSQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPrRenderOnScreenKeyboards/557133", + "context": "Test: Render On Screen Keyboards (Keyman - Windows)", + "created_at": "2025-06-15T19:42:46Z", + "updated_at": "2025-06-15T19:42: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575348, + "node_id": "SC_kwDOAY2xT88AAAAInGSTNA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPrRenderOnScreenKeyboards/557133", + "context": "Test: Render On Screen Keyboards (Keyman - Windows)", + "created_at": "2025-06-15T19:42:46Z", + "updated_at": "2025-06-15T19:42: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575293, + "node_id": "SC_kwDOAY2xT88AAAAInGSS_Q", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPullRequests/557132", + "context": "Test (Keyman - Windows)", + "created_at": "2025-06-15T19:42:45Z", + "updated_at": "2025-06-15T19:42: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575270, + "node_id": "SC_kwDOAY2xT88AAAAInGSS5g", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/KeymanDesktop_TestPullRequests/557132", + "context": "Test (Keyman - Windows)", + "created_at": "2025-06-15T19:42:45Z", + "updated_at": "2025-06-15T19:42: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575236, + "node_id": "SC_kwDOAY2xT88AAAAInGSSxA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557131", + "context": "Test: Keyman Core - macOS (Common)", + "created_at": "2025-06-15T19:42:44Z", + "updated_at": "2025-06-15T19:42: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575200, + "node_id": "SC_kwDOAY2xT88AAAAInGSSoA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557130", + "context": "Test (Keyman - macOS)", + "created_at": "2025-06-15T19:42:44Z", + "updated_at": "2025-06-15T19:42: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575170, + "node_id": "SC_kwDOAY2xT88AAAAInGSSgg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/557129", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-06-15T19:42:44Z", + "updated_at": "2025-06-15T19:42: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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575149, + "node_id": "SC_kwDOAY2xT88AAAAInGSSbQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Test_Common_Windows/557129", + "context": "Test: Common - Windows (Common)", + "created_at": "2025-06-15T19:42:43Z", + "updated_at": "2025-06-15T19:42:43Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575110, + "node_id": "SC_kwDOAY2xT88AAAAInGSSRg", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557128", + "context": "Test: Common - macOS (Common)", + "created_at": "2025-06-15T19:42:43Z", + "updated_at": "2025-06-15T19:42:43Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575083, + "node_id": "SC_kwDOAY2xT88AAAAInGSSKw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557127", + "context": "Test: Keyman Core - Linux (Common)", + "created_at": "2025-06-15T19:42:42Z", + "updated_at": "2025-06-15T19:42:42Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575057, + "node_id": "SC_kwDOAY2xT88AAAAInGSSEQ", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557126", + "context": "Test:Integration (Keyman - Linux)", + "created_at": "2025-06-15T19:42:42Z", + "updated_at": "2025-06-15T19:42:42Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575027, + "node_id": "SC_kwDOAY2xT88AAAAInGSR8w", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/build/557125", + "context": "Test (Keyman - Linux)", + "created_at": "2025-06-15T19:42:41Z", + "updated_at": "2025-06-15T19:42:41Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983575008, + "node_id": "SC_kwDOAY2xT88AAAAInGSR4A", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_WASM/557124", + "context": "Test: Keyman Core - WASM (Common)", + "created_at": "2025-06-15T19:42:41Z", + "updated_at": "2025-06-15T19:42:41Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983574980, + "node_id": "SC_kwDOAY2xT88AAAAInGSRxA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_Common_KPAPI_TestPullRequests_WASM/557124", + "context": "Test: Keyman Core - WASM (Common)", + "created_at": "2025-06-15T19:42:41Z", + "updated_at": "2025-06-15T19:42:41Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983574948, + "node_id": "SC_kwDOAY2xT88AAAAInGSRpA", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keymanweb_TestPullRequests/557123", + "context": "Test (Keyman - Web)", + "created_at": "2025-06-15T19:42:40Z", + "updated_at": "2025-06-15T19:42:40Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983574923, + "node_id": "SC_kwDOAY2xT88AAAAInGSRiw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keymanweb_TestPullRequests/557123", + "context": "Test (Keyman - Web)", + "created_at": "2025-06-15T19:42:40Z", + "updated_at": "2025-06-15T19:42:40Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983574902, + "node_id": "SC_kwDOAY2xT88AAAAInGSRdg", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestSamplesAndTestProjects/557122", + "context": "Test: Samples and Test Projects (Keyman - iOS)", + "created_at": "2025-06-15T19:42:39Z", + "updated_at": "2025-06-15T19:42:39Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983574872, + "node_id": "SC_kwDOAY2xT88AAAAInGSRWA", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestSamplesAndTestProjects/557122", + "context": "Test: Samples and Test Projects (Keyman - iOS)", + "created_at": "2025-06-15T19:42:39Z", + "updated_at": "2025-06-15T19:42:39Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983574841, + "node_id": "SC_kwDOAY2xT88AAAAInGSROQ", + "state": "pending", + "description": "TeamCity build started", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestPullRequests/557121", + "context": "Test (Keyman - iOS)", + "created_at": "2025-06-15T19:42:39Z", + "updated_at": "2025-06-15T19:42:39Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/u/7018967?v=4", + "id": 36983574807, + "node_id": "SC_kwDOAY2xT88AAAAInGSRFw", + "state": "pending", + "description": "TeamCity build was queued", + "target_url": "https://build.palaso.org/buildConfiguration/Keyman_iOS_TestPullRequests/557121", + "context": "Test (Keyman - iOS)", + "created_at": "2025-06-15T19:42:38Z", + "updated_at": "2025-06-15T19:42:38Z", + "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/870d37f56b489ef5067b4352a06505369d9038e0", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 36983566808, + "node_id": "SC_kwDOAY2xT88AAAAInGRx2A", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14196#issuecomment-2972280376", + "context": "user_testing", + "created_at": "2025-06-15T19:40:53Z", + "updated_at": "2025-06-15T19:40:53Z", + "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/fixtures/8fd5ccc9026b85d6a780d8bcf862380e7a89aefb-check-runs.json b/resources/build/pr-build-status/fixtures/8fd5ccc9026b85d6a780d8bcf862380e7a89aefb-check-runs.json new file mode 100644 index 0000000000..c7a120e18f --- /dev/null +++ b/resources/build/pr-build-status/fixtures/8fd5ccc9026b85d6a780d8bcf862380e7a89aefb-check-runs.json @@ -0,0 +1,246 @@ +[ + { + "id": 44207248981, + "name": "triage", + "node_id": "CR_kwDOAY2xT88AAAAKSvUWVQ", + "head_sha": "8fd5ccc9026b85d6a780d8bcf862380e7a89aefb", + "external_id": "08ccda57-e306-5823-a607-291b5482a6f2", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/44207248981", + "html_url": "https://github.com/keymanapp/keyman/actions/runs/15691412642/job/44207248981", + "details_url": "https://github.com/keymanapp/keyman/actions/runs/15691412642/job/44207248981", + "status": "completed", + "conclusion": "success", + "started_at": "2025-06-16T20:38:08Z", + "completed_at": "2025-06-16T20:38:12Z", + "output": { + "title": null, + "summary": null, + "text": null, + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/keymanapp/keyman/check-runs/44207248981/annotations" + }, + "check_suite": { + "id": 40219154773 + }, + "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/14199", + "id": 2594414200, + "number": 14199, + "head": { + "ref": "maint/resources/14172-pr-build-status-2", + "sha": "c0d28233302cbc22c7d7625dfba5930006ad7d0b", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "maint/developer/support-buildLevel", + "sha": "825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + } + } + ] + }, + { + "id": 44207248005, + "name": "GitGuardian Security Checks", + "node_id": "CR_kwDOAY2xT88AAAAKSvUShQ", + "head_sha": "8fd5ccc9026b85d6a780d8bcf862380e7a89aefb", + "external_id": "", + "url": "https://api.github.com/repos/keymanapp/keyman/check-runs/44207248005", + "html_url": "https://github.com/keymanapp/keyman/runs/44207248005", + "details_url": "https://dashboard.gitguardian.com", + "status": "completed", + "conclusion": "success", + "started_at": "2025-06-16T20:38:03Z", + "completed_at": "2025-06-16T20:38:06Z", + "output": { + "title": "No secrets detected ✅", + "summary": "**8** commits were scanned without uncovering any secrets.\n", + "text": "Commits scanned: **8**\n\n\n\n- Pull request #14199: `maint/resources/14172-pr-build-status-2` 👉 `maint/developer/support-buildLevel`\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/44207248005/annotations" + }, + "check_suite": { + "id": 40219152500 + }, + "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/14199", + "id": 2594414200, + "number": 14199, + "head": { + "ref": "maint/resources/14172-pr-build-status-2", + "sha": "c0d28233302cbc22c7d7625dfba5930006ad7d0b", + "repo": { + "id": 26063183, + "url": "https://api.github.com/repos/keymanapp/keyman", + "name": "keyman" + } + }, + "base": { + "ref": "maint/developer/support-buildLevel", + "sha": "825f7d2df0069fa56a8f2a534b42a49ee1f21ca1", + "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/8fd5ccc9026b85d6a780d8bcf862380e7a89aefb-statuses.json b/resources/build/pr-build-status/fixtures/8fd5ccc9026b85d6a780d8bcf862380e7a89aefb-statuses.json new file mode 100644 index 0000000000..1d1798bdd3 --- /dev/null +++ b/resources/build/pr-build-status/fixtures/8fd5ccc9026b85d6a780d8bcf862380e7a89aefb-statuses.json @@ -0,0 +1,35 @@ +[ + { + "url": "https://api.github.com/repos/keymanapp/keyman/statuses/8fd5ccc9026b85d6a780d8bcf862380e7a89aefb", + "avatar_url": "https://avatars.githubusercontent.com/in/133554?v=4", + "id": 37011551277, + "node_id": "SC_kwDOAY2xT88AAAAIng90LQ", + "state": "success", + "description": "User tests are not required", + "target_url": "https://github.com/keymanapp/keyman/pull/14199#issuecomment-2975084272", + "context": "user_testing", + "created_at": "2025-06-16T20:38:04Z", + "updated_at": "2025-06-16T20:38:04Z", + "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/package-lock.json b/resources/build/pr-build-status/package-lock.json new file mode 100644 index 0000000000..ff65ee2506 --- /dev/null +++ b/resources/build/pr-build-status/package-lock.json @@ -0,0 +1,1528 @@ +{ + "name": "@keymanapp/pr-build-status", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@keymanapp/pr-build-status", + "license": "MIT", + "dependencies": { + "@actions/core": "^1.9.1", + "@actions/github": "^6.0.1" + }, + "devDependencies": { + "chai": "^5.1.0", + "mocha": "^11.2.2", + "mocha-teamcity-reporter": "^4.0.0" + } + }, + "node_modules/@actions/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", + "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", + "license": "MIT", + "dependencies": { + "@actions/exec": "^1.1.1", + "@actions/http-client": "^2.0.1" + } + }, + "node_modules/@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "license": "MIT", + "dependencies": { + "@actions/io": "^1.0.1" + } + }, + "node_modules/@actions/github": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.1.tgz", + "integrity": "sha512-xbZVcaqD4XnQAe35qSQqskb3SqIAfRyLBrHMd/8TuL7hJSz2QtbDwnNM8zWx4zO5l2fnGtseNE3MbEvD7BxVMw==", + "license": "MIT", + "dependencies": { + "@actions/http-client": "^2.2.0", + "@octokit/core": "^5.0.1", + "@octokit/plugin-paginate-rest": "^9.2.2", + "@octokit/plugin-rest-endpoint-methods": "^10.4.0", + "@octokit/request": "^8.4.1", + "@octokit/request-error": "^5.1.1", + "undici": "^5.28.5" + } + }, + "node_modules/@actions/http-client": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", + "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", + "license": "MIT" + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.1.tgz", + "integrity": "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.4.1", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", + "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", + "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^8.4.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.2.tgz", + "integrity": "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", + "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", + "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", + "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "license": "Apache-2.0" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "license": "ISC" + }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", + "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mocha": { + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.6.0.tgz", + "integrity": "sha512-i0JVb+OUBqw63X/1pC3jCyJsqYisgxySBbsQa8TKvefpA1oEnw7JXxXnftfMHRsw7bEEVGRtVlHcDYXBa7FzVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/mocha-teamcity-reporter": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mocha-teamcity-reporter/-/mocha-teamcity-reporter-4.2.0.tgz", + "integrity": "sha512-H08IvAIsiCcdXAEObzp/VvJHFfLummxt5wpr0gU4yxx6pTb7ZmKnVjyXkFi+3gYi3bu/j2iJdoK9Aknz16mbBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "mocha": ">=6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "license": "ISC" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workerpool": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.2.tgz", + "integrity": "sha512-Xz4Nm9c+LiBHhDR5bDLnNzmj6+5F+cyEAWPMkbs2awq/dYazR/efelZzUAjB/y3kNHL+uzkHvxVVpaOfGCPV7A==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/resources/build/pr-build-status/package.json b/resources/build/pr-build-status/package.json new file mode 100644 index 0000000000..5e9dc1bc70 --- /dev/null +++ b/resources/build/pr-build-status/package.json @@ -0,0 +1,20 @@ +{ + "description": "CHeck pull request build status - development area", + "type": "module", + "dependencies": { + "@actions/core": "^1.9.1", + "@actions/github": "^6.0.1" + }, + "devDependencies": { + "mocha": "^11.2.2", + "mocha-teamcity-reporter": "^4.0.0", + "chai": "^5.1.0" + }, + "license": "MIT", + "main": "pr-build-status.mjs", + "name": "@keymanapp/pr-build-status", + "private": true, + "scripts": { + "test": "mocha pr-build-status.tests.mjs" + } +} diff --git a/resources/build/pr-build-status/pr-build-status.mjs b/resources/build/pr-build-status/pr-build-status.mjs new file mode 100644 index 0000000000..cfd0dbd8c0 --- /dev/null +++ b/resources/build/pr-build-status/pr-build-status.mjs @@ -0,0 +1,188 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Clone of the pr-build-status.yml file for unit testing and development. The + * indented section of this file below is copied verbatim into + * .github/workflows/pr-build-status.yml, and allows us to develop the YAML + * script without introducing a dependency on the repository. + */ + +// Copy the indented section into the step for pr-build-status.mjs. + +// START OF CLONED SECTION + + // 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 reduceStatuses(statuses) { + 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}; + } 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};; + } else if(status.context == 'API Verification') { + if(!o[status.context]) o[status.context] = {type: 'check', state: status.state}; + } else if(status.context == 'Ubuntu Packaging') { + if(!o[status.context]) o[status.context] = {type: 'build', state: 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 + } 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 + o[status.context] = {type: 'unknown', state: status.state}; + } + return o; + + }, {}); + return filtered_statuses; + } + + // + // Given the collection of status checks we care about, return + // an aggregate status -- error, failed, pending, or success, + // and a summary description + // + function calculateFinalStatus(filtered_statuses) { + 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 + return [ + 'error', `An unknown context ${context} was found, cannot calculate build status.` + ]; + } + 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) { + return ['pending', 'Checks have not yet been triggered ⌛']; + } + + const state = + counts.error ? 'error' : + counts.failed ? 'failed' : + 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.failed, 'failed ❌'); + appendDescription(counts.pending, 'pending ⌛'); + appendDescription(counts.success, 'completed successfully ✅'); + + return [ state, description ]; + } + + async function getCommitStatuses(github, owner, repo, 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) { + 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 ❌']; + } + + const filtered_statuses = reduceStatuses(statuses); + + const result = calculateFinalStatus(filtered_statuses); + return result; + } + + 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) { + // To ensure that + 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: '' + } + }); + } + +// END OF CLONED SECTION +// THIS SECTION MUST BE INCLUDED, UNCOMMENTED IN THE .yml + + // const { owner, repo } = context.repo; + // const sha = context.payload?.check_suite?.sha || context.sha; + // 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]); + +// END OF COMMENTED SECTION + +// Following code is used only for unit testing; do not include in the .yml + +export const unitTestEndpoints = { + getCommitStatuses, calculateCheckResult, getCommitCheckRuns +}; \ No newline at end of file diff --git a/resources/build/pr-build-status/pr-build-status.tests.mjs b/resources/build/pr-build-status/pr-build-status.tests.mjs new file mode 100644 index 0000000000..b9a4429eac --- /dev/null +++ b/resources/build/pr-build-status/pr-build-status.tests.mjs @@ -0,0 +1,44 @@ +import * as fs from 'node:fs'; +import 'mocha'; +import { assert } from 'chai'; + +import { unitTestEndpoints } from './pr-build-status.mjs'; + +// debug only +import { getOctokit } from '@actions/github'; +import * as process from 'node:process'; + +const commits = { + '825f7d2df0069fa56a8f2a534b42a49ee1f21ca1': [ 'success', '24 checks completed successfully ✅' ], + '870d37f56b489ef5067b4352a06505369d9038e0': [ 'error', '6 checks in an error state ❌; 17 checks completed successfully ✅' ], + '11a558e926370253db37026c95b7ff5a6aa1e8fc': [ 'success', '22 checks completed successfully ✅' ], + '8fd5ccc9026b85d6a780d8bcf862380e7a89aefb': [ 'pending', 'Checks have not yet been triggered ⌛' ], + '153683cfb007c6066c9dcf71d25afa4c66efa17f': [ 'pending', 'Checks have not yet been triggered ⌛' ], +}; + +// When adding new SHAs to test, run this to collect data from GitHub +const debug = false; +if(debug) { + + async function writeTestFile(sha) { + fs.writeFileSync('fixtures/' + sha + '-statuses.json', + JSON.stringify(await unitTestEndpoints.getCommitStatuses(octokit, 'keymanapp', 'keyman', sha), null, 2), 'utf-8'); + fs.writeFileSync('fixtures/' + sha + '-check-runs.json', + JSON.stringify(await unitTestEndpoints.getCommitCheckRuns(octokit, 'keymanapp', 'keyman', sha), null, 2), 'utf-8'); + } + + const octokit = getOctokit(process.env['GITHUB_TOKEN']); + for(const sha of Object.keys(commits)) await writeTestFile(sha); +} + +describe('pr-build-status', function () { + 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]); + }); + } +}); diff --git a/resources/build/run-required-test-builds.sh b/resources/build/run-required-test-builds.sh index bc9b307c62..03f782dfeb 100755 --- a/resources/build/run-required-test-builds.sh +++ b/resources/build/run-required-test-builds.sh @@ -65,6 +65,8 @@ function triggerTestBuilds() { local -n platforms=$1 local branch="$2" + local found_build=false + # Cancel any already running builds for this branch if builder_has_option --dry-run; then builder_echo "DRY RUN: cancel current builds for $branch" @@ -83,6 +85,7 @@ function triggerTestBuilds() { eval test_builds='(${'bc_test_$platform'[@]})' for test_build in "${test_builds[@]}"; do if [[ $test_build == "" ]]; then continue; fi + found_build=true if [ "${test_build:(-7)}" == "_GitHub" ]; then local job=${test_build%_GitHub} @@ -102,6 +105,26 @@ function triggerTestBuilds() { fi done done + + if [[ $found_build == false ]]; then + postSkippedBuildsStatusResult + fi +} + +# +# Add a 'Test Build (Keyman)' successful status check to the commit +# +function postSkippedBuildsStatusResult() { + if builder_has_option --dry-run; then + builder_echo "DRY RUN: write successful status check 'Skipping since no platform builds necessary'" + else + curl --silent --write-out '\n' \ + --request POST \ + --header "Accept: application/vnd.github+json" \ + --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 } # @@ -241,16 +264,7 @@ if (( ${#build_platforms[@]} > 0)); then triggerTestBuilds build_platforms $PRNUM else builder_echo heading "No builds to start" - if builder_has_option --dry-run; then - builder_echo "DRY RUN: write successful status check 'Skipping since no platform builds necessary'" - else - curl --silent --write-out '\n' \ - --request POST \ - --header "Accept: application/vnd.github+json" \ - --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 + postSkippedBuildsStatusResult fi exit 0 From 876fa29df810fde6250578e41016bf5a90ea6742 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 19 Jun 2025 07:37:48 +1000 Subject: [PATCH 039/262] chore(resources): temporarily test GHA on push to current branch --- .github/workflows/pr-build-status.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-build-status.yml b/.github/workflows/pr-build-status.yml index f8de2f7adf..3c4c865c72 100644 --- a/.github/workflows/pr-build-status.yml +++ b/.github/workflows/pr-build-status.yml @@ -1,8 +1,9 @@ name: Keyman Build Summary on: - # push: - # branches: - # - "maint/resources/14172-pr-build-status-2" + # Temporary for testing: + push: + branches: + - "maint/resources/14172-pr-build-status-2" check_run: types: [completed] From 06f1489dd86aa522da5976d2880949e0791c0c73 Mon Sep 17 00:00:00 2001 From: Meng-Heng Date: Fri, 20 Jun 2025 16:18:30 +0700 Subject: [PATCH 040/262] chore: include more valid bcp 47 tags and add markdown alerts into docs --- developer/docs/help/reference/bcp-47.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/developer/docs/help/reference/bcp-47.md b/developer/docs/help/reference/bcp-47.md index f5c8a49539..671c9e52a9 100644 --- a/developer/docs/help/reference/bcp-47.md +++ b/developer/docs/help/reference/bcp-47.md @@ -30,13 +30,21 @@ The following are all examples of valid BCP 47 tags: * `en-US`: English, in United States * `km-Khmr-KH`: Khmer, written in the Khmer script, in Cambodia * `km-fonipa`: Khmer, transcribed in IPA +* `und-cpmn`: Undetermined (Cypro-Minoan) +* `tok-Zzzz-AA`: Toki Pona (Unknown Script) + +> [!NOTE] +> `Zzzz` is an uncoded script of IOS 15924 and one of the Special codes of the Script subtag. +> `AA` is a Private Use Code. ### The language subtag The only required option is the Language subtag, which is an [ISO 639-1][2] or [ISO 639-3][3] code. -ISO 639-1 tags are a two-letter code. ISO 639-3 tags are a three-letter code. +- ISO 639-1 tags are a two-letter code. +- ISO 639-3 tags are a three-letter code. + First, try to find your language on the list of two-letter ISO 639-1 codes. [This Wikipedia page][4] lists all of the two-letter codes. @@ -46,10 +54,11 @@ it will give you an appropriate code. In this example, I searched Glottolog for “[Saanich][6]” (name of the First Nations that speak SENĆOŦEN) and found `str` as the code for all Straits Salish languages. -The Language subtag is conventionally written in lower case. +> [!IMPORTANT] +> The Language subtag is conventionally written in lower case. -The next two subtags are **optional**, however, they allow you to be more -specific about your language. +> [!NOTE] +> The next two subtags are **optional**, however, they allow you to be more specific about your language. ### The script subtag @@ -67,8 +76,8 @@ of the **Canadian Aboriginal syllabics** family of writing systems. If I wrote a keyboard or lexical model that produced syllabics, I would choose `Cans`, as that is the **ISO 15924** tag for Canadian Aboriginal syllabics. -The Script subtag is conventionally written in title case - first letter -capitalized. +> [!IMPORTANT] +> The Script subtag is conventionally written in title case - first letter capitalized. ### The region subtag @@ -100,7 +109,8 @@ and Mexico. Another common UN M49 region code is `001` for the whole world. -Alphabetic region subtags are conventionally written in upper case. +> [!IMPORTANT] +> Alphabetic region subtags are conventionally written in upper case. [1]: https://en.wikipedia.org/wiki/IETF_language_tag [2]: https://en.wikipedia.org/wiki/ISO_639-1 From 3f4705d96df95349baa7fc66848eb3c18280d35a Mon Sep 17 00:00:00 2001 From: MengHeng <90595388+Meng-Heng@users.noreply.github.com> Date: Fri, 20 Jun 2025 17:04:07 +0700 Subject: [PATCH 041/262] Apply suggestions from code review Co-authored-by: rc-swag <58423624+rc-swag@users.noreply.github.com> --- .../help/guides/develop/touch-keyboard-tutorial/gestures.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md b/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md index d39d86891f..0765a6dd62 100644 --- a/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md +++ b/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md @@ -8,8 +8,8 @@ Keyman Developer offers **Longpress** keys, **Flicks**, and **Multitaps** for to ## Tips -* We can use both **flicks** and **long presses** in a single keyboard. Generally, we recommend only having flicks in one direction--south (down), and long presses work well with this pattern. See, for example, [sil_euro_latin](../../../../keyboard/sil_euro_latin/3.0.3/sil_euro_latin#toc-mobile-keyboard-layout). - * You’ll also notice on sil_euro_latin that the south-flicks on the base (and shift) layers correspond to the numeric/symbol layer, which is also helpful for remembering and discovering. +***Flicks** and **long presses** can both be used in a single keyboard. In this scenario, it is recommended to use flicks in only one direction—south (down). Long presses work well alongside this pattern. See, for example, [sil_euro_latin](../../../../keyboard/sil_euro_latin/3.0.3/sil_euro_latin#toc-mobile-keyboard-layout). + * Another helpful tip seen in the sil_euro_latin keyboard is using south-flicks on the base and shift layers corresponding to the numeric/symbol layer, this aids in both memorization and discovery. * **Flicks** in multiple directions are possible, but currently Keyman does not support hints for multiple directions, so they are not very discoverable. * **Long presses** are more discoverable, but **flicks** are faster. We suggest using south flicks along with long presses, and including the flick output in the long press for maximum discoverability and optimum ergonomics. @@ -17,7 +17,7 @@ Keyman Developer offers **Longpress** keys, **Flicks**, and **Multitaps** for to * **Flicks** near screen edges tend to be problematic--once you drag off the screen, then Keyman can no longer tell that you are flicking (or you may end up activating system controls on the bottom edge): * On the bottom row, south flicks are not recommended. - * On the top row, we have the banner space reserved in order to be able to support flicks and long presses up into that region of the screen--not an ideal solution, but it works. + * On the top row, we have the banner space reserved in order to be able to support flicks and long presses up into that region of the screen. * Left/right screen edges, horizontal flicks are not the best practice. ## See also From ba8dd1a17e38f6cd933b5b24ea749cbc4be76320 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Sat, 21 Jun 2025 14:44:07 -0500 Subject: [PATCH 042/262] chore(developer): document findInstanceObject() return value per code review Fixes: #14099 --- developer/src/common/web/utils/src/xml-utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/developer/src/common/web/utils/src/xml-utils.ts b/developer/src/common/web/utils/src/xml-utils.ts index 1d2b01c71e..721e503f9e 100644 --- a/developer/src/common/web/utils/src/xml-utils.ts +++ b/developer/src/common/web/utils/src/xml-utils.ts @@ -364,6 +364,7 @@ export class KeymanXMLWriter { * traverse an AJV instancePath and map to an object if possible * @param source object tree root (contains the root object) * @param path ajv split instancePath, such as '/keyboard3/layers/0'.split('/') + * @returns undefined if the path was not present, null if path went to something that wasn't an object, otherwise the compileContext object is returned. */ export function findInstanceObject(source: any, path: string[]) : any { if(!path || !source || path.length == 0) { From a84ede81b0d77d5c6c6683c19f3070c0b189260b Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Sun, 22 Jun 2025 14:01:22 -0400 Subject: [PATCH 043/262] auto: increment master version to 19.0.68 --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 06cd831c72..be1a83fb18 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 19.0.67 alpha 2025-06-22 + +* chore(developer): ldml: additional line numbers (#14104) + ## 19.0.66 alpha 2025-06-16 * fix(windows): add values wucUpdateAvailable and wucNotChecked to TRemoteUpdateCheckResult enum (#14123) diff --git a/VERSION.md b/VERSION.md index f0d2c39d19..bf865b419e 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.67 \ No newline at end of file +19.0.68 \ No newline at end of file From 94cdf834dfe293b618e9dda4fa7825f48ebbf27b Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 23 Jun 2025 05:05:01 +1000 Subject: [PATCH 044/262] maint(common): polish docs and add tests for build-bot --- docs/build-bot.md | 103 +++++++++--------- resources/build/run-required-test-builds.sh | 4 +- .../test/build-bot/trigger-build-bot.test.sh | 4 + resources/build/trigger-build-bot.inc.sh | 13 ++- resources/build/trigger-builds.inc.sh | 8 +- 5 files changed, 72 insertions(+), 60 deletions(-) diff --git a/docs/build-bot.md b/docs/build-bot.md index 8f06e7d75f..06c34d4333 100644 --- a/docs/build-bot.md +++ b/docs/build-bot.md @@ -29,6 +29,59 @@ Or you may want to ensure that an artifact is built for Windows: Build-bot: release windows ``` +## Build Level + +The build level specifies what we want to be run for a test build on a PR. + +There is a bit of nomenclature overlap with a 'release build'. A 'release' +buildLevel for a 'test build' is roughly equivalent to what is performed in a +'release build', however, a test build is only ever uploaded to test endpoints +(i.e. TestFlight, Play Store 'test' streams), and never to *.keyman.com, or to +other release distribution endpoints. + +The build level is controlled by the Build-bot commit trailer and PR body +Build-bot/Test-bot trailers. The default build level will be 'build'. + +For target branch builds, the build level will always be 'build', and Build-bot: +commit trailers are ignored. + +### 'skip' build level + +Don't do a build at all. This would be appropriate for documentation PRs, for +example, or changes only to comments in source files. + +### 'build' build level + +Build the code and run unit tests, but don't create artifacts. What this looks +like will vary from platform to platform, but there are some common things we +won't do: +* we won't upload artifacts to TeamCity or *.keyman.com +* we won't upload artifacts to any endpoint such as Play Store +* we won't upload symbols to Sentry +* we won't codesign + +However, we _will_ still build an installer (skipping codesigning), as this is +part of the 'build' buildLevel rather than the 'release' buildLevel. The +installer will be thrown away for 'build' build level -- it will not be +available for download as an artifact. + +For a platform-specific example, on macOS we will also skip notarizing, as this +is costly and depends on external network resources, making it fragile. + +### 'release' build level + +A full test build will be run, roughly equivalent to a release build. We will do +the following steps: +* codesign (many platforms) and notarization (macOS) +* upload artifacts to TeamCity (all platforms) +* upload builds to TestFlight / Play Store 'test' endpoints (iOS/Android) +* upload symbols to Sentry + +For a 'release' build level: +* we won't upload artifacts to *.keyman.com +* we won't upload artifacts to any release endpoint such as Debian, + packages.sil.org, etc, or to the release areas for Play Store or App Store + ## Controlling the build bot with trailers The build bot respects commit trailers and trailers in the PR body. The commands @@ -81,56 +134,6 @@ Note that other platforms are still 'skip' but not included in the build set. Finally, the PR author pushes another commit, with `Build-bot: release windows`. The build set is now: `(android:skip ios:skip windows:release)`. -## Build Level - -The build level specifies what we want to be run for a test build on a PR. - -There is a bit of nomenclature overlap with a 'release build'. A 'release' -buildLevel for a 'test build' is roughly equivalent to what is performed in a -'release build', however, a test build is only ever uploaded to test endpoints -(i.e. TestFlight, Play Store 'test' streams), and never to *.keyman.com, or to -other release distribution endpoints. - -The build level is controlled by the Build-bot commit trailer and PR body -Build-bot/Test-bot trailers. The default build level will be 'build'. - -For target branch builds, the build level will always be 'build', and Build-bot: -commit trailers are ignored. - -### 'skip' build level - -Don't do a build at all. This would be appropriate for documentation PRs, for -example, or changes only to comments in source files. - -### 'build' build level - -Build the code and run unit tests, but don't create artifacts. What this looks -like will vary from platform to platform, but there are some common things we -won't do: -* we won't upload artifacts to TeamCity or *.keyman.com -* we won't upload artifacts to any endpoint such as Play Store -* we won't upload symbols to Sentry -* we won't codesign - -However, we _will_ still build an installer (skipping codesigning), as this is -part of the 'build' buildLevel rather than the 'release' buildLevel. - -For a platform-specific example, on macOS we will also skip notarizing, as this -is costly and depends on external network resources, making it fragile. - -### 'release' build level - -A full test build will be run, roughly equivalent to a release build. We will do -the following steps: -* codesign (many platforms) and notarization (macOS) -* upload artifacts to TeamCity (all platforms) -* upload builds to TestFlight / Play Store 'test' endpoints (iOS/Android) -* upload symbols to Sentry - -For a 'release' build level: -* we won't upload artifacts to *.keyman.com -* we won't upload artifacts to any release endpoint such as Debian, - packages.sil.org, etc, or to the release areas for Play Store or App Store [Build Level]: #Build_Level \ No newline at end of file diff --git a/resources/build/run-required-test-builds.sh b/resources/build/run-required-test-builds.sh index bc9b307c62..4426e46bf8 100755 --- a/resources/build/run-required-test-builds.sh +++ b/resources/build/run-required-test-builds.sh @@ -19,7 +19,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" builder_describe "Run test builds for the given pull request/primary branch" \ "--dry-run,-n Only report back which builds would be started" \ - "--branch,-b=PRNUM Branch (master,beta) or pull-request to test" + "--branch,-b=PRNUM Branch (master,beta,stable-x.y) or pull-request to test" if [[ $# -eq 1 ]] && [[ "$1" =~ ^[0-9]+$ ]]; then # For transitional period, when build configuration on TC needs to support @@ -110,7 +110,7 @@ function triggerTestBuilds() { # if [[ ! "$PRNUM" =~ ^[[:digit:]]+$ ]]; then - # branch name is 'master', 'beta' [, or 'stable' -- in the future] + # 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 exit 0 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 c0d914a6ce..3629cc3c6a 100755 --- a/resources/build/test/build-bot/trigger-build-bot.test.sh +++ b/resources/build/test/build-bot/trigger-build-bot.test.sh @@ -28,6 +28,10 @@ test_build_bot_check_messages() { _do_test_build_bot_check_messages_file 14013 "[windows]=release" "$ALL_BUILD_PLATFORMS_SKIP_EXPECTED" # Simplified data for testing Build-bot command sequences only _do_test_build_bot_check_messages_file 9999 "[windows]=release" '[windows]="skip" [developer]="release"' + + # test another sequence of commands + _do_test_build_bot_check_messages_inline 8 "[windows]=release" '[common]="release" [windows]="skip" [developer]="build"' '{ "body": "Build-bot: skip windows\nBuild-bot: build developer\nBuild-bot: release common" }' '[]' + # Test some bad inputs _do_test_build_bot_check_messages_inline 1 "[windows]=release" '[windows]="release"' '{}' '[{ "commit": { "message": "maint(common): test\nBuild-bot: foo windows\n" }}]' # empty body, 'foo windows' in commit diff --git a/resources/build/trigger-build-bot.inc.sh b/resources/build/trigger-build-bot.inc.sh index d55ac473ae..f9b428be35 100644 --- a/resources/build/trigger-build-bot.inc.sh +++ b/resources/build/trigger-build-bot.inc.sh @@ -38,7 +38,7 @@ function test_bot_check_pr_body() { } # -# Check PR commit messages for Build-bot commands Later commands override +# Check PR commit messages for Build-bot commands. Later commands override # earlier ones. Also checks PR body for any overriding commands. Note, only # checks the first 250 commits, so later build commands will be ignored; in this # situation, use the PR body command. Also ignores build commands in PR @@ -77,9 +77,6 @@ function build_bot_check_messages() { buildBotCommands+=("${prCommands[@]}") fi - # We now know that our command has only a-z, comma and space, so we can parse - # without risking escaping our bash jail - for buildBotCommand in "${buildBotCommands[@]}"; do # Block illegal Build-bot: commands if [[ ! "$buildBotCommand" =~ ^[a-z,\ ]+$ ]]; then @@ -87,6 +84,9 @@ function build_bot_check_messages() { continue fi + # We now know that our command has only a-z, comma and space, so we can + # parse without risking escaping our bash jail + if [[ ! -z "${buildBotCommand// }" ]]; then build_bot_update_commands $buildBotCommand fi @@ -97,8 +97,9 @@ function build_bot_check_messages() { # # parses a 'Build-bot: [platform...]' command and modifies -# the global build_platforms associative array with new levels -# note that this function assumes that inputs are sanitized, see +# the global build_platforms associative array with new levels. +# +# Note that this function assumes that inputs are sanitized, see # build_bot_check_messages # function build_bot_update_commands() { diff --git a/resources/build/trigger-builds.inc.sh b/resources/build/trigger-builds.inc.sh index aa07a30089..bfea20bb1a 100644 --- a/resources/build/trigger-builds.inc.sh +++ b/resources/build/trigger-builds.inc.sh @@ -32,8 +32,12 @@ function triggerReleaseBuilds() { # Trigger a build on TeamCity # # Parameters: -# 1: 'true' if this is for a PR branch, otherwise 'false' (for master/beta/stable-x.y) -# 2: 'skip' - don't run build; 'build' - run build and unit tests; 'release' - run build, unit tests, and deploy +# 1: isTestBuild: 'true' if this is for a PR branch or a test build +# for a primary branch; false is this is a release +# build for a primary branch (master/beta/stable-x.y) +# 2: buildLevel: 'skip' - don't run build; +# 'build' - run build and unit tests; +# 'release' - run build, unit tests, and deploy # 3: TeamCity build configuration name # 4: TeamCity VCS id, which is different for PR test configurations # 5: branch name in git From 8145c017d750f3db59670639e764fd8c0305c66b Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 24 Jun 2025 07:12:09 +1000 Subject: [PATCH 045/262] feat(resources): support ignoring unknown options in builder Fixes: #14200 Test-bot: skip --- docs/builder.md | 70 ++++++++++++++++++++++++++++++++---- resources/build/test/test.sh | 55 +++++++++++++++++++++++++--- resources/builder.inc.sh | 16 +++++++++ 3 files changed, 131 insertions(+), 10 deletions(-) diff --git a/docs/builder.md b/docs/builder.md index c0b889ce9f..360a15a550 100644 --- a/docs/builder.md +++ b/docs/builder.md @@ -182,6 +182,8 @@ a user or called by another script: An option will be inherited by child scripts if you append a `+` to the option name. + Other standard options are listed in [standard builder options]. + * **module dependencies**: these are other builder scripts which must be configured and built before the actions in this script can continue. Only `configure` and `build` actions are ever passed to dependency scripts; these @@ -320,20 +322,33 @@ across targets. Use this judiciously; for example, Keyman Core uses this to build both x86_64 and arm64 targets, and test only the appropriate architecture on macOS. -# Standard builder parameters +# Standard builder options -The following parameters are pre-defined and should not be overridden: +The following options are pre-defined and should not be overridden: * `--help`, `-h`: displays help on using this script * `--color`: forces on ANSI color output for the script * `--no-color`: forces off ANSI color output for the script * `--verbose`, `-v`: verbose mode, sets the [`$builder_verbose`] variable -* `--debug`, `-d`: debug build; see [`builder_is_debug_build`] for more detail +* `--debug`, `-d`: debug build (default for local builds); see + [`builder_is_debug_build`] for more detail +* `--release`: release build, inverse of `--debug`, to force a non-debug build * `--offline`: allow to build while offline. This might fail if not all dependencies are cached. * `--deps`: Build dependencies if required (default) * `--no-deps`: Skip build of dependencies * `--force-deps`: Reconfigure and rebuild all dependencies +* `--builder-ignore-unknown-options`: Ignore any subsequent unrecognized `-` or + `--` prefixed options in the parameter list. This is intended for use in + transitional situations where scripts may need to temporarily support passing + additional parameters; see also: #14200 for more background, + [`builder_ignore_unknown_options`], and [`$builder_ignored_options`]. + +## Internal builder options + +The following options are used internally in builder and should never be included +as parameters otherwise: `--builder-dep-parent`, `--builder-child`, +`--builder-report-dependencies`, `--builder-completion-describe` -------------------------------------------------------------------------------- @@ -482,7 +497,7 @@ There is one standard option: `--debug`. You should not include `--debug` in the `builder_describe` call, as it is always available. See [`builder_is_debug_build`] for more details. -Note that you should not include any of the [standard builder parameters] here. +Note that you should not include any of the [standard builder options] here. **Dependencies** are defined with a `@` prefix, for example: @@ -842,6 +857,47 @@ fi -------------------------------------------------------------------------------- +## `builder_ignore_unknown_options` function + +Returns `true` (aka 0) if the `--builder-ignore-unknown-options` standard option +was passed in. + +### Usage + +```bash +if builder_ignore_unknown_options; then + builder_echo "These options were ignored: ${builder_ignored_options[@]}" +fi +``` + +### Description + +This function should probably never be used in a builder script; it is included +for completeness and testing. See discussion in [standard builder options]. + +-------------------------------------------------------------------------------- + +## `$builder_ignored_options` variable + +Lists all the unrecognized options passed in; only used if the +`--builder-ignore-unknown-options` option was set (because if not set, the +script would have aborted in `builder_parse` with an unknown option error). + +### Usage + +```bash +if builder_ignore_unknown_options; then + builder_echo "These options were ignored: ${builder_ignored_options[@]}" +fi +``` + +### Description + +This variable should probably never be used in a builder script; it is included +for completeness and testing. See discussion in [standard builder options]. + +-------------------------------------------------------------------------------- + ## `builder_is_debug_build` function Returns `true` (aka 0) if the `--debug` standard option was passed in. This @@ -1208,7 +1264,7 @@ Note: it is often cleaner to use [`builder_echo`] than to use these variables di Note: it is recommended that you use `$(builder_term text)` instead of `${BUILDER_TERM_START}text${BUILDER_TERM_END}`. -[standard builder parameters]: #standard-builder-parameters +[standard builder options]: #standard-builder-options [`builder_describe`]: #builder_describe-function [`builder_describe_internal_dependency`]: #builder_describe_internal_dependency-function [`builder_describe_outputs`]: #builder_describe_outputs-function @@ -1218,6 +1274,9 @@ Note: it is recommended that you use `$(builder_term text)` instead of [`builder_finish_action`]: #builder_finish_action-function [`builder_has_action`]: #builder_has_action-function [`builder_has_option`]: #builder_has_option-function +[`$builder_ignored_options`]: #builder_ignored_options-variable +[`builder_ignore_unknown_options`]: #builder_ignore_unknown_options-function +[`builder_is_debug_build`]: #builder_is_debug_build-function [`builder_parse`]: #builder_parse-function [`builder_start_action`]: #builder_start_action-function [`builder_use_color`]: #builder_use_color-function @@ -1228,4 +1287,3 @@ Note: it is recommended that you use `$(builder_term text)` instead of [`builder_echo`]: #builder_echo-function [`builder_die`]: #builder_die-function [`builder_echo_debug`]: #builder_echo_debug-function -[`builder_is_debug_build`]: #builder_is_debug_build-function diff --git a/resources/build/test/test.sh b/resources/build/test/test.sh index 75bffe4637..9da63b09df 100755 --- a/resources/build/test/test.sh +++ b/resources/build/test/test.sh @@ -143,13 +143,13 @@ fi function verify_option() { local OPTIONNAME=$1 local VARIABLE=$2 - local EXCPECTED=$3 + local EXPECTED=$3 if builder_has_option ${OPTIONNAME}; then - if [[ ${!VARIABLE} == ${EXCPECTED} ]]; then - echo "PASS: ${OPTIONNAME} option variable \$${VARIABLE} has expected value '${EXCPECTED}'" + if [[ ${!VARIABLE} == ${EXPECTED} ]]; then + echo "PASS: ${OPTIONNAME} option variable \$${VARIABLE} has expected value '${EXPECTED}'" else - builder_die "FAIL: ${OPTIONNAME} option variable \$${VARIABLE} had value '${!VARIABLE}' but should have had '${EXCPECTED}'" + builder_die "FAIL: ${OPTIONNAME} option variable \$${VARIABLE} had value '${!VARIABLE}' but should have had '${EXPECTED}'" fi else builder_die "FAIL: ${OPTIONNAME} option not found" @@ -185,6 +185,53 @@ if [[ ${builder_extra_params[2]} != "three four five" ]]; then builder_die "FAIL: -- extra parameter 'three four five' not found" fi +#---------------------------------------------------------------------- +# Test handling of unknown options +echo -e "${COLOR_BLUE}## Running 'unknown options' tests${COLOR_RESET}" + +# Test long form option with --builder-ignore-unknown-options +builder_parse --builder-ignore-unknown-options --some-other-option +if ! builder_ignore_unknown_options; then + builder_die "FAIL: builder_ignore_unknown_options should be set" +fi +if [[ "${builder_ignored_options[@]}" != "--some-other-option" ]]; then + builder_die "FAIL: --builder-ignore-unknown-options did not collect --some-other-option" +fi +echo "PASS: --builder-ignore-unknown-options, --some-other-option was collected but ignored" + +# Test short form option with --builder-ignore-unknown-options +builder_parse --builder-ignore-unknown-options -x +if ! builder_ignore_unknown_options; then + builder_die "FAIL: builder_ignore_unknown_options should be set" +fi +if [[ "${builder_ignored_options[@]}" != "-x" ]]; then + builder_die "FAIL: --builder-ignore-unknown-options '${builder_ignored_options[@]}' did not collect -x" +fi +echo "PASS: --builder-ignore-unknown-options, -x was collected but ignored" + +# Test long form option without --builder-ignore-unknown-options +(builder_parse --some-other-option >/dev/null) && EXIT_CODE=0 || EXIT_CODE=$? +if [[ $EXIT_CODE != 64 ]]; then + builder_die "FAIL: without --builder-ignore-unknown-options, --some-other-option parameter should not have been recognized" +fi +echo "PASS: without --builder-ignore-unknown-options, --some-other-option was not a recognized parameter" + +# Test short form option without --builder-ignore-unknown-options +(builder_parse -x >/dev/null) && EXIT_CODE=0 || EXIT_CODE=$? +if [[ $EXIT_CODE != 64 ]]; then + builder_die "FAIL: without --builder-ignore-unknown-options, -x parameter should not have been recognized" +fi +echo "PASS: without --builder-ignore-unknown-options, -x was not a recognized parameter" + +# Test that ignored options variables are correct without --builder-ignore-unknown-options +builder_parse test +if builder_ignore_unknown_options; then + builder_die "FAIL: without --builder-ignore-unknown-options, builder_ignore_unknown_options() should not be set" +fi +if [[ ! -z "${builder_ignored_options[@]}" ]]; then + builder_die "FAIL: without --builder-ignore-unknown-options, builder_ignored_options should be empty" +fi + #---------------------------------------------------------------------- # Test output of: --feature --bar --baz (#11676) echo -e "${COLOR_BLUE}## Testing output of: builder_parse --feature xyzzy --bar abc --baz def test${COLOR_RESET}" diff --git a/resources/builder.inc.sh b/resources/builder.inc.sh index 25c9b5994c..51bc8773b5 100755 --- a/resources/builder.inc.sh +++ b/resources/builder.inc.sh @@ -1327,6 +1327,8 @@ _builder_parse_expanded_parameters() { _builder_current_action= _builder_is_child=1 _builder_offline= + _builder_ignore_unknown_options=1 + builder_ignored_options=() local n=0 @@ -1471,6 +1473,9 @@ _builder_parse_expanded_parameters() { --offline) _builder_offline=--offline ;; + --builder-ignore-unknown-options) + _builder_ignore_unknown_options=0 + ;; *) # script does not recognize anything of action or target form at this point. if [[ $key =~ ^: ]]; then @@ -1487,6 +1492,9 @@ _builder_parse_expanded_parameters() { # For child builds, don't fail the build when pass inheritable # parameters (#11408) builder_echo_debug "Parameter '$key' is not supported, ignoring" + elif [[ $key =~ ^- ]] && builder_ignore_unknown_options; then + builder_echo warning "Ignoring unknown option $key" + builder_ignored_options+=("$key") else _builder_parameter_error "$0" parameter "$key" fi @@ -1565,6 +1573,14 @@ _builder_parse_expanded_parameters() { fi } +# +# Returns 0 (true) if --builder-ignore-unknown-options flag has been specified +# in the command line +# +function builder_ignore_unknown_options() { + return $_builder_ignore_unknown_options +} + _builder_pad() { local count=$1 local text1=$2 From d49bc2efdb336156390997f92271ef74d2906da2 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 24 Jun 2025 08:51:12 +1000 Subject: [PATCH 046/262] chore(resources): remove redundant comments --- .github/workflows/pr-build-status.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pr-build-status.yml b/.github/workflows/pr-build-status.yml index 3c4c865c72..d755eca9b7 100644 --- a/.github/workflows/pr-build-status.yml +++ b/.github/workflows/pr-build-status.yml @@ -93,7 +93,7 @@ jobs: counts.pending ? 'pending' : 'success'; - let description = ''; //; + let description = ''; function appendDescription(count, state) { if(!count) return; if(description != '') description += '; '; @@ -160,7 +160,6 @@ jobs: } async function updateCheck(github, owner, repo, checkRunId, status, description) { - // To ensure that const checkStatus = status == 'pending' ? 'in_progress' : 'completed'; const conclusion = checkStatus == 'in_progress' ? undefined : (status == 'success' ? 'success' : 'failure'); From 7915793d9e6fb6bb7266198146c67d7a6aca008d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 24 Jun 2025 08:52:05 +1000 Subject: [PATCH 047/262] docs(common): clarify when release builds are run Build-bot: skip --- docs/build-bot.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/build-bot.md b/docs/build-bot.md index 06c34d4333..e879154a96 100644 --- a/docs/build-bot.md +++ b/docs/build-bot.md @@ -82,6 +82,8 @@ For a 'release' build level: * we won't upload artifacts to any release endpoint such as Debian, packages.sil.org, etc, or to the release areas for Play Store or App Store +(uploading to *.keyman.com and to release endpoints happens in the release TC build config/GHA) + ## Controlling the build bot with trailers The build bot respects commit trailers and trailers in the PR body. The commands From 0f4a1cdfe75f91e265aa06a6d1bc40049e677bfe Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 24 Jun 2025 09:03:28 +1000 Subject: [PATCH 048/262] fix(resources): allow branch names in run-required-test-builds shortcut call This is a transitional patch while we wait for the Build-bot changes to flow through all the open PRs. Fixes the broken master test build. --- resources/build/run-required-test-builds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/build/run-required-test-builds.sh b/resources/build/run-required-test-builds.sh index 4426e46bf8..3e8b60d951 100755 --- a/resources/build/run-required-test-builds.sh +++ b/resources/build/run-required-test-builds.sh @@ -21,7 +21,7 @@ builder_describe "Run test builds for the given pull request/primary branch" \ "--dry-run,-n Only report back which builds would be started" \ "--branch,-b=PRNUM Branch (master,beta,stable-x.y) or pull-request to test" -if [[ $# -eq 1 ]] && [[ "$1" =~ ^[0-9]+$ ]]; then +if [[ $# -eq 1 ]] && [[ "$1" =~ ^([0-9]+|master|beta|stable-[0-9]+\.[0-9]+)$ ]]; then # For transitional period, when build configuration on TC needs to support # both forms, we will pass a modified set of params to builder_parse # From 795f9f75aeb3d1e1478243a982048e1a068f8d8a Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 24 Jun 2025 09:47:12 +1000 Subject: [PATCH 049/262] maint(resources): build pr-build-status.yml from sources --- .github/workflows/pr-build-status.yml | 347 +++++++++--------- resources/build/pr-build-status/build.sh | 15 +- .../build/pr-build-status/pr-build-status.mjs | 22 +- .../pr-build-status.prefix.yml | 28 ++ 4 files changed, 228 insertions(+), 184 deletions(-) create mode 100644 resources/build/pr-build-status/pr-build-status.prefix.yml diff --git a/.github/workflows/pr-build-status.yml b/.github/workflows/pr-build-status.yml index d755eca9b7..01d89d6182 100644 --- a/.github/workflows/pr-build-status.yml +++ b/.github/workflows/pr-build-status.yml @@ -1,10 +1,18 @@ +# +# 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: - # Temporary for testing: - push: - branches: - - "maint/resources/14172-pr-build-status-2" - check_run: types: [completed] @@ -17,167 +25,168 @@ 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 reduceStatuses(statuses) { - 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}; - } 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};; - } else if(status.context == 'API Verification') { - if(!o[status.context]) o[status.context] = {type: 'check', state: status.state}; - } else if(status.context == 'Ubuntu Packaging') { - if(!o[status.context]) o[status.context] = {type: 'build', state: 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 - } 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 - o[status.context] = {type: 'unknown', state: status.state}; - } - return o; - - }, {}); - return filtered_statuses; - } - - // - // Given the collection of status checks we care about, return - // an aggregate status -- error, failed, pending, or success, - // and a summary description - // - function calculateFinalStatus(filtered_statuses) { - 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 - return [ - 'error', `An unknown context ${context} was found, cannot calculate build status.` - ]; - } - 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) { - return ['pending', 'Checks have not yet been triggered ⌛']; - } - - const state = - counts.error ? 'error' : - counts.failed ? 'failed' : - 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.failed, 'failed ❌'); - appendDescription(counts.pending, 'pending ⌛'); - appendDescription(counts.success, 'completed successfully ✅'); - - return [ state, description ]; - } - - async function getCommitStatuses(github, owner, repo, 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) { - 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 ❌']; - } - - const filtered_statuses = reduceStatuses(statuses); - - const result = calculateFinalStatus(filtered_statuses); - return result; - } - - 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) { - 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 || context.sha; - 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]); + 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 reduceStatuses(statuses) { + 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}; + } 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};; + } else if(status.context == 'API Verification') { + if(!o[status.context]) o[status.context] = {type: 'check', state: status.state}; + } else if(status.context == 'Ubuntu Packaging') { + if(!o[status.context]) o[status.context] = {type: 'build', state: 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 + } 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 + o[status.context] = {type: 'unknown', state: status.state}; + } + return o; + + }, {}); + return filtered_statuses; + } + + // + // Given the collection of status checks we care about, return + // an aggregate status -- error, failed, pending, or success, + // and a summary description + // + function calculateFinalStatus(filtered_statuses) { + 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 + return [ + 'error', `An unknown context ${context} was found, cannot calculate build status.` + ]; + } + 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) { + return ['pending', 'Checks have not yet been triggered ⌛']; + } + + const state = + counts.error ? 'error' : + counts.failed ? 'failed' : + 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.failed, 'failed ❌'); + appendDescription(counts.pending, 'pending ⌛'); + appendDescription(counts.success, 'completed successfully ✅'); + + return [ state, description ]; + } + + async function getCommitStatuses(github, owner, repo, 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) { + 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 ❌']; + } + + const filtered_statuses = reduceStatuses(statuses); + + const result = calculateFinalStatus(filtered_statuses); + return result; + } + + 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) { + 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 || context.sha; + 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]); + diff --git a/resources/build/pr-build-status/build.sh b/resources/build/pr-build-status/build.sh index 7672bb286c..f30623fa9b 100755 --- a/resources/build/pr-build-status/build.sh +++ b/resources/build/pr-build-status/build.sh @@ -6,11 +6,22 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" ## END STANDARD BUILD SCRIPT INCLUDE -builder_describe "Test the pr-build-status.yml GHA" test +builder_describe "Build and test the pr-build-status.yml GHA" build test builder_parse "$@" -# TODO: consider generating the .yml from here? +YML_PREFIX_FILE="${THIS_SCRIPT_PATH}/pr-build-status.prefix.yml" +YML_FILE="${KEYMAN_ROOT}/.github/workflows/pr-build-status.yml" +MJS_FILE="${THIS_SCRIPT_PATH}/pr-build-status.mjs" +# Regenerate pr-build-status.yml from the script here +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 "$YML_PREFIX\n$SCRIPT_CLONE\n$SCRIPT_SUFFIX\n" > "$YML_FILE" +} + +builder_run_action build do_build builder_run_action test npm test diff --git a/resources/build/pr-build-status/pr-build-status.mjs b/resources/build/pr-build-status/pr-build-status.mjs index cfd0dbd8c0..29034df5ff 100644 --- a/resources/build/pr-build-status/pr-build-status.mjs +++ b/resources/build/pr-build-status/pr-build-status.mjs @@ -1,16 +1,13 @@ /* * Keyman is copyright (C) SIL Global. MIT License. * - * Clone of the pr-build-status.yml file for unit testing and development. The - * indented section of this file below is copied verbatim into - * .github/workflows/pr-build-status.yml, and allows us to develop the YAML - * script without introducing a dependency on the repository. + * Source script of pr-build-status.yml file for unit testing and development. + * The indented section of this file below is copied verbatim into + * .github/workflows/pr-build-status.yml by build.sh build, and allows us to + * develop the YAML script without introducing a dependency on the repository. */ -// Copy the indented section into the step for pr-build-status.mjs. - -// START OF CLONED SECTION - +// CLONE:START // 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 @@ -86,7 +83,7 @@ counts.pending ? 'pending' : 'success'; - let description = ''; //; + let description = ''; function appendDescription(count, state) { if(!count) return; if(description != '') description += '; '; @@ -153,7 +150,6 @@ } async function updateCheck(github, owner, repo, checkRunId, status, description) { - // To ensure that const checkStatus = status == 'pending' ? 'in_progress' : 'completed'; const conclusion = checkStatus == 'in_progress' ? undefined : (status == 'success' ? 'success' : 'failure'); @@ -170,8 +166,8 @@ }); } -// END OF CLONED SECTION -// THIS SECTION MUST BE INCLUDED, UNCOMMENTED IN THE .yml +// CLONE:END +// CLONE-COMMENTED:START // const { owner, repo } = context.repo; // const sha = context.payload?.check_suite?.sha || context.sha; @@ -179,7 +175,7 @@ // const res = await test(github, owner, repo, sha); // await updateCheck(github, owner, repo, checkRunId, res[0], res[1]); -// END OF COMMENTED SECTION +// CLONE-COMMENTED:END // Following code is used only for unit testing; do not include in the .yml diff --git a/resources/build/pr-build-status/pr-build-status.prefix.yml b/resources/build/pr-build-status/pr-build-status.prefix.yml new file mode 100644 index 0000000000..6ac2d78896 --- /dev/null +++ b/resources/build/pr-build-status/pr-build-status.prefix.yml @@ -0,0 +1,28 @@ +# +# 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: + check_run: + types: [completed] + +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: | From 9de8d260cc3be84f4d1934a66c0e369aede84d83 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 24 Jun 2025 16:47:06 +1000 Subject: [PATCH 050/262] maint(resources): support additional workflow events for pr-build-status Also retrieves the correct SHA from each workflow event. Build-bot: skip Test-bot: skip --- .github/workflows/pr-build-status.yml | 22 +++++++++++++++---- .../build/pr-build-status/pr-build-status.mjs | 7 +++++- .../pr-build-status.prefix.yml | 15 ++++++++++--- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pr-build-status.yml b/.github/workflows/pr-build-status.yml index 01d89d6182..958ce67fd9 100644 --- a/.github/workflows/pr-build-status.yml +++ b/.github/workflows/pr-build-status.yml @@ -13,9 +13,18 @@ # name: Keyman Build Summary on: - check_run: - types: [completed] - + 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 @@ -185,7 +194,12 @@ jobs: } const { owner, repo } = context.repo; - const sha = context.payload?.check_suite?.sha || context.sha; + const sha = + context.payload?.check_suite?.sha || /* check run / status completed */ + context.payload?.inputs?.commit || /* manual run */ + context.payload?.push?.after || /* push */ + context.sha; + 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]); diff --git a/resources/build/pr-build-status/pr-build-status.mjs b/resources/build/pr-build-status/pr-build-status.mjs index 29034df5ff..8e1308d136 100644 --- a/resources/build/pr-build-status/pr-build-status.mjs +++ b/resources/build/pr-build-status/pr-build-status.mjs @@ -170,7 +170,12 @@ // CLONE-COMMENTED:START // const { owner, repo } = context.repo; - // const sha = context.payload?.check_suite?.sha || context.sha; + // const sha = + // context.payload?.check_suite?.sha || /* check run / status completed */ + // context.payload?.inputs?.commit || /* manual run */ + // context.payload?.push?.after || /* push */ + // context.sha; + // 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]); diff --git a/resources/build/pr-build-status/pr-build-status.prefix.yml b/resources/build/pr-build-status/pr-build-status.prefix.yml index 6ac2d78896..cbeba112a1 100644 --- a/resources/build/pr-build-status/pr-build-status.prefix.yml +++ b/resources/build/pr-build-status/pr-build-status.prefix.yml @@ -13,9 +13,18 @@ # name: Keyman Build Summary on: - check_run: - types: [completed] - + 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 From 6ab6e2b20e6cc7738a3b48972eabec88c304e9d6 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 24 Jun 2025 16:54:56 +1000 Subject: [PATCH 051/262] fix(resources): use correct payload for status event --- .github/workflows/pr-build-status.yml | 7 ++++--- resources/build/pr-build-status/pr-build-status.mjs | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pr-build-status.yml b/.github/workflows/pr-build-status.yml index 958ce67fd9..5e471b7f2f 100644 --- a/.github/workflows/pr-build-status.yml +++ b/.github/workflows/pr-build-status.yml @@ -195,10 +195,11 @@ jobs: const { owner, repo } = context.repo; const sha = - context.payload?.check_suite?.sha || /* check run / status completed */ + context.payload?.check_suite?.sha || /* check run completed */ context.payload?.inputs?.commit || /* manual run */ - context.payload?.push?.after || /* push */ - context.sha; + 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); diff --git a/resources/build/pr-build-status/pr-build-status.mjs b/resources/build/pr-build-status/pr-build-status.mjs index 8e1308d136..30df82deb3 100644 --- a/resources/build/pr-build-status/pr-build-status.mjs +++ b/resources/build/pr-build-status/pr-build-status.mjs @@ -171,10 +171,11 @@ // const { owner, repo } = context.repo; // const sha = - // context.payload?.check_suite?.sha || /* check run / status completed */ + // context.payload?.check_suite?.sha || /* check run completed */ // context.payload?.inputs?.commit || /* manual run */ - // context.payload?.push?.after || /* push */ - // context.sha; + // 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); From e4ccb6e961497686961a7e66e131623abae9e06d Mon Sep 17 00:00:00 2001 From: MengHeng <90595388+Meng-Heng@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:48:53 +0700 Subject: [PATCH 052/262] Apply suggestions from code review Co-authored-by: Marc Durdin --- .../help/guides/develop/touch-keyboard-tutorial/gestures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md b/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md index 0765a6dd62..c66039cee3 100644 --- a/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md +++ b/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md @@ -2,7 +2,7 @@ title: Using Gestures in Keyman Developer --- -Keyman Developer offers **Longpress** keys, **Flicks**, and **Multitaps** for touch keyboard layout. To allow gestures onto your keyboard, go to the Touch layout tab and define the keys as followed: +Keyman Developer offers **Longpress** keys, **Flicks**, and **Multitaps** for touch keyboard layout. To allow gestures onto your keyboard, go to the Touch layout tab and define the keys as follows: Keyman Developer touch layout gestures From 80e4ee0074f2d7b2de439af086f5272dfbda9c46 Mon Sep 17 00:00:00 2001 From: Meng-Heng Date: Tue, 24 Jun 2025 17:13:32 +0700 Subject: [PATCH 053/262] chore: include link to gestures docs in index and minor visual organization in index --- .../develop/touch-keyboard-tutorial/index.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/developer/docs/help/guides/develop/touch-keyboard-tutorial/index.md b/developer/docs/help/guides/develop/touch-keyboard-tutorial/index.md index bc7c8e792f..acfe3efcec 100644 --- a/developer/docs/help/guides/develop/touch-keyboard-tutorial/index.md +++ b/developer/docs/help/guides/develop/touch-keyboard-tutorial/index.md @@ -38,17 +38,23 @@ ago) used the / key as the deadkey. /d gave ɗ, and so on. It would be possible to use that desktop keyboard logic on touch -screens.  But your mobile device users will find these rules annoying. +screens. But your mobile device users will find these rules annoying. + Instead of two keys they would probably have to press four, since the standard alphabetical keyboard on most touch devices does not have the slash key. So they would have to press the key to switch to the numeric -keyboard, then press slash, then press the key to go back to the -alphabetical keyboard, then the letter. What touch screen users would +keyboard, then press slash, then press the key to switch back to the +alphabetical keyboard, and then the letter. What touch screen users would appreciate is something that uses the great feature of touch screens, the long press or press and hold on a letter, to see analogous letters. -So we’ll set up a touch screen keyboard that lets you press and hold ‘d’ -to see the two hooked d characters, press and hold ‘b’ to see the hooked -b characters, and so on. + +So we’ll set up a touch screen keyboard that lets you +* press and hold ‘d’ to see the two hooked d characters +* press and hold ‘b’ to see the hooked b characters, and so on. [Next: Making, testing and distributing the touch keyboard](making-touch-keyboard) + +## See also + +* Tips for using [gestures](gestures) in Keyman Developer From 7aadd622df568924e7111cf0d5f9890132c57bda Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 24 Jun 2025 15:09:55 +0200 Subject: [PATCH 054/262] maint(windows): fix location of kmcomp.zip --- .../developer/publish-developer-to-downloads-keyman-com.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/teamcity/developer/publish-developer-to-downloads-keyman-com.ps1 b/resources/teamcity/developer/publish-developer-to-downloads-keyman-com.ps1 index eb99dfb2e4..d7105bf6c9 100644 --- a/resources/teamcity/developer/publish-developer-to-downloads-keyman-com.ps1 +++ b/resources/teamcity/developer/publish-developer-to-downloads-keyman-com.ps1 @@ -48,7 +48,7 @@ mkdir ${upload_path} # # Build Keyman Compiler WINE archive # -copy ..\release\${kmcomp_zip} ..\${upload_path}\${kmcomp_zip} +copy release\${build_number}\${kmcomp_zip} ${upload_path}\${kmcomp_zip} # # Copy source files From d727991fff779ed4e2ed5d63cb6e89a4d4312030 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 24 Jun 2025 15:28:29 +0200 Subject: [PATCH 055/262] maint(common): simplify scripts This addresses code review comments. --- .../teamcity/common/common-test-common-web.sh | 14 ++----------- .../teamcity/common/common-test-common.sh | 14 ++----------- .../teamcity/common/common-test-core-wasm.sh | 16 +++------------ resources/teamcity/common/common-test-core.sh | 20 +++++++------------ 4 files changed, 14 insertions(+), 50 deletions(-) diff --git a/resources/teamcity/common/common-test-common-web.sh b/resources/teamcity/common/common-test-common-web.sh index c11b931c99..ea1994337e 100755 --- a/resources/teamcity/common/common-test-common-web.sh +++ b/resources/teamcity/common/common-test-common-web.sh @@ -1,7 +1,5 @@ #!/usr/bin/env bash # Keyman is copyright (C) SIL Global. MIT License. -# -# TC build script to build and run unit tests in /common/web # shellcheck disable=SC2164 # shellcheck disable=SC1091 @@ -16,16 +14,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" builder_describe \ "Run unit tests in /common/web" \ - "all run all actions" \ - "build build and test common/web" + "all configure, build, and test common/web" builder_parse "$@" -# shellcheck disable=SC2154 -cd "${KEYMAN_ROOT}/common/web" - -if builder_has_action all; then - "${KEYMAN_ROOT}/common/web/build.sh" configure build test -else - builder_run_action build "${KEYMAN_ROOT}/common/web/build.sh" configure build test -fi +builder_run_action all "${KEYMAN_ROOT}/common/web/build.sh" configure build test diff --git a/resources/teamcity/common/common-test-common.sh b/resources/teamcity/common/common-test-common.sh index 757e8e99db..b5fef4018b 100755 --- a/resources/teamcity/common/common-test-common.sh +++ b/resources/teamcity/common/common-test-common.sh @@ -1,7 +1,5 @@ #!/usr/bin/env bash # Keyman is copyright (C) SIL Global. MIT License. -# -# TC build script to build and test common modules # shellcheck disable=SC2164 # shellcheck disable=SC1091 @@ -16,16 +14,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" builder_describe \ "Run unit tests in /common" \ - "all run all actions" \ - "build build and test common modules" + "all configure, build, and test common modules" builder_parse "$@" -# shellcheck disable=SC2154 -cd "${KEYMAN_ROOT}/common" - -if builder_has_action all; then - "${KEYMAN_ROOT}/common/build.sh" configure build test -else - builder_run_action build "${KEYMAN_ROOT}/common/build.sh" configure build test -fi +builder_run_action all "${KEYMAN_ROOT}/common/build.sh" configure build test diff --git a/resources/teamcity/common/common-test-core-wasm.sh b/resources/teamcity/common/common-test-core-wasm.sh index d7c84ca30a..ceeefd83b2 100755 --- a/resources/teamcity/common/common-test-core-wasm.sh +++ b/resources/teamcity/common/common-test-core-wasm.sh @@ -1,7 +1,5 @@ #!/usr/bin/env bash # Keyman is copyright (C) SIL Global. MIT License. -# -# TC build script to test Keyman Core Desktop on WASM # shellcheck disable=SC2164 # shellcheck disable=SC1091 @@ -16,17 +14,9 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ################################ Main script ################################ builder_describe \ - "Test Keyman Core on Linux" \ - "all run all actions" \ - "build full meson build and test of Keyman Core for WASM" + "Test Keyman Core on WASM" \ + "all full meson build and test of Keyman Core for WASM" builder_parse "$@" -# shellcheck disable=SC2154 -cd "${KEYMAN_ROOT}/core" - -if builder_has_action all; then - "${KEYMAN_ROOT}/core/build.sh" configure:wasm build:wasm test:wasm -else - builder_run_action build "${KEYMAN_ROOT}/core/build.sh" configure:wasm build:wasm test:wasm -fi +builder_run_action build "${KEYMAN_ROOT}/core/build.sh" configure:wasm build:wasm test:wasm diff --git a/resources/teamcity/common/common-test-core.sh b/resources/teamcity/common/common-test-core.sh index 0674969ef7..236e4c2cee 100755 --- a/resources/teamcity/common/common-test-core.sh +++ b/resources/teamcity/common/common-test-core.sh @@ -1,7 +1,5 @@ #!/usr/bin/env bash # Keyman is copyright (C) SIL Global. MIT License. -# -# TC build script to test Keyman Core # shellcheck disable=SC2164 # shellcheck disable=SC1091 @@ -20,13 +18,10 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" builder_describe \ "Test Keyman Core" \ - "all run all actions" \ - "build full meson build and test of Keyman Core" + "all full meson build and test of Keyman Core" builder_parse "$@" -cd "${KEYMAN_ROOT}/core" - if is_ubuntu; then ARCH=arch elif is_windows; then @@ -38,12 +33,11 @@ else return 1 fi -if builder_has_action all; then +function do_all() { "${KEYMAN_ROOT}/core/build.sh" configure:${ARCH} build:${ARCH} test:${ARCH} -else - builder_run_action build "${KEYMAN_ROOT}/core/build.sh" configure:${ARCH} build:${ARCH} test:${ARCH} -fi + if is_windows; then + "${KEYMAN_ROOT}/core/build.sh" configure:x64 build:x64 test:x64 + fi +} -if is_windows; then - "${KEYMAN_ROOT}/core/build.sh" configure:x64 build:x64 test:x64 -fi +builder_run_action all do_all From bc5e05fd84f044f032b344d97673fa0c1dabb134 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 24 Jun 2025 18:48:02 +0200 Subject: [PATCH 056/262] refactor(developer): use shared `upload_help` function Build-bot: build developer Test-bot: skip --- .../teamcity/developer/keyman-developer-release.sh | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/resources/teamcity/developer/keyman-developer-release.sh b/resources/teamcity/developer/keyman-developer-release.sh index 5e969bddf6..bf4e4090dc 100755 --- a/resources/teamcity/developer/keyman-developer-release.sh +++ b/resources/teamcity/developer/keyman-developer-release.sh @@ -19,7 +19,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ################################ Main script ################################ builder_describe \ - "Build Keyman Developer on Windows" \ + "Build release of Keyman Developer" \ "all run all actions" \ "build build Keyman Developer and test keyboards" \ "publish publish release of Keyman Developer" \ @@ -70,16 +70,6 @@ function _publish_to_downloads_keyman_com() { builder_echo end "publish to downloads.keyman.com" success "Finished publishing release to downloads.keyman.com" } -function _publish_api_documentation() { - # Upload new Keyman Developer API documentation to help.keyman.com - builder_echo start "publish api documentation" "Uploading new Keyman Developer API documentation to help.keyman.com" - - export HELP_KEYMAN_COM="${HELP_KEYMAN_COM:-${KEYMAN_ROOT}/../help.keyman.com}" - "${KEYMAN_ROOT}/resources/build/help-keyman-com.sh" developer - - builder_echo end "publish api documentation" success "Finished uploading new Keyman Developer API documentation to help.keyman.com" -} - function build_developer_action() { _build_developer _build_testkeyboards @@ -101,7 +91,7 @@ function publish_action() { download_symbol_server_index publish_new_symbols _publish_to_downloads_keyman_com - _publish_api_documentation + upload_help "api documentation" developer } if builder_has_action all; then From 86c2e716b0d38262a50d90cba34ce87edf6e1f5a Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Tue, 24 Jun 2025 14:01:57 -0400 Subject: [PATCH 057/262] auto: increment master version to 19.0.69 --- HISTORY.md | 7 +++++++ VERSION.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index be1a83fb18..0741f732b6 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,12 @@ # Keyman Version History +## 19.0.68 alpha 2025-06-24 + +* maint(resources): Build-bot now available to control test builds (#14125) +* maint(developer): support buildLevel (#14196) +* fix(resources): allow branch names in run-required-test-builds shortcut call (#14217) +* maint(resources): add pr-build-status GitHub Action to summarize build status (#14199) + ## 19.0.67 alpha 2025-06-22 * chore(developer): ldml: additional line numbers (#14104) diff --git a/VERSION.md b/VERSION.md index bf865b419e..8cd8fece59 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.68 \ No newline at end of file +19.0.69 \ No newline at end of file From bb282303e4c0dc980aacac1a3689b616345ffed2 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 25 Jun 2025 05:47:12 +1000 Subject: [PATCH 058/262] maint(resources): minor cleanup Build-bot: skip --- .github/workflows/pr-build-status.yml | 1 + resources/build/pr-build-status/build.sh | 2 +- resources/build/pr-build-status/pr-build-status.mjs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-build-status.yml b/.github/workflows/pr-build-status.yml index 5e471b7f2f..31d31afbc2 100644 --- a/.github/workflows/pr-build-status.yml +++ b/.github/workflows/pr-build-status.yml @@ -1,3 +1,4 @@ +# GENERATED FILE - DO NOT EDIT! # # Keyman is copyright (C) SIL Global. MIT License. # diff --git a/resources/build/pr-build-status/build.sh b/resources/build/pr-build-status/build.sh index f30623fa9b..1b0572b959 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 "$YML_PREFIX\n$SCRIPT_CLONE\n$SCRIPT_SUFFIX\n" > "$YML_FILE" + echo -e "# 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/pr-build-status.mjs b/resources/build/pr-build-status/pr-build-status.mjs index 30df82deb3..6d4a26baaa 100644 --- a/resources/build/pr-build-status/pr-build-status.mjs +++ b/resources/build/pr-build-status/pr-build-status.mjs @@ -3,7 +3,7 @@ * * Source script of pr-build-status.yml file for unit testing and development. * The indented section of this file below is copied verbatim into - * .github/workflows/pr-build-status.yml by build.sh build, and allows us to + * .github/workflows/pr-build-status.yml by `build.sh build`, and allows us to * develop the YAML script without introducing a dependency on the repository. */ From 41b89bb0d1c26edd41a727b34f3cd3e10f76422c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 25 Jun 2025 05:50:30 +1000 Subject: [PATCH 059/262] maint(resources): add pr-build-status test to resources/build.sh Also tweak one output string for clarity. Build-bot: skip --- .github/workflows/pr-build-status.yml | 2 +- resources/build.sh | 1 + resources/build/pr-build-status/pr-build-status.mjs | 2 +- resources/build/pr-build-status/pr-build-status.tests.mjs | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pr-build-status.yml b/.github/workflows/pr-build-status.yml index 31d31afbc2..236970f141 100644 --- a/.github/workflows/pr-build-status.yml +++ b/.github/workflows/pr-build-status.yml @@ -102,7 +102,7 @@ jobs: // If we do not have any statuses yet, we wait if(Object.keys(filtered_statuses).length == 0 || !hasBuilds) { - return ['pending', 'Checks have not yet been triggered ⌛']; + return ['pending', 'Builds have not yet been triggered ⌛']; } const state = diff --git a/resources/build.sh b/resources/build.sh index 5ac7bde55d..48be50fa96 100755 --- a/resources/build.sh +++ b/resources/build.sh @@ -16,6 +16,7 @@ builder_parse "$@" if builder_start_action test; then ./build/test/test.sh ./build/test/build-bot/trigger-build-bot.test.sh + ./build/pr-build-status/build.sh test ./build/publish-minimum-versions.sh test builder_finish_action success test fi diff --git a/resources/build/pr-build-status/pr-build-status.mjs b/resources/build/pr-build-status/pr-build-status.mjs index 6d4a26baaa..091377d942 100644 --- a/resources/build/pr-build-status/pr-build-status.mjs +++ b/resources/build/pr-build-status/pr-build-status.mjs @@ -74,7 +74,7 @@ // If we do not have any statuses yet, we wait if(Object.keys(filtered_statuses).length == 0 || !hasBuilds) { - return ['pending', 'Checks have not yet been triggered ⌛']; + return ['pending', 'Builds have not yet been triggered ⌛']; } const state = 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 b9a4429eac..bfb49fe64b 100644 --- a/resources/build/pr-build-status/pr-build-status.tests.mjs +++ b/resources/build/pr-build-status/pr-build-status.tests.mjs @@ -12,8 +12,8 @@ const commits = { '825f7d2df0069fa56a8f2a534b42a49ee1f21ca1': [ 'success', '24 checks completed successfully ✅' ], '870d37f56b489ef5067b4352a06505369d9038e0': [ 'error', '6 checks in an error state ❌; 17 checks completed successfully ✅' ], '11a558e926370253db37026c95b7ff5a6aa1e8fc': [ 'success', '22 checks completed successfully ✅' ], - '8fd5ccc9026b85d6a780d8bcf862380e7a89aefb': [ 'pending', 'Checks have not yet been triggered ⌛' ], - '153683cfb007c6066c9dcf71d25afa4c66efa17f': [ 'pending', 'Checks have not yet been triggered ⌛' ], + '8fd5ccc9026b85d6a780d8bcf862380e7a89aefb': [ 'pending', 'Builds have not yet been triggered ⌛' ], + '153683cfb007c6066c9dcf71d25afa4c66efa17f': [ 'pending', 'Builds have not yet been triggered ⌛' ], }; // When adding new SHAs to test, run this to collect data from GitHub From 61faff205769fb29017388f14d65a5b6843dd20b Mon Sep 17 00:00:00 2001 From: DavidLRowe Date: Tue, 24 Jun 2025 21:40:03 -0800 Subject: [PATCH 060/262] Add details about Template choices --- developer/docs/help/context/keyboard-editor.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/developer/docs/help/context/keyboard-editor.md b/developer/docs/help/context/keyboard-editor.md index fb0be6d34c..5666625506 100644 --- a/developer/docs/help/context/keyboard-editor.md +++ b/developer/docs/help/context/keyboard-editor.md @@ -265,6 +265,9 @@ Template... current layout are not in the new template, their definitions will be lost. Therefore, selecting an appropriate template early in the development process is suggested. + - template-basic: has only a phone layout (4 rows of keys), with three layers: default (K_A through K_Z plus some punctuation), shift (Shift K_A through Shift K_Z, plus some punctuation), numeric (K_0 through K_9 unshifted and shifted, plus other non-alphanumerice keys). + - template-latin: has both phone and tablet layouts (which are identical except for some key spacing, each with 4 rows of keys), with four layers: default, shift, numeric, symbol. The first three layers are similar to those of template-basic, with the addition of longpress keys for accented characters. The symbol layer has many symbols not usually seen on a physical keyboard. + - template-traditional: has both phone (4 rows of keys) and tablet (5 rows of keys) layouts. The phone layout is similar to the template-basic layout. The tablet layout has eight layers: default, shift, ctrl, shift-ctrl, alt, shift-alt, ctrl-alt, shift-ctrl-alt. The default layer has the keys from the default layer of the desktop layout, the shift layer has the keys from the shift layer of the desktop layout, and so on. Import from On Screen : If you have an existing On-Screen Keyboard for your keyboard layout, @@ -631,4 +634,4 @@ web page. The Send to email function makes it easier to open the addresses on your mobile device without having to enter them manually. -Learn more about [testing on the web and mobile devices](../guides/test/). \ No newline at end of file +Learn more about [testing on the web and mobile devices](../guides/test/). From 5f20b2ff5671e339c68ad0ccebea8e1b4f2945ba Mon Sep 17 00:00:00 2001 From: DavidLRowe Date: Tue, 24 Jun 2025 21:41:57 -0800 Subject: [PATCH 061/262] adjust spacing --- developer/docs/help/context/keyboard-editor.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/developer/docs/help/context/keyboard-editor.md b/developer/docs/help/context/keyboard-editor.md index 5666625506..04b6a157f3 100644 --- a/developer/docs/help/context/keyboard-editor.md +++ b/developer/docs/help/context/keyboard-editor.md @@ -265,8 +265,11 @@ Template... current layout are not in the new template, their definitions will be lost. Therefore, selecting an appropriate template early in the development process is suggested. + - template-basic: has only a phone layout (4 rows of keys), with three layers: default (K_A through K_Z plus some punctuation), shift (Shift K_A through Shift K_Z, plus some punctuation), numeric (K_0 through K_9 unshifted and shifted, plus other non-alphanumerice keys). + - template-latin: has both phone and tablet layouts (which are identical except for some key spacing, each with 4 rows of keys), with four layers: default, shift, numeric, symbol. The first three layers are similar to those of template-basic, with the addition of longpress keys for accented characters. The symbol layer has many symbols not usually seen on a physical keyboard. + - template-traditional: has both phone (4 rows of keys) and tablet (5 rows of keys) layouts. The phone layout is similar to the template-basic layout. The tablet layout has eight layers: default, shift, ctrl, shift-ctrl, alt, shift-alt, ctrl-alt, shift-ctrl-alt. The default layer has the keys from the default layer of the desktop layout, the shift layer has the keys from the shift layer of the desktop layout, and so on. Import from On Screen From 9077e6a7b2707017ef123160f653d98c7fd4d7a6 Mon Sep 17 00:00:00 2001 From: DavidLRowe Date: Tue, 24 Jun 2025 21:46:24 -0800 Subject: [PATCH 062/262] fix line lengths --- developer/docs/help/context/keyboard-editor.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/developer/docs/help/context/keyboard-editor.md b/developer/docs/help/context/keyboard-editor.md index 04b6a157f3..c445bb7280 100644 --- a/developer/docs/help/context/keyboard-editor.md +++ b/developer/docs/help/context/keyboard-editor.md @@ -266,11 +266,19 @@ Template... be lost. Therefore, selecting an appropriate template early in the development process is suggested. - - template-basic: has only a phone layout (4 rows of keys), with three layers: default (K_A through K_Z plus some punctuation), shift (Shift K_A through Shift K_Z, plus some punctuation), numeric (K_0 through K_9 unshifted and shifted, plus other non-alphanumerice keys). + - template-basic: has only a phone layout (4 rows of keys), with three layers: default (K_A through K_Z + plus some punctuation), shift (Shift K_A through Shift K_Z, plus some punctuation), numeric (K_0 through + K_9 unshifted and shifted, plus other non-alphanumerice keys). - - template-latin: has both phone and tablet layouts (which are identical except for some key spacing, each with 4 rows of keys), with four layers: default, shift, numeric, symbol. The first three layers are similar to those of template-basic, with the addition of longpress keys for accented characters. The symbol layer has many symbols not usually seen on a physical keyboard. + - template-latin: has both phone and tablet layouts (which are identical except for some key spacing, + each with 4 rows of keys), with four layers: default, shift, numeric, symbol. The first three layers + are similar to those of template-basic, with the addition of longpress keys for accented characters. + The symbol layer has many symbols not usually seen on a physical keyboard. - - template-traditional: has both phone (4 rows of keys) and tablet (5 rows of keys) layouts. The phone layout is similar to the template-basic layout. The tablet layout has eight layers: default, shift, ctrl, shift-ctrl, alt, shift-alt, ctrl-alt, shift-ctrl-alt. The default layer has the keys from the default layer of the desktop layout, the shift layer has the keys from the shift layer of the desktop layout, and so on. + - template-traditional: has both phone (4 rows of keys) and tablet (5 rows of keys) layouts. The phone + layout is similar to the template-basic layout. The tablet layout has eight layers: default, shift, ctrl, + shift-ctrl, alt, shift-alt, ctrl-alt, shift-ctrl-alt. The default layer has the keys from the default layer + of the desktop layout, the shift layer has the keys from the shift layer of the desktop layout, and so on. Import from On Screen : If you have an existing On-Screen Keyboard for your keyboard layout, From 3262946928fc66976cd153026b1e2b9caf81d1a8 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Wed, 25 Jun 2025 14:01:20 -0400 Subject: [PATCH 063/262] auto: increment master version to 19.0.70 --- HISTORY.md | 5 +++++ VERSION.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 0741f732b6..421bd68a1f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # Keyman Version History +## 19.0.69 alpha 2025-06-25 + +* maint(resources): support additional workflow events for pr-build-status (#14219) +* chore: add tips and caution of gestures for Keyman Developer + update license + removal of span tag (#14204) + ## 19.0.68 alpha 2025-06-24 * maint(resources): Build-bot now available to control test builds (#14125) diff --git a/VERSION.md b/VERSION.md index 8cd8fece59..15ad51e2c1 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.69 \ No newline at end of file +19.0.70 \ No newline at end of file From 399baddf23a338d1fd8202d82a7603433dab3ab7 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 25 Jun 2025 20:13:09 +0200 Subject: [PATCH 064/262] refactor(linux): rename linux agent specific functions This change prefixes functions with `ba_` that are intended to be run on a Linux build agent. That should make it easier to distinguish them from functions that deal with building Keyman for Linux. Test-bot: skip --- .../developer/developer-actions.inc.sh | 2 +- resources/teamcity/includes/tc-actions.inc.sh | 6 +++--- resources/teamcity/includes/tc-helpers.inc.sh | 4 ++-- resources/teamcity/includes/tc-linux.inc.sh | 18 +++++++++--------- resources/teamcity/web/web-actions.inc.sh | 12 ++++++------ 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/resources/teamcity/developer/developer-actions.inc.sh b/resources/teamcity/developer/developer-actions.inc.sh index 45d3792dbe..66cb82756a 100644 --- a/resources/teamcity/developer/developer-actions.inc.sh +++ b/resources/teamcity/developer/developer-actions.inc.sh @@ -8,7 +8,7 @@ developer_install_dependencies_on_linux_action() { builder_echo start "install dependencies" "Installing dependencies" - linux_check_and_install_packages devscripts jq meson + ba_linux_check_and_install_packages devscripts jq meson install_nvm install_emscripten diff --git a/resources/teamcity/includes/tc-actions.inc.sh b/resources/teamcity/includes/tc-actions.inc.sh index 8f671e14b2..0834c32f43 100644 --- a/resources/teamcity/includes/tc-actions.inc.sh +++ b/resources/teamcity/includes/tc-actions.inc.sh @@ -26,15 +26,15 @@ linux_additional_test_dependencies_action() { fi # shellcheck disable=SC2086 - linux_check_and_install_packages ${TOINSTALL} + ba_linux_check_and_install_packages ${TOINSTALL} if ! is_os_version_or_higher 24.04; then builder_heading "Installing python3-coverage from pip" pip3 install --user coverage fi - linux_install_nvm - linux_install_dependencies_for_tests + ba_linux_install_nvm + ba_linux_install_dependencies_for_tests builder_echo end additional_dependencies success "Finished installing additional dependencies" } diff --git a/resources/teamcity/includes/tc-helpers.inc.sh b/resources/teamcity/includes/tc-helpers.inc.sh index 5170f3c25e..c2d2253010 100644 --- a/resources/teamcity/includes/tc-helpers.inc.sh +++ b/resources/teamcity/includes/tc-helpers.inc.sh @@ -34,7 +34,7 @@ install_nvm() { # on Windows and macOS build agents are configured manually return 0 fi - linux_install_nvm + ba_linux_install_nvm } # Set the environment variables required to use node/nvm and set the @@ -59,7 +59,7 @@ install_emscripten() { # on Windows and macOS build agents are configured manually return 0 fi - linux_install_emscripten + ba_linux_install_emscripten } set_variables_for_emscripten() { diff --git a/resources/teamcity/includes/tc-linux.inc.sh b/resources/teamcity/includes/tc-linux.inc.sh index cf1964de21..8427ba765e 100644 --- a/resources/teamcity/includes/tc-linux.inc.sh +++ b/resources/teamcity/includes/tc-linux.inc.sh @@ -15,7 +15,7 @@ is_os_version_or_higher() { # Returns 0 if the specified package is installed. # Parameter: # $1 - Package name to check (e.g., "curl") -is_package_installed() { +_is_package_installed() { local PACKAGE=$1 dpkg -s "${PACKAGE}" >/dev/null 2>&1 @@ -24,7 +24,7 @@ is_package_installed() { # Check if the specified packages are installed and install them if not. # Parameters: # $* - List of package names to check and install (e.g., "lcov jq") -linux_check_and_install_packages() { +ba_linux_check_and_install_packages() { if ! is_ubuntu; then return 0 fi @@ -35,7 +35,7 @@ linux_check_and_install_packages() { for p in ${PACKAGES} do - if ! is_package_installed "${p}"; then + if ! _is_package_installed "${p}"; then TOINSTALL="${TOINSTALL} ${p}" fi done @@ -49,7 +49,7 @@ linux_check_and_install_packages() { } # Install nvm if it is not already installed and install the latest LTS -linux_install_nvm() { +ba_linux_install_nvm() { builder_echo start install_nvm "Checking and installing nvm" if [[ -f "${HOME}/.nvm/nvm.sh" ]]; then builder_echo "nvm is already installed" @@ -66,7 +66,7 @@ linux_install_nvm() { builder_echo end install_nvm success "Finished checking and installing nvm" } -linux_install_emscripten() { +ba_linux_install_emscripten() { builder_echo start "install emscripten" "Checking and installing emscripten" if { [[ ! -z "${EMSCRIPTEN_BASE:-}" ]] && [[ -f "${EMSCRIPTEN_BASE}/emcc" ]] ; } || [[ -f "${HOME}/emsdk/upstream/emscripten/emcc" ]]; then @@ -90,15 +90,15 @@ linux_install_emscripten() { } # Install additional dependencies required for running integration tests. -linux_install_dependencies_for_tests() { +ba_linux_install_dependencies_for_tests() { builder_echo start install_dependencies_for_tests "Installing dependencies for tests" - linux_check_and_install_packages xvfb xserver-xephyr metacity mutter dbus-x11 weston xwayland + ba_linux_check_and_install_packages xvfb xserver-xephyr metacity mutter dbus-x11 weston xwayland builder_echo end install_dependencies_for_tests success "Finished installing dependencies for tests" } -linux_start_xvfb() { +ba_linux_start_xvfb() { # On Linux start Xvfb etc local PID_FILE=/tmp/keymanweb-pids builder_echo "Starting Xvfb..." @@ -115,7 +115,7 @@ linux_start_xvfb() { export DISPLAY=:32 } -linux_stop_xvfb() { +ba_linux_stop_xvfb() { # On Linux stop Xvfb etc local PID_FILE=/tmp/keymanweb-pids if [[ -f "${PID_FILE}" ]]; then diff --git a/resources/teamcity/web/web-actions.inc.sh b/resources/teamcity/web/web-actions.inc.sh index 187eef8bb6..60665e01f2 100644 --- a/resources/teamcity/web/web-actions.inc.sh +++ b/resources/teamcity/web/web-actions.inc.sh @@ -9,9 +9,9 @@ web_install_dependencies_on_linux_action() { builder_echo start "install dependencies" "Install dependencies" # shellcheck disable=SC2086 - linux_check_and_install_packages devscripts jq + ba_linux_check_and_install_packages devscripts jq - linux_install_nvm + ba_linux_install_nvm _install_playwright_dependencies builder_echo end "install dependencies" success "Finished installing dependencies" @@ -23,7 +23,7 @@ _install_playwright_dependencies() { fi # shellcheck disable=SC2086 - linux_check_and_install_packages ibevent-2.1-7t64 libxslt1.1 libwoff1 \ + ba_linux_check_and_install_packages ibevent-2.1-7t64 libxslt1.1 libwoff1 \ libvpx9 libgstreamer-plugins-bad1.0-0 libwebpdemux2 libharfbuzz-icu0 \ libenchant-2-2 libsecret-1-0 libhyphen0 libmanette-0.2-0 libflite1 \ gstreamer1.0-libav libnss3 libnspr4 libatk1.0-0t64 libatk-bridge2.0-0t64 \ @@ -41,14 +41,14 @@ web_build_action() { web_test_action() { builder_echo start web_test "Running tests for native KeymanWeb" if is_ubuntu; then - linux_start_xvfb - trap "linux_stop_xvfb" ERR + ba_linux_start_xvfb + trap "ba_linux_stop_xvfb" ERR fi "${KEYMAN_ROOT}/web/ci.sh" test if is_ubuntu; then - linux_stop_xvfb + ba_linux_stop_xvfb trap ERR fi From 28ba904b370a6dcbc36f7eb36ae33b57fdb2516c Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 13 Jun 2025 18:47:02 -0500 Subject: [PATCH 065/262] chore(developer): rename ObjectWithMetadata to ObjectWithCompileContext Fixes: #14117 --- .../docs/internal/developer-messaging.md | 2 +- .../web/utils/src/compiler-interfaces.ts | 8 +- .../web/utils/src/developer-utils-messages.ts | 4 +- developer/src/common/web/utils/src/index.ts | 2 +- .../src/common/web/utils/src/symbol-utils.ts | 6 +- .../src/compiler/kmn-compiler-messages.ts | 10 +- .../kmc-ldml/src/compiler/empty-compiler.ts | 4 +- .../src/compiler/ldml-compiler-messages.ts | 102 +++++++++--------- developer/src/kmc-ldml/src/compiler/loca.ts | 4 +- .../src/compiler/substitution-tracker.ts | 24 ++--- developer/src/kmc-ldml/src/compiler/tran.ts | 8 +- developer/src/kmc-ldml/src/compiler/vars.ts | 4 +- 12 files changed, 89 insertions(+), 89 deletions(-) diff --git a/developer/docs/internal/developer-messaging.md b/developer/docs/internal/developer-messaging.md index d7fe3bb9d4..ab6c6afd4a 100644 --- a/developer/docs/internal/developer-messaging.md +++ b/developer/docs/internal/developer-messaging.md @@ -102,7 +102,7 @@ Example: keys.ts \- compiler \- call site for error ```js static ERROR_InvalidScanCode = SevError | 0x0009; -static Error_InvalidScanCode = (o:{id: string, invalidCodeList: string}, x: ObjectWithMetadata) => mx( +static Error_InvalidScanCode = (o:{id: string, invalidCodeList: string}, x: ObjectWithCompileContext) => mx( this.ERROR_InvalidScanCode, context, Form '${def(o.id)}' has invalid/unknown scancodes '${def(o.codes)}', `…additional markdown detail…` diff --git a/developer/src/common/web/utils/src/compiler-interfaces.ts b/developer/src/common/web/utils/src/compiler-interfaces.ts index 3cce4e75ef..f24250fb54 100644 --- a/developer/src/common/web/utils/src/compiler-interfaces.ts +++ b/developer/src/common/web/utils/src/compiler-interfaces.ts @@ -1,5 +1,5 @@ import { CompilerCallbacks } from "./compiler-callbacks.js"; -import { ObjectWithMetadata } from "./symbol-utils.js"; +import { ObjectWithCompileContext } from "./symbol-utils.js"; import { KeymanXMLReader, XML_FILENAME_SYMBOL } from "./xml-utils.js"; /** @@ -215,7 +215,7 @@ export class CompilerError { * @param x any object parsed from XML or with the XML_META_DATA_SYMBOL symbol copied over * @returns modified event object */ - public static setFromMetadata(event: CompilerEvent, x?: ObjectWithMetadata): CompilerEvent { + public static setFromMetadata(event: CompilerEvent, x?: ObjectWithCompileContext): CompilerEvent { if(x) { const metadata = KeymanXMLReader.getMetaData(x) || {}; const offset = metadata?.startIndex; @@ -458,7 +458,7 @@ export function dedentCompilerMessageDetail(event: CompilerEvent) { * // it contains redundant info. * // 2. No code execution within the arrow function other than the 'mx' call, string interpolation, * // with `${def(o.property)}` as the max complexity of interpolation. - * static Error_InvalidScanCode = (o:{id: string, invalidCodeList: string}, x: ObjectWithMetadata) => mx( + * static Error_InvalidScanCode = (o:{id: string, invalidCodeList: string}, x: ObjectWithCompileContext) => mx( * this.ERROR_InvalidScanCode, x, * `Form '${def(o.id)}' has invalid/unknown scancodes '${def(o.codes)}'`, * // Note: If detail is omitted, leave the trailing comma on the prior line to leave room for it @@ -474,7 +474,7 @@ export function dedentCompilerMessageDetail(event: CompilerEvent) { * @see CompilerMessageSpec * @returns the event */ -export function CompilerMessageObjectSpec(code: number, context: ObjectWithMetadata, message: string, detail?: string): CompilerEvent { +export function CompilerMessageObjectSpec(code: number, context: ObjectWithCompileContext, message: string, detail?: string): CompilerEvent { let evt = CompilerMessageSpec(code, message, detail); // constructs raw message evt = CompilerError.setFromMetadata(evt, context); // updates with offset from context return evt; diff --git a/developer/src/common/web/utils/src/developer-utils-messages.ts b/developer/src/common/web/utils/src/developer-utils-messages.ts index cd1eb8d2fe..bad77b8bb5 100644 --- a/developer/src/common/web/utils/src/developer-utils-messages.ts +++ b/developer/src/common/web/utils/src/developer-utils-messages.ts @@ -3,7 +3,7 @@ */ import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageDef as def, CompilerMessageSpec as m, CompilerMessageObjectSpec as mx } from './compiler-interfaces.js'; import { constants } from '@keymanapp/ldml-keyboard-constants'; -import { ObjectWithMetadata } from './symbol-utils.js'; +import { ObjectWithCompileContext } from './symbol-utils.js'; const DeveloperUtilsErrMask = CompilerErrorNamespace.DeveloperUtils; // const SevInfo = CompilerErrorSeverity.Info | DeveloperUtilsErrMask; @@ -15,7 +15,7 @@ const SevError = CompilerErrorSeverity.Error | DeveloperUtilsErrMask; export class DeveloperUtilsMessages { // structured Ajv validation error static ERROR_SchemaValidationError = SevError | 0x0001; - static Error_SchemaValidationError = (o:{instancePath:string, keyword:string, message: string, params: string}, x?: ObjectWithMetadata) => mx( + static Error_SchemaValidationError = (o:{instancePath:string, keyword:string, message: string, params: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_SchemaValidationError, x, `Error validating LDML XML file: ${def(o.instancePath)}: ${def(o.keyword)}: ${def(o.message)} ${def(o.params)}`, ); diff --git a/developer/src/common/web/utils/src/index.ts b/developer/src/common/web/utils/src/index.ts index 1174dd4684..5da53ef028 100644 --- a/developer/src/common/web/utils/src/index.ts +++ b/developer/src/common/web/utils/src/index.ts @@ -68,7 +68,7 @@ export { UrlSubpathCompilerCallback } from './utils/UrlSubpathCompilerCallback.j export { DeveloperUtilsMessages } from './developer-utils-messages.js'; export * as SourceFilenamePatterns from './source-filename-patterns.js'; export { KeymanXMLType, KeymanXMLWriter, KeymanXMLReader, KeymanXMLMetadata, XML_FILENAME_SYMBOL, withOffset } from './xml-utils.js'; -export { SymbolUtils, ObjectWithMetadata } from './symbol-utils.js'; +export { SymbolUtils, ObjectWithCompileContext as ObjectWithCompileContext } from './symbol-utils.js'; export * as LineUtils from './line-utils.js'; export * as GitHubUrls from './github-urls.js'; export * as CloudUrls from './cloud-urls.js'; diff --git a/developer/src/common/web/utils/src/symbol-utils.ts b/developer/src/common/web/utils/src/symbol-utils.ts index 9404b404d8..f050385c22 100644 --- a/developer/src/common/web/utils/src/symbol-utils.ts +++ b/developer/src/common/web/utils/src/symbol-utils.ts @@ -12,7 +12,7 @@ * Defined as 'any' here to reduce noise on the client side. * @see {@link KeymanXMLReader.getMetaData()} */ -export type ObjectWithMetadata = any; +export type ObjectWithCompileContext = any; export class SymbolUtils { /** @@ -21,8 +21,8 @@ export class SymbolUtils { * @param from source for symbols * @returns the onto object */ - public static copySymbols(onto: T, from: ObjectWithMetadata): T { - const o = onto as ObjectWithMetadata; + public static copySymbols(onto: T, from: ObjectWithCompileContext): T { + const o = onto as ObjectWithCompileContext; for (const sym of Object.getOwnPropertySymbols(from)) { o[sym] = from[sym]; } diff --git a/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts b/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts index 3274a62ca1..7271f16601 100644 --- a/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts +++ b/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts @@ -1,6 +1,6 @@ import { KeyAddress } from "../kmw-compiler/validate-layout-file.js"; import { kmnfile } from "../kmw-compiler/compiler-globals.js"; -import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageSpec as m, CompilerMessageObjectSpec as mx, CompilerMessageDef as def, CompilerMessageSpecWithException, KeymanUrls, ObjectWithMetadata } from "@keymanapp/developer-utils"; +import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageSpec as m, CompilerMessageObjectSpec as mx, CompilerMessageDef as def, CompilerMessageSpecWithException, KeymanUrls, ObjectWithCompileContext } from "@keymanapp/developer-utils"; const Namespace = CompilerErrorNamespace.KmnCompiler; const SevInfo = CompilerErrorSeverity.Info | Namespace; @@ -107,7 +107,7 @@ export class KmnCompilerMessages { ); static FATAL_UnicodeSetOutOfRange = SevFatal | 0x904; - static Fatal_UnicodeSetOutOfRange = (x?: ObjectWithMetadata) => mx( + static Fatal_UnicodeSetOutOfRange = (x?: ObjectWithCompileContext) => mx( this.FATAL_UnicodeSetOutOfRange, x, `UnicodeSet buffer was too small`, `Raised when caller to UnicodeSet functions provides an invalid buffer. If @@ -118,7 +118,7 @@ export class KmnCompilerMessages { // TODO: rename the following functions to Error_UsetHasStrings etc static ERROR_UnicodeSetHasStrings = SevError | 0x905; - static Error_UnicodeSetHasStrings = (x?: ObjectWithMetadata) => mx( + static Error_UnicodeSetHasStrings = (x?: ObjectWithCompileContext) => mx( this.ERROR_UnicodeSetHasStrings, x, `uset contains strings, not allowed`, `The provided uset uses multi-character strings, (\`{}\` notation, e.g. @@ -130,7 +130,7 @@ export class KmnCompilerMessages { ); static ERROR_UnicodeSetHasProperties = SevError | 0x906; - static Error_UnicodeSetHasProperties = (x?: ObjectWithMetadata) => mx( + static Error_UnicodeSetHasProperties = (x?: ObjectWithCompileContext) => mx( this.ERROR_UnicodeSetHasProperties, x, `uset contains properties, not allowed`, `The provided uset uses property notation (\`\\p{…}\` or \`[:…:]\`). LDML @@ -142,7 +142,7 @@ export class KmnCompilerMessages { ); static ERROR_UnicodeSetSyntaxError = SevError | 0x907; - static Error_UnicodeSetSyntaxError = (x?: ObjectWithMetadata) => mx( + static Error_UnicodeSetSyntaxError = (x?: ObjectWithCompileContext) => mx( this.ERROR_UnicodeSetSyntaxError, x, `uset had a Syntax Error while parsing`, `The provided uset has a syntax error and could not be parsed. Verify the diff --git a/developer/src/kmc-ldml/src/compiler/empty-compiler.ts b/developer/src/kmc-ldml/src/compiler/empty-compiler.ts index 79015dca6b..42d09d859d 100644 --- a/developer/src/kmc-ldml/src/compiler/empty-compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/empty-compiler.ts @@ -1,7 +1,7 @@ import { SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants'; import { SectionCompiler } from "./section-compiler.js"; import { util, KMXPlus, LdmlKeyboardTypes } from "@keymanapp/common-types"; -import { CompilerCallbacks, LDMLKeyboard, ObjectWithMetadata } from "@keymanapp/developer-utils"; +import { CompilerCallbacks, LDMLKeyboard, ObjectWithCompileContext } from "@keymanapp/developer-utils"; import { VarsCompiler } from './vars.js'; import { LdmlCompilerMessages } from './ldml-compiler-messages.js'; @@ -34,7 +34,7 @@ export class StrsCompiler extends EmptyCompiler { const strs = section; /** attempt to find a context object for the string */ - function findContextForString(s: string): ObjectWithMetadata { + function findContextForString(s: string): ObjectWithCompileContext { // try exact match for(const str of strs.strings) { if (str.value == s) return str.context; diff --git a/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts b/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts index f76e0d3b3a..b4f4039924 100644 --- a/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts +++ b/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts @@ -1,4 +1,4 @@ -import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageObjectSpec as mx, CompilerMessageSpec as m, CompilerMessageDef as def, ObjectWithMetadata } from '@keymanapp/developer-utils'; +import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageObjectSpec as mx, CompilerMessageSpec as m, CompilerMessageDef as def, ObjectWithCompileContext } from '@keymanapp/developer-utils'; // const SevInfo = CompilerErrorSeverity.Info | CompilerErrorNamespace.LdmlKeyboardCompiler; const SevHint = CompilerErrorSeverity.Hint | CompilerErrorNamespace.LdmlKeyboardCompiler; const SevWarn = CompilerErrorSeverity.Warn | CompilerErrorNamespace.LdmlKeyboardCompiler; @@ -13,37 +13,37 @@ const SevErrorTransform = SevError | 0xF00; */ export class LdmlCompilerMessages { static HINT_NormalizationDisabled = SevHint | 0x0001; - static Hint_NormalizationDisabled = (x?: ObjectWithMetadata) => mx( + static Hint_NormalizationDisabled = (x?: ObjectWithCompileContext) => mx( this.HINT_NormalizationDisabled, x, `normalization=disabled is not recommended.`, ); static ERROR_InvalidLocale = SevError | 0x0002; - static Error_InvalidLocale = (o:{tag: string}, x?: ObjectWithMetadata) => mx( + static Error_InvalidLocale = (o:{tag: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_InvalidLocale, x, `Invalid BCP 47 locale form '${def(o.tag)}'`, ); static ERROR_HardwareLayerHasTooManyRows = SevError | 0x0003; - static Error_HardwareLayerHasTooManyRows = (x?: ObjectWithMetadata) => mx( + static Error_HardwareLayerHasTooManyRows = (x?: ObjectWithCompileContext) => mx( this.ERROR_HardwareLayerHasTooManyRows, x, `'hardware' layer has too many rows`, ); static ERROR_RowOnHardwareLayerHasTooManyKeys = SevError | 0x0004; - static Error_RowOnHardwareLayerHasTooManyKeys = (o: { row: number, hardware: string, modifiers: string }, x?: ObjectWithMetadata) => mx( + static Error_RowOnHardwareLayerHasTooManyKeys = (o: { row: number, hardware: string, modifiers: string }, x?: ObjectWithCompileContext) => mx( this.ERROR_RowOnHardwareLayerHasTooManyKeys, x, `Row #${def(o.row)} on 'hardware' ${def(o.hardware)} layer for modifier ${def(o.modifiers)} has too many keys`, ); static ERROR_KeyNotFoundInKeyBag = SevError | 0x0005; - static Error_KeyNotFoundInKeyBag = (o: { keyId: string, col: number, row: number, layer: string, form: string }, x?: ObjectWithMetadata) => mx( + static Error_KeyNotFoundInKeyBag = (o: { keyId: string, col: number, row: number, layer: string, form: string }, x?: ObjectWithCompileContext) => mx( this.ERROR_KeyNotFoundInKeyBag, x, `Key '${def(o.keyId)}' in position #${def(o.col)} on row #${def(o.row)} of layer ${def(o.layer)}, form '${def(o.form)}' not found in key bag`, ); static HINT_OneOrMoreRepeatedLocales = SevHint | 0x0006; - static Hint_OneOrMoreRepeatedLocales = (x?: ObjectWithMetadata) => mx( + static Hint_OneOrMoreRepeatedLocales = (x?: ObjectWithCompileContext) => mx( this.HINT_OneOrMoreRepeatedLocales, x, `After minimization, one or more locales is repeated and has been removed`, ); @@ -54,37 +54,37 @@ export class LdmlCompilerMessages { m(this.ERROR_InvalidFile, `The source file has an invalid structure: ${def(o.errorText)}`); static HINT_LocaleIsNotMinimalAndClean = SevHint | 0x0008; - static Hint_LocaleIsNotMinimalAndClean = (o:{sourceLocale: string, locale: string}, x?: ObjectWithMetadata) => mx( + static Hint_LocaleIsNotMinimalAndClean = (o:{sourceLocale: string, locale: string}, x?: ObjectWithCompileContext) => mx( this.HINT_LocaleIsNotMinimalAndClean, x, `Locale '${def(o.sourceLocale)}' is not minimal or correctly formatted and should be '${def(o.locale)}'`, ); static ERROR_InvalidScanCode = SevError | 0x0009; - static Error_InvalidScanCode = (o: { codes: string, id: string }, x?: ObjectWithMetadata) => mx( + static Error_InvalidScanCode = (o: { codes: string, id: string }, x?: ObjectWithCompileContext) => mx( this.ERROR_InvalidScanCode, x, `Form '${def(o.id)}' has invalid/unknown scancodes '${def(o.codes)}'`, ); static WARN_CustomForm = SevWarn | 0x000A; - static Warn_CustomForm = (o: { id: string }, x?: ObjectWithMetadata) => mx( + static Warn_CustomForm = (o: { id: string }, x?: ObjectWithCompileContext) => mx( this.WARN_CustomForm, x, `Custom
element. Key layout may not be as expected.`, ); static ERROR_GestureKeyNotFoundInKeyBag = SevError | 0x000B; - static Error_GestureKeyNotFoundInKeyBag = (o:{keyId: string, parentKeyId: string, attribute: string}, x?: ObjectWithMetadata) => mx( + static Error_GestureKeyNotFoundInKeyBag = (o:{keyId: string, parentKeyId: string, attribute: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_GestureKeyNotFoundInKeyBag, x, `Key '${def(o.keyId)}' not found in key bag, referenced from other '${def(o.parentKeyId)}' in ${def(o.attribute)}`, ); static HINT_NoDisplayForMarker = SevHint | 0x000C; - static Hint_NoDisplayForMarker = (o: { id: string }, x?: ObjectWithMetadata) => mx( + static Hint_NoDisplayForMarker = (o: { id: string }, x?: ObjectWithCompileContext) => mx( this.HINT_NoDisplayForMarker, x, `Key element with id "${def(o.id)}" has only marker output, but there is no matching display element by output or keyId. Keycap may be blank.`, ); static ERROR_InvalidVersion = SevError | 0x000D; - static Error_InvalidVersion = (o: { version: string; }, x?: ObjectWithMetadata) => mx( + static Error_InvalidVersion = (o: { version: string; }, x?: ObjectWithCompileContext) => mx( this.ERROR_InvalidVersion, x, `Version number '${def(o.version)}' must be a semantic version format string with 'major.minor.patch' components.`, `The version number in the LDML keyboard file must be a [semantic @@ -98,152 +98,152 @@ export class LdmlCompilerMessages { ); static ERROR_MustBeAtLeastOneLayerElement = SevError | 0x000E; - static Error_MustBeAtLeastOneLayerElement = (x?: ObjectWithMetadata) => mx( + static Error_MustBeAtLeastOneLayerElement = (x?: ObjectWithCompileContext) => mx( this.ERROR_MustBeAtLeastOneLayerElement, x, `The source file must contain at least one layer element.`, ); static HINT_NoDisplayForSwitch = SevHint | 0x000F; - static Hint_NoDisplayForSwitch = (o: { id: string }, x?: ObjectWithMetadata) => mx( + static Hint_NoDisplayForSwitch = (o: { id: string }, x?: ObjectWithCompileContext) => mx( this.HINT_NoDisplayForSwitch, x, `Key element with id "${def(o.id)}" is a layer switch key, but there is no matching display element by keyId. Keycap may be blank.`, ); static ERROR_DisplayIsRepeated = SevError | 0x0010; - static Error_DisplayIsRepeated = (o:{display?: string}, x?: ObjectWithMetadata) => mx( + static Error_DisplayIsRepeated = (o:{display?: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_DisplayIsRepeated, x, `display display='${def(o.display)}' refers to the same keyId or output as another entry.`, ); static ERROR_KeyMissingToGapOrSwitch = SevError | 0x0011; - static Error_KeyMissingToGapOrSwitch = (o:{keyId: string}, x?: ObjectWithMetadata) => mx( + static Error_KeyMissingToGapOrSwitch = (o:{keyId: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_KeyMissingToGapOrSwitch, x, `key id='${def(o.keyId)}' must have either output=, gap=, or layerId=.`, ); static ERROR_ExcessHardware = SevError | 0x0012; - static Error_ExcessHardware = (o:{formId: string}, x?: ObjectWithMetadata) => mx( + static Error_ExcessHardware = (o:{formId: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_ExcessHardware, x, `layers formId=${def(o.formId)}: Can only have one non-'touch' element`, ); static ERROR_InvalidHardware = SevError | 0x0013; - static Error_InvalidHardware = (o: { formId: string }, x?: ObjectWithMetadata) => mx( + static Error_InvalidHardware = (o: { formId: string }, x?: ObjectWithCompileContext) => mx( this.ERROR_InvalidHardware, x, `layers has invalid value formId=${def(o.formId)}`, ); static ERROR_InvalidModifier = SevError | 0x0014; - static Error_InvalidModifier = (o:{modifiers: string}, x?: ObjectWithMetadata) => mx( + static Error_InvalidModifier = (o:{modifiers: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_InvalidModifier, x, `layer has invalid modifiers='${def(o.modifiers)}'`, ); static ERROR_MissingFlicks = SevError | 0x0015; - static Error_MissingFlicks = (o: {id: string, flickId: string}, x?: ObjectWithMetadata) => mx( + static Error_MissingFlicks = (o: {id: string, flickId: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_MissingFlicks, x, `key id=${def(o.id)} refers to missing flickId=${def(o.flickId)}`, ); static ERROR_DuplicateVariable = SevError | 0x0016; - static Error_DuplicateVariable = (o:{id: string}, x?: ObjectWithMetadata) => mx( + static Error_DuplicateVariable = (o:{id: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_DuplicateVariable, x, `duplicate variable: id=${def(o.id)}`, ); // Not hit due to XML parsing static ERROR_InvalidTransformsType = SevError | 0x0018; - static Error_InvalidTransformsType = (o:{type: string}, x?: ObjectWithMetadata) => mx( + static Error_InvalidTransformsType = (o:{type: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_InvalidTransformsType, x, `Invalid transforms type: '${def(o.type)}'`, ); static ERROR_DuplicateTransformsType = SevError | 0x0019; - static Error_DuplicateTransformsType = (o:{type: string}, x?: ObjectWithMetadata) => mx( + static Error_DuplicateTransformsType = (o:{type: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_DuplicateTransformsType, x, `Duplicate transforms type: '${def(o.type)}'`, ); static ERROR_MixedTransformGroup = SevError | 0x001A; - static Error_MixedTransformGroup = (x?: ObjectWithMetadata) => mx( + static Error_MixedTransformGroup = (x?: ObjectWithCompileContext) => mx( this.ERROR_MixedTransformGroup, x, `transformGroup cannot contain both reorder and transform elements`, ); static ERROR_EmptyTransformGroup = SevError | 0x001B; - static Error_EmptyTransformGroup = (x?: ObjectWithMetadata) => mx( + static Error_EmptyTransformGroup = (x?: ObjectWithCompileContext) => mx( this.ERROR_EmptyTransformGroup, x, `transformGroup must have either reorder or transform elements`, ); static ERROR_MissingStringVariable = SevError | 0x001C; - static Error_MissingStringVariable = (o:{id: string}, x?: ObjectWithMetadata) => mx( + static Error_MissingStringVariable = (o:{id: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_MissingStringVariable, x, `Reference to undefined string variable: \${${def(o.id)}}`, ); static ERROR_MissingSetVariable = SevError | 0x001D; - static Error_MissingSetVariable = (o:{id: string}, x?: ObjectWithMetadata) => mx( + static Error_MissingSetVariable = (o:{id: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_MissingSetVariable, x, `Reference to undefined set variable: \$[${def(o.id)}]`, ); static ERROR_MissingUnicodeSetVariable = SevError | 0x001E; - static Error_MissingUnicodeSetVariable = (o:{id: string}, x?: ObjectWithMetadata) => mx( + static Error_MissingUnicodeSetVariable = (o:{id: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_MissingUnicodeSetVariable, x, `Reference to undefined UnicodeSet variable: \$[${def(o.id)}]`, ); static ERROR_NeedSpacesBetweenSetVariables = SevError | 0x001F; - static Error_NeedSpacesBetweenSetVariables = (o:{item: string}, x?: ObjectWithMetadata) => mx( + static Error_NeedSpacesBetweenSetVariables = (o:{item: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_NeedSpacesBetweenSetVariables, x, `Need spaces between set variables: ${def(o.item)}`, ); static ERROR_CantReferenceSetFromUnicodeSet = SevError | 0x0020; - static Error_CantReferenceSetFromUnicodeSet = (o:{id: string}, x?: ObjectWithMetadata) => mx( + static Error_CantReferenceSetFromUnicodeSet = (o:{id: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_CantReferenceSetFromUnicodeSet, x, `Illegal use of set variable from within UnicodeSet: \$[${def(o.id)}]` ); static ERROR_MissingMarkers = SevError | 0x0021; - static Error_MissingMarkers = (o: { ids: string }, x?: ObjectWithMetadata) => mx( + static Error_MissingMarkers = (o: { ids: string }, x?: ObjectWithCompileContext) => mx( this.ERROR_MissingMarkers, x, `Markers used for matching but not defined: ${def(o.ids)}` ); static ERROR_DisplayNeedsToOrId = SevError | 0x0022; - static Error_DisplayNeedsToOrId = (o:{display?: string}, x?: ObjectWithMetadata) => mx( + static Error_DisplayNeedsToOrId = (o:{display?: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_DisplayNeedsToOrId, x, `display display='${def(o.display)}' needs output= or keyId=, but not both`, ); static HINT_PUACharacters = SevHint | 0x0023; - static Hint_PUACharacters = (o: { count: number, lowestCh: string }, x?: ObjectWithMetadata) => mx( + static Hint_PUACharacters = (o: { count: number, lowestCh: string }, x?: ObjectWithCompileContext) => mx( this.HINT_PUACharacters, x, `File contains ${def(o.count)} PUA character(s), including ${def(o.lowestCh)}`, ); static WARN_UnassignedCharacters = SevWarn | 0x0024; - static Warn_UnassignedCharacters = (o: { count: number, lowestCh: string }, x?: ObjectWithMetadata) => mx( + static Warn_UnassignedCharacters = (o: { count: number, lowestCh: string }, x?: ObjectWithCompileContext) => mx( this.WARN_UnassignedCharacters, x, `File contains ${def(o.count)} unassigned character(s), including ${def(o.lowestCh)}`, ); static ERROR_IllegalCharacters = SevError | 0x0025; - static Error_IllegalCharacters = (o: { count: number, lowestCh: string }, x?: ObjectWithMetadata) => mx( + static Error_IllegalCharacters = (o: { count: number, lowestCh: string }, x?: ObjectWithCompileContext) => mx( this.ERROR_IllegalCharacters, x, `File contains ${def(o.count)} illegal character(s), including ${def(o.lowestCh) }`, ); static HINT_CharClassImplicitDenorm = SevHint | 0x0026; - static Hint_CharClassImplicitDenorm = (o: { lowestCh: number }, x?: ObjectWithMetadata) => mx( + static Hint_CharClassImplicitDenorm = (o: { lowestCh: number }, x?: ObjectWithCompileContext) => mx( this.HINT_CharClassImplicitDenorm, x, `File has character classes which span non-NFD character(s), including ${def(o.lowestCh)}. These will not match any text.`, ); static WARN_CharClassExplicitDenorm = SevWarn | 0x0027; - static Warn_CharClassExplicitDenorm = (o: { lowestCh: number }, x?: ObjectWithMetadata) => mx( + static Warn_CharClassExplicitDenorm = (o: { lowestCh: number }, x?: ObjectWithCompileContext) => mx( this.WARN_CharClassExplicitDenorm, x, `File has character classes which include non-NFD characters(s), including ${def(o.lowestCh)}. These will not match any text.`, ); @@ -251,32 +251,32 @@ export class LdmlCompilerMessages { // Available: 0x0028 static ERROR_InvalidVariableIdentifier = SevError | 0x0029; - static Error_InvalidVariableIdentifier = (o: { id: string }, x?: ObjectWithMetadata) => mx( + static Error_InvalidVariableIdentifier = (o: { id: string }, x?: ObjectWithCompileContext) => mx( this.ERROR_InvalidVariableIdentifier, x, `Invalid variable identifier "${def(o.id)}". Identifiers must be between 1 and 32 characters, and can use A-Z, a-z, 0-9, and _.`, ); static ERROR_InvalidMarkerIdentifier = SevError | 0x002A; - static Error_InvalidMarkerIdentifier = (o: { id: string }, x?: ObjectWithMetadata) => mx( + static Error_InvalidMarkerIdentifier = (o: { id: string }, x?: ObjectWithCompileContext) => mx( this.ERROR_InvalidMarkerIdentifier, x, `Invalid marker identifier "\m{${def(o.id)}}". Identifiers must be between 1 and 32 characters, and can use A-Z, a-z, 0-9, and _.`, ); static WARN_StringDenorm = SevWarn | 0x002B; - static Warn_StringDenorm = (o: { s: string }, x?: ObjectWithMetadata) => mx( + static Warn_StringDenorm = (o: { s: string }, x?: ObjectWithCompileContext) => mx( this.WARN_StringDenorm, x, `File contains string "${def(o.s)}" that is neither NFC nor NFD.`, ); static ERROR_DuplicateLayerWidth = SevError | 0x002C; - static Error_DuplicateLayerWidth = (o: { minDeviceWidth: number }, x?: ObjectWithMetadata) => mx( + static Error_DuplicateLayerWidth = (o: { minDeviceWidth: number }, x?: ObjectWithCompileContext) => mx( this.ERROR_DuplicateLayerWidth, x, `Two or more layers have minDeviceWidth=${def(o.minDeviceWidth)}`, `Touch layers must have distinct widths.` ); static ERROR_InvalidLayerWidth = SevError | 0x002D; - static Error_InvalidLayerWidth = (o: { minDeviceWidth: number }, x?: ObjectWithMetadata) => mx( + static Error_InvalidLayerWidth = (o: { minDeviceWidth: number }, x?: ObjectWithCompileContext) => mx( this.ERROR_InvalidLayerWidth, x, `Invalid Layers minDeviceWidth=${def(o.minDeviceWidth)}`, `Width must be between 1-999 (millimeters), inclusive.` // sync with layr_max_minDeviceWidth / layr_max_maxDeviceWidth (from spec) @@ -285,7 +285,7 @@ export class LdmlCompilerMessages { // Available: 0x02E-0x2F static ERROR_InvalidQuadEscape = SevError | 0x0030; - static Error_InvalidQuadEscape = (o: { cp: string, recommended: string }, x?: ObjectWithMetadata) => mx( + static Error_InvalidQuadEscape = (o: { cp: string, recommended: string }, x?: ObjectWithCompileContext) => mx( this.ERROR_InvalidQuadEscape, x, `Invalid escape "${def(o.cp)}"`, `**Hint**: Use "${def(o.recommended)}"`, @@ -296,7 +296,7 @@ export class LdmlCompilerMessages { // This is a bit of a catch-all and represents messages bubbling up from the underlying regex engine static ERROR_UnparseableTransformFrom = SevErrorTransform | 0x00; - static Error_UnparseableTransformFrom = (o: { from: string, message: string }, x?: ObjectWithMetadata) => mx( + static Error_UnparseableTransformFrom = (o: { from: string, message: string }, x?: ObjectWithCompileContext) => mx( this.ERROR_UnparseableTransformFrom, x, `Invalid transform from="${def(o.from)}": "${def(o.message)}"`, ); @@ -306,7 +306,7 @@ export class LdmlCompilerMessages { //------------------------------------------------------------------------------| static ERROR_IllegalTransformDollarsign = SevErrorTransform | 0x01; - static Error_IllegalTransformDollarsign = (o: { from: string }, x?: ObjectWithMetadata) => mx( + static Error_IllegalTransformDollarsign = (o: { from: string }, x?: ObjectWithCompileContext) => mx( this.ERROR_IllegalTransformDollarsign, x, `Invalid transform from="${def(o.from)}": Unescaped dollar-sign ($) is not valid transform syntax.`, ` **Hint**: Use \`\\$\` to match a literal dollar-sign. If this precedes a @@ -315,27 +315,27 @@ export class LdmlCompilerMessages { `); static ERROR_TransformFromMatchesNothing = SevErrorTransform | 0x02; - static Error_TransformFromMatchesNothing = (o: { from: string }, x?: ObjectWithMetadata) => mx( + static Error_TransformFromMatchesNothing = (o: { from: string }, x?: ObjectWithCompileContext) => mx( this.ERROR_TransformFromMatchesNothing, x, `Invalid transfom from="${def(o.from)}": Matches an empty string.` ); static ERROR_IllegalTransformPlus = SevErrorTransform | 0x03; - static Error_IllegalTransformPlus = (o: { from: string }, x?: ObjectWithMetadata) => mx( + static Error_IllegalTransformPlus = (o: { from: string }, x?: ObjectWithCompileContext) => mx( this.ERROR_IllegalTransformPlus, x, `Invalid transform from="${def(o.from)}": Unescaped plus (+) is not valid transform syntax.`, ` **Hint**: Use \`\\+\` to match a literal plus. `); static ERROR_IllegalTransformAsterisk = SevErrorTransform | 0x04; - static Error_IllegalTransformAsterisk = (o: { from: string }, x?: ObjectWithMetadata) =>mx( + static Error_IllegalTransformAsterisk = (o: { from: string }, x?: ObjectWithCompileContext) =>mx( this.ERROR_IllegalTransformAsterisk, x, `Invalid transform from="${def(o.from)}": Unescaped asterisk (*) is not valid transform syntax.`, ` **Hint**: Use \`\\*\` to match a literal asterisk. `); static ERROR_IllegalTransformToUset = SevErrorTransform | 0x05; - static Error_IllegalTransformToUset = (o: { to: string }, x?: ObjectWithMetadata) => mx( + static Error_IllegalTransformToUset = (o: { to: string }, x?: ObjectWithCompileContext) => mx( this.ERROR_IllegalTransformToUset, x, `Invalid transform to="${def(o.to)}": Set variable (\\$[…]) cannot be used in 'to=' unless part of a map.`, ` **Hint**: If a map was meant, must use the form @@ -343,7 +343,7 @@ export class LdmlCompilerMessages { `); static ERROR_UnparseableTransformTo = SevErrorTransform | 0x06; - static Error_UnparseableTransformTo = (o: {to: string, message: string}, x?: ObjectWithMetadata) => mx( + static Error_UnparseableTransformTo = (o: {to: string, message: string}, x?: ObjectWithCompileContext) => mx( this.ERROR_UnparseableTransformTo, x, `Invalid transform to="${def(o.to)}": "${def(o.message)}"`, ); diff --git a/developer/src/kmc-ldml/src/compiler/loca.ts b/developer/src/kmc-ldml/src/compiler/loca.ts index 3cc222e613..19c2522a4a 100644 --- a/developer/src/kmc-ldml/src/compiler/loca.ts +++ b/developer/src/kmc-ldml/src/compiler/loca.ts @@ -1,6 +1,6 @@ import { constants } from "@keymanapp/ldml-keyboard-constants"; import { KMXPlus } from '@keymanapp/common-types'; -import { LDMLKeyboard, ObjectWithMetadata } from '@keymanapp/developer-utils'; +import { LDMLKeyboard, ObjectWithCompileContext } from '@keymanapp/developer-utils'; import { SectionCompiler } from "./section-compiler.js"; import { LdmlCompilerMessages } from "./ldml-compiler-messages.js"; @@ -28,7 +28,7 @@ export class LocaCompiler extends SectionCompiler { * @param loc locale tag * @returns context metadata or null */ - private contextForLocale(loc: string) : ObjectWithMetadata { + private contextForLocale(loc: string) : ObjectWithCompileContext { if (loc == this.keyboard3.locale) return this.keyboard3; return this.keyboard3?.locales; } diff --git a/developer/src/kmc-ldml/src/compiler/substitution-tracker.ts b/developer/src/kmc-ldml/src/compiler/substitution-tracker.ts index 61daefa0cf..ef78b55685 100644 --- a/developer/src/kmc-ldml/src/compiler/substitution-tracker.ts +++ b/developer/src/kmc-ldml/src/compiler/substitution-tracker.ts @@ -1,5 +1,5 @@ import { LdmlKeyboardTypes } from "@keymanapp/common-types"; -import { ObjectWithMetadata } from "@keymanapp/developer-utils"; +import { ObjectWithCompileContext } from "@keymanapp/developer-utils"; /** * Verb for SubstitutionTracker.add() @@ -15,7 +15,7 @@ export enum SubstitutionUse { variable, } -type SubstitutionSet = Map; +type SubstitutionSet = Map; /** Tracks usage of markers */ export class SubstitutionTracker { @@ -29,10 +29,10 @@ export class SubstitutionTracker { all: SubstitutionSet; constructor() { - this.emitted = new Map(); - this.consumed = new Map(); - this.matched = new Map(); - this.all = new Map(); + this.emitted = new Map(); + this.consumed = new Map(); + this.matched = new Map(); + this.all = new Map(); } /** @@ -40,7 +40,7 @@ export class SubstitutionTracker { * @param verb what kind of use we are adding * @param markers list of substitutions to add */ - add(verb: SubstitutionUse, markers: string[], x?: ObjectWithMetadata) { + add(verb: SubstitutionUse, markers: string[], x?: ObjectWithCompileContext) { if (!markers.length) { return; // skip if empty } @@ -77,20 +77,20 @@ export class SubstitutionTracker { /** rollup of several substitution types */ export class Substitutions { - addSetAndStringSubtitution(verb: SubstitutionUse, str?: string, x?: ObjectWithMetadata) { + addSetAndStringSubtitution(verb: SubstitutionUse, str?: string, x?: ObjectWithCompileContext) { this.set.add(verb, LdmlKeyboardTypes.VariableParser.allSetReferences(str), x); this.addStringAndMarkerSubstitution(verb, str, x); } /** add a string that can have string var substitutions or markers */ - addStringAndMarkerSubstitution(verb: SubstitutionUse, str?: string, x?: ObjectWithMetadata) { + addStringAndMarkerSubstitution(verb: SubstitutionUse, str?: string, x?: ObjectWithCompileContext) { this.addMarkers(verb, str, x); this.addStringSubstitution(verb, str, x); } - addStringSubstitution(verb: SubstitutionUse, str?: string, x?: ObjectWithMetadata) { + addStringSubstitution(verb: SubstitutionUse, str?: string, x?: ObjectWithCompileContext) { this.string.add(verb, LdmlKeyboardTypes.VariableParser.allStringReferences(str), x); } /** add a string that's just markers */ - addMarkers(verb: SubstitutionUse, str?: string, x?: ObjectWithMetadata) { + addMarkers(verb: SubstitutionUse, str?: string, x?: ObjectWithCompileContext) { this.markers.add(verb, LdmlKeyboardTypes.MarkerParser.allReferences(str), x); LdmlKeyboardTypes.MarkerParser.allBrokenReferences(str).forEach(m => this.badMarkers.set(m, x)); } @@ -110,6 +110,6 @@ export class Substitutions { this.set = new SubstitutionTracker(); this.string = new SubstitutionTracker(); this.uset = new SubstitutionTracker(); - this.badMarkers = new Map(); + this.badMarkers = new Map(); } } diff --git a/developer/src/kmc-ldml/src/compiler/tran.ts b/developer/src/kmc-ldml/src/compiler/tran.ts index 1cc9bd0289..745eeb6d0f 100644 --- a/developer/src/kmc-ldml/src/compiler/tran.ts +++ b/developer/src/kmc-ldml/src/compiler/tran.ts @@ -1,6 +1,6 @@ import { constants, SectionIdent } from "@keymanapp/ldml-keyboard-constants"; import { KMXPlus, LdmlKeyboardTypes, util } from '@keymanapp/common-types'; -import { CompilerCallbacks, LDMLKeyboard, ObjectWithMetadata } from "@keymanapp/developer-utils"; +import { CompilerCallbacks, LDMLKeyboard, ObjectWithCompileContext } from "@keymanapp/developer-utils"; import { SectionCompiler } from "./section-compiler.js"; import Bksp = KMXPlus.Bksp; @@ -237,7 +237,7 @@ export abstract class TransformCompiler DTD this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidVariableIdentifier({id}, x)); return false; From 0a35181bed3439ccd50203343c9512748f1d0a37 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Sat, 21 Jun 2025 14:36:26 -0500 Subject: [PATCH 066/262] chore(developer): rename x: to compileContext: Fixes: #14117 --- .../types/src/kmx/kmx-plus/element-string.ts | 4 +- common/web/types/src/kmx/kmx-plus/kmx-plus.ts | 34 +-- .../ldml-keyboard/unicodeset-parser-api.ts | 4 +- common/web/types/src/main.ts | 2 + common/web/types/src/util/types.ts | 6 + .../docs/internal/developer-messaging.md | 4 +- .../web/utils/src/compiler-interfaces.ts | 12 +- .../web/utils/src/developer-utils-messages.ts | 6 +- developer/src/common/web/utils/src/index.ts | 2 +- .../src/common/web/utils/src/symbol-utils.ts | 8 +- .../src/common/web/utils/src/xml-utils.ts | 4 +- .../src/kmc-kmn/src/compiler/compiler.ts | 22 +- .../src/compiler/kmn-compiler-messages.ts | 20 +- developer/src/kmc-ldml/src/compiler/disp.ts | 8 +- .../kmc-ldml/src/compiler/empty-compiler.ts | 3 +- developer/src/kmc-ldml/src/compiler/keys.ts | 18 +- developer/src/kmc-ldml/src/compiler/layr.ts | 6 +- .../src/compiler/ldml-compiler-messages.ts | 203 +++++++++--------- developer/src/kmc-ldml/src/compiler/loca.ts | 5 +- developer/src/kmc-ldml/src/compiler/meta.ts | 12 +- .../src/compiler/substitution-tracker.ts | 48 ++--- developer/src/kmc-ldml/src/compiler/tran.ts | 29 +-- developer/src/kmc-ldml/src/compiler/vars.ts | 13 +- .../src/kmc-ldml/test/helpers/compareXml.ts | 5 +- 24 files changed, 243 insertions(+), 235 deletions(-) create mode 100644 common/web/types/src/util/types.ts diff --git a/common/web/types/src/kmx/kmx-plus/element-string.ts b/common/web/types/src/kmx/kmx-plus/element-string.ts index e65d996f34..af17145427 100644 --- a/common/web/types/src/kmx/kmx-plus/element-string.ts +++ b/common/web/types/src/kmx/kmx-plus/element-string.ts @@ -81,11 +81,11 @@ export class ElementString extends Array { // error. So we can just exit here. return null; // UnicodeSet error } - const uset = sections.usetparser.parseUnicodeSet(item.segment, needRanges, options?.x); + const uset = sections.usetparser.parseUnicodeSet(item.segment, needRanges, options?.compileContext); if (!uset) { return null; // UnicodeSet error already added to callback } - elem.uset = sections.uset.allocUset(uset, sections, options?.x); + elem.uset = sections.uset.allocUset(uset, sections, options?.compileContext); elem.value = sections.strs.allocString('', {...options, singleOk: true}); // no string } else if (item.type === ElementType.codepoint || item.type === ElementType.escaped || item.type === ElementType.string) { // some kind of a string diff --git a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts index 4ccf80ed17..4fd5dcd632 100644 --- a/common/web/types/src/kmx/kmx-plus/kmx-plus.ts +++ b/common/web/types/src/kmx/kmx-plus/kmx-plus.ts @@ -141,7 +141,7 @@ export class StrsItem { /** add any context from the options to this strsitem */ setContext(opts?: StrsOptions) { // At present, there's only a single piece of context available - this._context = this._context || opts?.x; + this._context = this._context || opts?.compileContext; } get context() : any { @@ -174,7 +174,7 @@ export interface StrsOptions { /** string can be stored as a single CharStrsItem, not in strs table. */ singleOk?: boolean; /** optional context */ - x?: any; + compileContext?: any; }; export class Strs extends Section { @@ -374,13 +374,13 @@ export class Vars extends Section { export class VarsItem extends Section { id: StrsItem; value: StrsItem; - x?: any; + compileContext?: any; - constructor(id: string, value: string, sections: DependencySections, x?: any) { + constructor(id: string, value: string, sections: DependencySections, compileContext?: any) { super(); this.id = sections.strs.allocString(id); - this.value = sections.strs.allocString(value, {unescape: true}); - this.x = x; + this.value = sections.strs.allocString(value, { unescape: true }); + this.compileContext = compileContext; } valid() : boolean { @@ -389,11 +389,11 @@ export class VarsItem extends Section { }; export class UnicodeSetItem extends VarsItem { - constructor(id: string, value: string, sections: DependencySections, usetparser: UnicodeSetParser, x?: any) { - super(id, value, sections, x); + constructor(id: string, value: string, sections: DependencySections, usetparser: UnicodeSetParser, compileContext?: any) { + super(id, value, sections, compileContext); const needRanges = sections.usetparser.sizeUnicodeSet(value); if (needRanges >= 0) { - this.unicodeSet = sections.usetparser.parseUnicodeSet(value, needRanges, x); + this.unicodeSet = sections.usetparser.parseUnicodeSet(value, needRanges, compileContext); } // otherwise: error (was recorded via callback) } unicodeSet?: UnicodeSet; @@ -403,9 +403,9 @@ export class UnicodeSetItem extends VarsItem { }; export class SetVarItem extends VarsItem { - constructor(id: string, value: string[], sections: DependencySections, x?: any) { - super(id, value.join(' '), sections, x); - this.items = sections.elem.allocElementString(sections, {x}, value); + constructor(id: string, value: string[], sections: DependencySections, compileContext?: any) { + super(id, value.join(' '), sections, compileContext); + this.items = sections.elem.allocElementString(sections, { compileContext }, value); } items: ElementString; // element string array valid() : boolean { @@ -414,8 +414,8 @@ export class SetVarItem extends VarsItem { }; export class StringVarItem extends VarsItem { - constructor(id: string, value: string, sections: DependencySections, x?: any) { - super(id, value, sections, x); + constructor(id: string, value: string, sections: DependencySections, compileContext?: any) { + super(id, value, sections, compileContext); } // no added fields }; @@ -453,7 +453,7 @@ export class Tran extends Section { }; export class UsetItem { - constructor(public uset: UnicodeSet, public str: StrsItem, public x?: any) { + constructor(public uset: UnicodeSet, public str: StrsItem, public compileContext?: any) { } compareTo(other: UsetItem) : number { return this.str.compareTo(other.str); @@ -462,11 +462,11 @@ export class UsetItem { export class Uset extends Section { usets: UsetItem[] = []; - allocUset(set: UnicodeSet, sections: DependencySections, x?: any) : UsetItem { + allocUset(set: UnicodeSet, sections: DependencySections, compileContext?: any) : UsetItem { // match the same pattern let result = this.usets.find(s => set.pattern == s.uset.pattern); if (result === undefined) { - result = new UsetItem(set, sections.strs.allocString(set.pattern), x); + result = new UsetItem(set, sections.strs.allocString(set.pattern), compileContext); this.usets.push(result); } return result; diff --git a/common/web/types/src/ldml-keyboard/unicodeset-parser-api.ts b/common/web/types/src/ldml-keyboard/unicodeset-parser-api.ts index 797a5415b9..6ad16cd4ee 100644 --- a/common/web/types/src/ldml-keyboard/unicodeset-parser-api.ts +++ b/common/web/types/src/ldml-keyboard/unicodeset-parser-api.ts @@ -8,13 +8,13 @@ export interface UnicodeSetParser { * @param pattern string to parse such as `[a-z]` * @param rangeCount number of ranges to allow for */ - parseUnicodeSet(pattern: string, rangeCount: number, x?: any) : UnicodeSet | null; + parseUnicodeSet(pattern: string, rangeCount: number, compileContext?: any) : UnicodeSet | null; /** * Calculate the number of ranges in a UnicodeSet * @param pattern string to parse such as `[a-z]` * @returns number of ranges, or -1 (with callback-reported err) on err */ - sizeUnicodeSet(pattern: string, x?: any) : number; + sizeUnicodeSet(pattern: string, compileContext?: any) : number; } /** diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index 071c931e9e..779af42ba5 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -19,6 +19,8 @@ export * as KmpJsonFile from './package/kmp-json-file.js'; export { Uni_IsSurrogate1, Uni_IsSurrogate2 } from './util/util.js'; export * as util from './util/util.js'; +export { ObjectWithCompileContext } from './util/types.js'; + export * as KeymanFileTypes from './util/file-types.js'; export * as Schemas from './schemas.js'; diff --git a/common/web/types/src/util/types.ts b/common/web/types/src/util/types.ts new file mode 100644 index 0000000000..3ed1dda4f9 --- /dev/null +++ b/common/web/types/src/util/types.ts @@ -0,0 +1,6 @@ +/** + * Any object with metadata, for example for line number errs. + * Defined as 'any' here to reduce noise on the client side. + * @see {@link KeymanXMLReader.getMetaData()} + */ +export type ObjectWithCompileContext = any; diff --git a/developer/docs/internal/developer-messaging.md b/developer/docs/internal/developer-messaging.md index ab6c6afd4a..453230a733 100644 --- a/developer/docs/internal/developer-messaging.md +++ b/developer/docs/internal/developer-messaging.md @@ -102,7 +102,7 @@ Example: keys.ts \- compiler \- call site for error ```js static ERROR_InvalidScanCode = SevError | 0x0009; -static Error_InvalidScanCode = (o:{id: string, invalidCodeList: string}, x: ObjectWithCompileContext) => mx( +static Error_InvalidScanCode = (o:{id: string, invalidCodeList: string}, compileContext:ObjectWithCompileContext) => mx( this.ERROR_InvalidScanCode, context, Form '${def(o.id)}' has invalid/unknown scancodes '${def(o.codes)}', `…additional markdown detail…` @@ -126,7 +126,7 @@ The `mx()` function does the following (note that `m()` is called first). ```js let evt = m(code, message, detail); // raw message -evt = LdmlCompilerMessages.offset(evt, x); // with offset +evt = LdmlCompilerMessages.offset(evt, compileContext); // with offset ``` Note: We could have: `ml(e:CompilerEvent, lineNumber?: number)` so that compilers with a line number could pass that in directly. diff --git a/developer/src/common/web/utils/src/compiler-interfaces.ts b/developer/src/common/web/utils/src/compiler-interfaces.ts index f24250fb54..74231aaf2b 100644 --- a/developer/src/common/web/utils/src/compiler-interfaces.ts +++ b/developer/src/common/web/utils/src/compiler-interfaces.ts @@ -1,5 +1,5 @@ import { CompilerCallbacks } from "./compiler-callbacks.js"; -import { ObjectWithCompileContext } from "./symbol-utils.js"; +import { ObjectWithCompileContext } from '@keymanapp/common-types'; import { KeymanXMLReader, XML_FILENAME_SYMBOL } from "./xml-utils.js"; /** @@ -215,9 +215,9 @@ export class CompilerError { * @param x any object parsed from XML or with the XML_META_DATA_SYMBOL symbol copied over * @returns modified event object */ - public static setFromMetadata(event: CompilerEvent, x?: ObjectWithCompileContext): CompilerEvent { - if(x) { - const metadata = KeymanXMLReader.getMetaData(x) || {}; + public static setFromMetadata(event: CompilerEvent, compileContext?: ObjectWithCompileContext): CompilerEvent { + if (compileContext) { + const metadata = KeymanXMLReader.getMetaData(compileContext) || {}; const offset = metadata?.startIndex; if (offset) { event.offset = offset; @@ -458,8 +458,8 @@ export function dedentCompilerMessageDetail(event: CompilerEvent) { * // it contains redundant info. * // 2. No code execution within the arrow function other than the 'mx' call, string interpolation, * // with `${def(o.property)}` as the max complexity of interpolation. - * static Error_InvalidScanCode = (o:{id: string, invalidCodeList: string}, x: ObjectWithCompileContext) => mx( - * this.ERROR_InvalidScanCode, x, + * static Error_InvalidScanCode = (o:{id: string, invalidCodeList: string}, compileContext: ObjectWithCompileContext) => mx( + * this.ERROR_InvalidScanCode, compileContext, * `Form '${def(o.id)}' has invalid/unknown scancodes '${def(o.codes)}'`, * // Note: If detail is omitted, leave the trailing comma on the prior line to leave room for it * `…additional markdown detail…` diff --git a/developer/src/common/web/utils/src/developer-utils-messages.ts b/developer/src/common/web/utils/src/developer-utils-messages.ts index bad77b8bb5..917af99e70 100644 --- a/developer/src/common/web/utils/src/developer-utils-messages.ts +++ b/developer/src/common/web/utils/src/developer-utils-messages.ts @@ -3,7 +3,7 @@ */ import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageDef as def, CompilerMessageSpec as m, CompilerMessageObjectSpec as mx } from './compiler-interfaces.js'; import { constants } from '@keymanapp/ldml-keyboard-constants'; -import { ObjectWithCompileContext } from './symbol-utils.js'; +import { ObjectWithCompileContext } from '@keymanapp/common-types'; const DeveloperUtilsErrMask = CompilerErrorNamespace.DeveloperUtils; // const SevInfo = CompilerErrorSeverity.Info | DeveloperUtilsErrMask; @@ -15,8 +15,8 @@ const SevError = CompilerErrorSeverity.Error | DeveloperUtilsErrMask; export class DeveloperUtilsMessages { // structured Ajv validation error static ERROR_SchemaValidationError = SevError | 0x0001; - static Error_SchemaValidationError = (o:{instancePath:string, keyword:string, message: string, params: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_SchemaValidationError, x, + static Error_SchemaValidationError = (o:{instancePath:string, keyword:string, message: string, params: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_SchemaValidationError, compileContext, `Error validating LDML XML file: ${def(o.instancePath)}: ${def(o.keyword)}: ${def(o.message)} ${def(o.params)}`, ); diff --git a/developer/src/common/web/utils/src/index.ts b/developer/src/common/web/utils/src/index.ts index 5da53ef028..a414139584 100644 --- a/developer/src/common/web/utils/src/index.ts +++ b/developer/src/common/web/utils/src/index.ts @@ -68,7 +68,7 @@ export { UrlSubpathCompilerCallback } from './utils/UrlSubpathCompilerCallback.j export { DeveloperUtilsMessages } from './developer-utils-messages.js'; export * as SourceFilenamePatterns from './source-filename-patterns.js'; export { KeymanXMLType, KeymanXMLWriter, KeymanXMLReader, KeymanXMLMetadata, XML_FILENAME_SYMBOL, withOffset } from './xml-utils.js'; -export { SymbolUtils, ObjectWithCompileContext as ObjectWithCompileContext } from './symbol-utils.js'; +export { SymbolUtils } from './symbol-utils.js'; export * as LineUtils from './line-utils.js'; export * as GitHubUrls from './github-urls.js'; export * as CloudUrls from './cloud-urls.js'; diff --git a/developer/src/common/web/utils/src/symbol-utils.ts b/developer/src/common/web/utils/src/symbol-utils.ts index f050385c22..a340344c9c 100644 --- a/developer/src/common/web/utils/src/symbol-utils.ts +++ b/developer/src/common/web/utils/src/symbol-utils.ts @@ -6,13 +6,7 @@ * Utilities for manipulating Symbol properties */ - -/** - * Any object with metadata, for example for line number errs. - * Defined as 'any' here to reduce noise on the client side. - * @see {@link KeymanXMLReader.getMetaData()} - */ -export type ObjectWithCompileContext = any; +import { ObjectWithCompileContext } from '@keymanapp/common-types'; export class SymbolUtils { /** diff --git a/developer/src/common/web/utils/src/xml-utils.ts b/developer/src/common/web/utils/src/xml-utils.ts index 721e503f9e..9da6c00071 100644 --- a/developer/src/common/web/utils/src/xml-utils.ts +++ b/developer/src/common/web/utils/src/xml-utils.ts @@ -389,9 +389,9 @@ export function findInstanceObject(source: any, path: string[]) : any { * @param c number for the offset setting * @param x if set, this object will be used as the base object instead of {} */ -export function withOffset(c: number, x?: any) : KeymanXMLMetadata { +export function withOffset(c: number, compileContext?: any) : KeymanXMLMetadata { // set metadata on an empty object - const o = Object.assign({}, x); + const o = Object.assign({}, compileContext); KeymanXMLReader.setMetaData(o, { startIndex: c }); diff --git a/developer/src/kmc-kmn/src/compiler/compiler.ts b/developer/src/kmc-kmn/src/compiler/compiler.ts index 21985ad09a..403f721f90 100644 --- a/developer/src/kmc-kmn/src/compiler/compiler.ts +++ b/developer/src/kmc-kmn/src/compiler/compiler.ts @@ -6,7 +6,7 @@ TODO: implement additional interfaces: */ // TODO: rename wasm-host? -import { VisualKeyboard, KvkFileReader, LdmlKeyboardTypes, KeymanFileTypes, KvkFileWriter } from '@keymanapp/common-types'; +import { VisualKeyboard, KvkFileReader, LdmlKeyboardTypes, KeymanFileTypes, KvkFileWriter, ObjectWithCompileContext } from '@keymanapp/common-types'; import { CompilerCallbacks, CompilerEvent, CompilerOptions, KeymanCompiler, KeymanCompilerArtifacts, KeymanCompilerArtifactOptional, KeymanCompilerResult, KeymanCompilerArtifact, KvksFileReader, @@ -530,7 +530,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet * @param rangeCount - number of ranges to allocate * @returns UnicodeSet accessor object, or null on failure */ - public parseUnicodeSet(pattern: string, rangeCount: number, x?: any) : LdmlKeyboardTypes.UnicodeSet | null { + public parseUnicodeSet(pattern: string, rangeCount: number, compileContext?: any) : LdmlKeyboardTypes.UnicodeSet | null { if(!this.verifyInitialized()) { /* c8 ignore next 2 */ // verifyInitialized will set a callback if needed @@ -563,7 +563,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet // rc is negative: it's an error code. this.wasmExports.free(buf); // translate error code into callback - this.callbacks.reportMessage(getUnicodeSetError(rc, x)); + this.callbacks.reportMessage(getUnicodeSetError(rc, compileContext)); return null; } } @@ -571,7 +571,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet /** * @internal */ - public sizeUnicodeSet(pattern: string, x?: any) : number { + public sizeUnicodeSet(pattern: string, compileContext?: any) : number { if(!this.verifyInitialized()) { /* c8 ignore next 2 */ return null; @@ -583,7 +583,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet if (rc >= 0) { return rc; } else { - this.callbacks.reportMessage(getUnicodeSetError(rc, x)); + this.callbacks.reportMessage(getUnicodeSetError(rc, compileContext)); return -1; } } @@ -594,7 +594,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet * @param rc parseUnicodeSet error code * @returns the compiler event */ -function getUnicodeSetError(rc: number, x?: any) : CompilerEvent { +function getUnicodeSetError(rc: number, compileContext?: ObjectWithCompileContext) : CompilerEvent { // from kmcmplib.h const KMCMP_ERROR_SYNTAX_ERR = -1; const KMCMP_ERROR_HAS_STRINGS = -2; @@ -602,16 +602,16 @@ function getUnicodeSetError(rc: number, x?: any) : CompilerEvent { const KMCMP_FATAL_OUT_OF_RANGE = -4; switch(rc) { case KMCMP_ERROR_SYNTAX_ERR: - return KmnCompilerMessages.Error_UnicodeSetSyntaxError(x); + return KmnCompilerMessages.Error_UnicodeSetSyntaxError(compileContext); case KMCMP_ERROR_HAS_STRINGS: - return KmnCompilerMessages.Error_UnicodeSetHasStrings(x); + return KmnCompilerMessages.Error_UnicodeSetHasStrings(compileContext); case KMCMP_ERROR_UNSUPPORTED_PROPERTY: - return KmnCompilerMessages.Error_UnicodeSetHasProperties(x); + return KmnCompilerMessages.Error_UnicodeSetHasProperties(compileContext); case KMCMP_FATAL_OUT_OF_RANGE: - return KmnCompilerMessages.Fatal_UnicodeSetOutOfRange(x); + return KmnCompilerMessages.Fatal_UnicodeSetOutOfRange(compileContext); default: /* c8 ignore next */ - return CompilerError.setFromMetadata(KmnCompilerMessages.Fatal_UnexpectedException({e: `Unexpected UnicodeSet error code ${rc}`}), x); + return CompilerError.setFromMetadata(KmnCompilerMessages.Fatal_UnexpectedException({e: `Unexpected UnicodeSet error code ${rc}`}), compileContext); } } diff --git a/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts b/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts index 7271f16601..46f573f253 100644 --- a/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts +++ b/developer/src/kmc-kmn/src/compiler/kmn-compiler-messages.ts @@ -1,7 +1,7 @@ import { KeyAddress } from "../kmw-compiler/validate-layout-file.js"; import { kmnfile } from "../kmw-compiler/compiler-globals.js"; -import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageSpec as m, CompilerMessageObjectSpec as mx, CompilerMessageDef as def, CompilerMessageSpecWithException, KeymanUrls, ObjectWithCompileContext } from "@keymanapp/developer-utils"; - +import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageSpec as m, CompilerMessageObjectSpec as mx, CompilerMessageDef as def, CompilerMessageSpecWithException, KeymanUrls } from "@keymanapp/developer-utils"; +import { ObjectWithCompileContext } from "@keymanapp/common-types"; const Namespace = CompilerErrorNamespace.KmnCompiler; const SevInfo = CompilerErrorSeverity.Info | Namespace; const SevHint = CompilerErrorSeverity.Hint | Namespace; @@ -107,8 +107,8 @@ export class KmnCompilerMessages { ); static FATAL_UnicodeSetOutOfRange = SevFatal | 0x904; - static Fatal_UnicodeSetOutOfRange = (x?: ObjectWithCompileContext) => mx( - this.FATAL_UnicodeSetOutOfRange, x, + static Fatal_UnicodeSetOutOfRange = (compileContext?: ObjectWithCompileContext) => mx( + this.FATAL_UnicodeSetOutOfRange, compileContext, `UnicodeSet buffer was too small`, `Raised when caller to UnicodeSet functions provides an invalid buffer. If you experience this error, it should be reported to the Keyman team for @@ -118,8 +118,8 @@ export class KmnCompilerMessages { // TODO: rename the following functions to Error_UsetHasStrings etc static ERROR_UnicodeSetHasStrings = SevError | 0x905; - static Error_UnicodeSetHasStrings = (x?: ObjectWithCompileContext) => mx( - this.ERROR_UnicodeSetHasStrings, x, + static Error_UnicodeSetHasStrings = (compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_UnicodeSetHasStrings, compileContext, `uset contains strings, not allowed`, `The provided uset uses multi-character strings, (\`{}\` notation, e.g. \`[żġħ{ie}{għ}]\`. ). Although full UnicodeSets support strings, LDML @@ -130,8 +130,8 @@ export class KmnCompilerMessages { ); static ERROR_UnicodeSetHasProperties = SevError | 0x906; - static Error_UnicodeSetHasProperties = (x?: ObjectWithCompileContext) => mx( - this.ERROR_UnicodeSetHasProperties, x, + static Error_UnicodeSetHasProperties = (compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_UnicodeSetHasProperties, compileContext, `uset contains properties, not allowed`, `The provided uset uses property notation (\`\\p{…}\` or \`[:…:]\`). LDML keyboards do not support Unicode properties in usets, because that would @@ -142,8 +142,8 @@ export class KmnCompilerMessages { ); static ERROR_UnicodeSetSyntaxError = SevError | 0x907; - static Error_UnicodeSetSyntaxError = (x?: ObjectWithCompileContext) => mx( - this.ERROR_UnicodeSetSyntaxError, x, + static Error_UnicodeSetSyntaxError = (compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_UnicodeSetSyntaxError, compileContext, `uset had a Syntax Error while parsing`, `The provided uset has a syntax error and could not be parsed. Verify the format of the uset against the specification. diff --git a/developer/src/kmc-ldml/src/compiler/disp.ts b/developer/src/kmc-ldml/src/compiler/disp.ts index fd15c8ace7..b38ef72531 100644 --- a/developer/src/kmc-ldml/src/compiler/disp.ts +++ b/developer/src/kmc-ldml/src/compiler/disp.ts @@ -62,7 +62,7 @@ export class DispCompiler extends SectionCompiler { const result = new Disp(); // displayOptions - result.baseCharacter = sections.strs.allocString(this.keyboard3.displays?.displayOptions?.baseCharacter, { unescape: true, x: this.keyboard3?.displays?.displayOptions }); + result.baseCharacter = sections.strs.allocString(this.keyboard3.displays?.displayOptions?.baseCharacter, { unescape: true, compileContext:this.keyboard3?.displays?.displayOptions }); // displays result.disps = this.keyboard3.displays?.display.map(display => ({ @@ -70,13 +70,13 @@ export class DispCompiler extends SectionCompiler { stringVariables: true, markers: true, unescape: true, - x: display, + compileContext: display, }, sections), - id: sections.strs.allocString(display.keyId, { x: display }), // not escaped, not substituted + id: sections.strs.allocString(display.keyId, { compileContext: display }), // not escaped, not substituted display: sections.strs.allocString(display.display, { stringVariables: true, unescape: true, - x: display, + compileContext: display, }, sections), })) || []; // TODO-LDML: need coverage for the [] diff --git a/developer/src/kmc-ldml/src/compiler/empty-compiler.ts b/developer/src/kmc-ldml/src/compiler/empty-compiler.ts index 42d09d859d..a88e861f58 100644 --- a/developer/src/kmc-ldml/src/compiler/empty-compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/empty-compiler.ts @@ -1,7 +1,8 @@ import { SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants'; import { SectionCompiler } from "./section-compiler.js"; import { util, KMXPlus, LdmlKeyboardTypes } from "@keymanapp/common-types"; -import { CompilerCallbacks, LDMLKeyboard, ObjectWithCompileContext } from "@keymanapp/developer-utils"; +import { CompilerCallbacks, LDMLKeyboard } from "@keymanapp/developer-utils"; +import { ObjectWithCompileContext } from "@keymanapp/common-types"; import { VarsCompiler } from './vars.js'; import { LdmlCompilerMessages } from './ldml-compiler-messages.js'; diff --git a/developer/src/kmc-ldml/src/compiler/keys.ts b/developer/src/kmc-ldml/src/compiler/keys.ts index 3c890a5067..8656e7fbf2 100644 --- a/developer/src/kmc-ldml/src/compiler/keys.ts +++ b/developer/src/kmc-ldml/src/compiler/keys.ts @@ -324,16 +324,16 @@ export class KeysCompiler extends SectionCompiler { // allocate the in-memory const flicks: KeysFlicks = new KeysFlicks( - sections.strs.allocString(flickId, { x: flick }) + sections.strs.allocString(flickId, { compileContext: flick }) ); // add data from each segment for (const flickSegment of flick.flickSegment) { const { keyId, directions } = flickSegment; - const keyIdStr = sections.strs.allocString(keyId, { x: flickSegment }); + const keyIdStr = sections.strs.allocString(keyId, { compileContext: flickSegment }); const directionsList: ListItem = sections.list.allocListFromSpaces( directions, - { x: flickSegment }, + { compileContext: flickSegment }, sections); flicks.flicks.push({ directions: directionsList, @@ -370,20 +370,20 @@ export class KeysCompiler extends SectionCompiler { if (!!gap) { flags |= constants.keys_key_flags_gap; } - const id = sections.strs.allocString(key.id, { x: key }); + const id = sections.strs.allocString(key.id, { compileContext: key }); const longPress: ListItem = sections.list.allocListFromSpaces( - longPressKeyIds, { x: key }, + longPressKeyIds, { compileContext: key }, sections); const longPressDefault = sections.strs.allocString(longPressDefaultKeyId, - { x: key }, + { compileContext: key }, sections); const multiTap: ListItem = sections.list.allocListFromSpaces( multiTapKeyIds, - { x: key }, + { compileContext: key }, sections); - const keySwitch = sections.strs.allocString(layerId, { x: key }); // 'switch' is a reserved word + const keySwitch = sections.strs.allocString(layerId, { compileContext: key }); // 'switch' is a reserved word const toRaw = output; @@ -396,7 +396,7 @@ export class KeysCompiler extends SectionCompiler { unescape: true, singleOk: true, nfd: true, - x: key, + compileContext: key, }, sections); if (!to.isOneChar) { diff --git a/developer/src/kmc-ldml/src/compiler/layr.ts b/developer/src/kmc-ldml/src/compiler/layr.ts index d450730bf9..baaf9282b5 100644 --- a/developer/src/kmc-ldml/src/compiler/layr.ts +++ b/developer/src/kmc-ldml/src/compiler/layr.ts @@ -69,13 +69,13 @@ export class LayrCompiler extends SectionCompiler { const sect = new Layr(); sect.lists = this.keyboard3.layers.map((layers) => { - const hardware = sections.strs.allocString(layers.formId, {x:layers}); + const hardware = sections.strs.allocString(layers.formId, {compileContext: layers}); // Already validated in validate const layerEntries = []; for (const layer of layers.layer) { const rows = layer.row.map((row) => { const erow: LayrRow = { - keys: row.keys.trim().split(/[ \t]+/).map((id) => sections.strs.allocString(id, { x: row })), + keys: row.keys.trim().split(/[ \t]+/).map((id) => sections.strs.allocString(id, { compileContext: row })), }; // include linenumber info for row return SectionCompiler.copySymbols(erow, row); @@ -84,7 +84,7 @@ export class LayrCompiler extends SectionCompiler { // push a layer entry for each modifier set for (const mod of mods) { layerEntries.push({ - id: sections.strs.allocString(layer.id, {x:layer}), + id: sections.strs.allocString(layer.id, {compileContext: layer}), mod, rows, }); diff --git a/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts b/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts index b4f4039924..1f879f475b 100644 --- a/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts +++ b/developer/src/kmc-ldml/src/compiler/ldml-compiler-messages.ts @@ -1,4 +1,5 @@ -import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageObjectSpec as mx, CompilerMessageSpec as m, CompilerMessageDef as def, ObjectWithCompileContext } from '@keymanapp/developer-utils'; +import { ObjectWithCompileContext } from "@keymanapp/common-types"; +import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageObjectSpec as mx, CompilerMessageSpec as m, CompilerMessageDef as def } from '@keymanapp/developer-utils'; // const SevInfo = CompilerErrorSeverity.Info | CompilerErrorNamespace.LdmlKeyboardCompiler; const SevHint = CompilerErrorSeverity.Hint | CompilerErrorNamespace.LdmlKeyboardCompiler; const SevWarn = CompilerErrorSeverity.Warn | CompilerErrorNamespace.LdmlKeyboardCompiler; @@ -13,38 +14,38 @@ const SevErrorTransform = SevError | 0xF00; */ export class LdmlCompilerMessages { static HINT_NormalizationDisabled = SevHint | 0x0001; - static Hint_NormalizationDisabled = (x?: ObjectWithCompileContext) => mx( - this.HINT_NormalizationDisabled, x, + static Hint_NormalizationDisabled = (compileContext?: ObjectWithCompileContext) => mx( + this.HINT_NormalizationDisabled, compileContext, `normalization=disabled is not recommended.`, ); static ERROR_InvalidLocale = SevError | 0x0002; - static Error_InvalidLocale = (o:{tag: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_InvalidLocale, x, + static Error_InvalidLocale = (o:{tag: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_InvalidLocale, compileContext, `Invalid BCP 47 locale form '${def(o.tag)}'`, ); static ERROR_HardwareLayerHasTooManyRows = SevError | 0x0003; - static Error_HardwareLayerHasTooManyRows = (x?: ObjectWithCompileContext) => mx( - this.ERROR_HardwareLayerHasTooManyRows, x, + static Error_HardwareLayerHasTooManyRows = (compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_HardwareLayerHasTooManyRows, compileContext, `'hardware' layer has too many rows`, ); static ERROR_RowOnHardwareLayerHasTooManyKeys = SevError | 0x0004; - static Error_RowOnHardwareLayerHasTooManyKeys = (o: { row: number, hardware: string, modifiers: string }, x?: ObjectWithCompileContext) => mx( - this.ERROR_RowOnHardwareLayerHasTooManyKeys, x, + static Error_RowOnHardwareLayerHasTooManyKeys = (o: { row: number, hardware: string, modifiers: string }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_RowOnHardwareLayerHasTooManyKeys, compileContext, `Row #${def(o.row)} on 'hardware' ${def(o.hardware)} layer for modifier ${def(o.modifiers)} has too many keys`, ); static ERROR_KeyNotFoundInKeyBag = SevError | 0x0005; - static Error_KeyNotFoundInKeyBag = (o: { keyId: string, col: number, row: number, layer: string, form: string }, x?: ObjectWithCompileContext) => mx( - this.ERROR_KeyNotFoundInKeyBag, x, + static Error_KeyNotFoundInKeyBag = (o: { keyId: string, col: number, row: number, layer: string, form: string }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_KeyNotFoundInKeyBag, compileContext, `Key '${def(o.keyId)}' in position #${def(o.col)} on row #${def(o.row)} of layer ${def(o.layer)}, form '${def(o.form)}' not found in key bag`, ); static HINT_OneOrMoreRepeatedLocales = SevHint | 0x0006; - static Hint_OneOrMoreRepeatedLocales = (x?: ObjectWithCompileContext) => mx( - this.HINT_OneOrMoreRepeatedLocales, x, + static Hint_OneOrMoreRepeatedLocales = (compileContext?: ObjectWithCompileContext) => mx( + this.HINT_OneOrMoreRepeatedLocales, compileContext, `After minimization, one or more locales is repeated and has been removed`, ); @@ -54,38 +55,38 @@ export class LdmlCompilerMessages { m(this.ERROR_InvalidFile, `The source file has an invalid structure: ${def(o.errorText)}`); static HINT_LocaleIsNotMinimalAndClean = SevHint | 0x0008; - static Hint_LocaleIsNotMinimalAndClean = (o:{sourceLocale: string, locale: string}, x?: ObjectWithCompileContext) => mx( - this.HINT_LocaleIsNotMinimalAndClean, x, + static Hint_LocaleIsNotMinimalAndClean = (o:{sourceLocale: string, locale: string}, compileContext?: ObjectWithCompileContext) => mx( + this.HINT_LocaleIsNotMinimalAndClean, compileContext, `Locale '${def(o.sourceLocale)}' is not minimal or correctly formatted and should be '${def(o.locale)}'`, ); static ERROR_InvalidScanCode = SevError | 0x0009; - static Error_InvalidScanCode = (o: { codes: string, id: string }, x?: ObjectWithCompileContext) => mx( - this.ERROR_InvalidScanCode, x, + static Error_InvalidScanCode = (o: { codes: string, id: string }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_InvalidScanCode, compileContext, `Form '${def(o.id)}' has invalid/unknown scancodes '${def(o.codes)}'`, ); static WARN_CustomForm = SevWarn | 0x000A; - static Warn_CustomForm = (o: { id: string }, x?: ObjectWithCompileContext) => mx( - this.WARN_CustomForm, x, + static Warn_CustomForm = (o: { id: string }, compileContext?: ObjectWithCompileContext) => mx( + this.WARN_CustomForm, compileContext, `Custom element. Key layout may not be as expected.`, ); static ERROR_GestureKeyNotFoundInKeyBag = SevError | 0x000B; - static Error_GestureKeyNotFoundInKeyBag = (o:{keyId: string, parentKeyId: string, attribute: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_GestureKeyNotFoundInKeyBag, x, + static Error_GestureKeyNotFoundInKeyBag = (o:{keyId: string, parentKeyId: string, attribute: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_GestureKeyNotFoundInKeyBag, compileContext, `Key '${def(o.keyId)}' not found in key bag, referenced from other '${def(o.parentKeyId)}' in ${def(o.attribute)}`, ); static HINT_NoDisplayForMarker = SevHint | 0x000C; - static Hint_NoDisplayForMarker = (o: { id: string }, x?: ObjectWithCompileContext) => mx( - this.HINT_NoDisplayForMarker, x, + static Hint_NoDisplayForMarker = (o: { id: string }, compileContext?: ObjectWithCompileContext) => mx( + this.HINT_NoDisplayForMarker, compileContext, `Key element with id "${def(o.id)}" has only marker output, but there is no matching display element by output or keyId. Keycap may be blank.`, ); static ERROR_InvalidVersion = SevError | 0x000D; - static Error_InvalidVersion = (o: { version: string; }, x?: ObjectWithCompileContext) => mx( - this.ERROR_InvalidVersion, x, + static Error_InvalidVersion = (o: { version: string; }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_InvalidVersion, compileContext, `Version number '${def(o.version)}' must be a semantic version format string with 'major.minor.patch' components.`, `The version number in the LDML keyboard file must be a [semantic version](https://semver.org) (semver) string. This string has a format of three @@ -98,186 +99,186 @@ export class LdmlCompilerMessages { ); static ERROR_MustBeAtLeastOneLayerElement = SevError | 0x000E; - static Error_MustBeAtLeastOneLayerElement = (x?: ObjectWithCompileContext) => mx( - this.ERROR_MustBeAtLeastOneLayerElement, x, + static Error_MustBeAtLeastOneLayerElement = (compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_MustBeAtLeastOneLayerElement, compileContext, `The source file must contain at least one layer element.`, ); static HINT_NoDisplayForSwitch = SevHint | 0x000F; - static Hint_NoDisplayForSwitch = (o: { id: string }, x?: ObjectWithCompileContext) => mx( - this.HINT_NoDisplayForSwitch, x, + static Hint_NoDisplayForSwitch = (o: { id: string }, compileContext?: ObjectWithCompileContext) => mx( + this.HINT_NoDisplayForSwitch, compileContext, `Key element with id "${def(o.id)}" is a layer switch key, but there is no matching display element by keyId. Keycap may be blank.`, ); static ERROR_DisplayIsRepeated = SevError | 0x0010; - static Error_DisplayIsRepeated = (o:{display?: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_DisplayIsRepeated, x, + static Error_DisplayIsRepeated = (o:{display?: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_DisplayIsRepeated, compileContext, `display display='${def(o.display)}' refers to the same keyId or output as another entry.`, ); static ERROR_KeyMissingToGapOrSwitch = SevError | 0x0011; - static Error_KeyMissingToGapOrSwitch = (o:{keyId: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_KeyMissingToGapOrSwitch, x, + static Error_KeyMissingToGapOrSwitch = (o:{keyId: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_KeyMissingToGapOrSwitch, compileContext, `key id='${def(o.keyId)}' must have either output=, gap=, or layerId=.`, ); static ERROR_ExcessHardware = SevError | 0x0012; - static Error_ExcessHardware = (o:{formId: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_ExcessHardware, x, + static Error_ExcessHardware = (o:{formId: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_ExcessHardware, compileContext, `layers formId=${def(o.formId)}: Can only have one non-'touch' element`, ); static ERROR_InvalidHardware = SevError | 0x0013; - static Error_InvalidHardware = (o: { formId: string }, x?: ObjectWithCompileContext) => mx( - this.ERROR_InvalidHardware, x, + static Error_InvalidHardware = (o: { formId: string }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_InvalidHardware, compileContext, `layers has invalid value formId=${def(o.formId)}`, ); static ERROR_InvalidModifier = SevError | 0x0014; - static Error_InvalidModifier = (o:{modifiers: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_InvalidModifier, x, + static Error_InvalidModifier = (o:{modifiers: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_InvalidModifier, compileContext, `layer has invalid modifiers='${def(o.modifiers)}'`, ); static ERROR_MissingFlicks = SevError | 0x0015; - static Error_MissingFlicks = (o: {id: string, flickId: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_MissingFlicks, x, + static Error_MissingFlicks = (o: {id: string, flickId: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_MissingFlicks, compileContext, `key id=${def(o.id)} refers to missing flickId=${def(o.flickId)}`, ); static ERROR_DuplicateVariable = SevError | 0x0016; - static Error_DuplicateVariable = (o:{id: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_DuplicateVariable, x, + static Error_DuplicateVariable = (o:{id: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_DuplicateVariable, compileContext, `duplicate variable: id=${def(o.id)}`, ); // Not hit due to XML parsing static ERROR_InvalidTransformsType = SevError | 0x0018; - static Error_InvalidTransformsType = (o:{type: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_InvalidTransformsType, x, + static Error_InvalidTransformsType = (o:{type: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_InvalidTransformsType, compileContext, `Invalid transforms type: '${def(o.type)}'`, ); static ERROR_DuplicateTransformsType = SevError | 0x0019; - static Error_DuplicateTransformsType = (o:{type: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_DuplicateTransformsType, x, + static Error_DuplicateTransformsType = (o:{type: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_DuplicateTransformsType, compileContext, `Duplicate transforms type: '${def(o.type)}'`, ); static ERROR_MixedTransformGroup = SevError | 0x001A; - static Error_MixedTransformGroup = (x?: ObjectWithCompileContext) => mx( - this.ERROR_MixedTransformGroup, x, + static Error_MixedTransformGroup = (compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_MixedTransformGroup, compileContext, `transformGroup cannot contain both reorder and transform elements`, ); static ERROR_EmptyTransformGroup = SevError | 0x001B; - static Error_EmptyTransformGroup = (x?: ObjectWithCompileContext) => mx( - this.ERROR_EmptyTransformGroup, x, + static Error_EmptyTransformGroup = (compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_EmptyTransformGroup, compileContext, `transformGroup must have either reorder or transform elements`, ); static ERROR_MissingStringVariable = SevError | 0x001C; - static Error_MissingStringVariable = (o:{id: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_MissingStringVariable, x, + static Error_MissingStringVariable = (o:{id: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_MissingStringVariable, compileContext, `Reference to undefined string variable: \${${def(o.id)}}`, ); static ERROR_MissingSetVariable = SevError | 0x001D; - static Error_MissingSetVariable = (o:{id: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_MissingSetVariable, x, + static Error_MissingSetVariable = (o:{id: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_MissingSetVariable, compileContext, `Reference to undefined set variable: \$[${def(o.id)}]`, ); static ERROR_MissingUnicodeSetVariable = SevError | 0x001E; - static Error_MissingUnicodeSetVariable = (o:{id: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_MissingUnicodeSetVariable, x, + static Error_MissingUnicodeSetVariable = (o:{id: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_MissingUnicodeSetVariable, compileContext, `Reference to undefined UnicodeSet variable: \$[${def(o.id)}]`, ); static ERROR_NeedSpacesBetweenSetVariables = SevError | 0x001F; - static Error_NeedSpacesBetweenSetVariables = (o:{item: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_NeedSpacesBetweenSetVariables, x, + static Error_NeedSpacesBetweenSetVariables = (o:{item: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_NeedSpacesBetweenSetVariables, compileContext, `Need spaces between set variables: ${def(o.item)}`, ); static ERROR_CantReferenceSetFromUnicodeSet = SevError | 0x0020; - static Error_CantReferenceSetFromUnicodeSet = (o:{id: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_CantReferenceSetFromUnicodeSet, x, + static Error_CantReferenceSetFromUnicodeSet = (o:{id: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_CantReferenceSetFromUnicodeSet, compileContext, `Illegal use of set variable from within UnicodeSet: \$[${def(o.id)}]` ); static ERROR_MissingMarkers = SevError | 0x0021; - static Error_MissingMarkers = (o: { ids: string }, x?: ObjectWithCompileContext) => mx( - this.ERROR_MissingMarkers, x, + static Error_MissingMarkers = (o: { ids: string }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_MissingMarkers, compileContext, `Markers used for matching but not defined: ${def(o.ids)}` ); static ERROR_DisplayNeedsToOrId = SevError | 0x0022; - static Error_DisplayNeedsToOrId = (o:{display?: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_DisplayNeedsToOrId, x, + static Error_DisplayNeedsToOrId = (o:{display?: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_DisplayNeedsToOrId, compileContext, `display display='${def(o.display)}' needs output= or keyId=, but not both`, ); static HINT_PUACharacters = SevHint | 0x0023; - static Hint_PUACharacters = (o: { count: number, lowestCh: string }, x?: ObjectWithCompileContext) => mx( - this.HINT_PUACharacters, x, + static Hint_PUACharacters = (o: { count: number, lowestCh: string }, compileContext?: ObjectWithCompileContext) => mx( + this.HINT_PUACharacters, compileContext, `File contains ${def(o.count)} PUA character(s), including ${def(o.lowestCh)}`, ); static WARN_UnassignedCharacters = SevWarn | 0x0024; - static Warn_UnassignedCharacters = (o: { count: number, lowestCh: string }, x?: ObjectWithCompileContext) => mx( - this.WARN_UnassignedCharacters, x, + static Warn_UnassignedCharacters = (o: { count: number, lowestCh: string }, compileContext?: ObjectWithCompileContext) => mx( + this.WARN_UnassignedCharacters, compileContext, `File contains ${def(o.count)} unassigned character(s), including ${def(o.lowestCh)}`, ); static ERROR_IllegalCharacters = SevError | 0x0025; - static Error_IllegalCharacters = (o: { count: number, lowestCh: string }, x?: ObjectWithCompileContext) => mx( - this.ERROR_IllegalCharacters, x, + static Error_IllegalCharacters = (o: { count: number, lowestCh: string }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_IllegalCharacters, compileContext, `File contains ${def(o.count)} illegal character(s), including ${def(o.lowestCh) }`, ); static HINT_CharClassImplicitDenorm = SevHint | 0x0026; - static Hint_CharClassImplicitDenorm = (o: { lowestCh: number }, x?: ObjectWithCompileContext) => mx( - this.HINT_CharClassImplicitDenorm, x, + static Hint_CharClassImplicitDenorm = (o: { lowestCh: number }, compileContext?: ObjectWithCompileContext) => mx( + this.HINT_CharClassImplicitDenorm, compileContext, `File has character classes which span non-NFD character(s), including ${def(o.lowestCh)}. These will not match any text.`, ); static WARN_CharClassExplicitDenorm = SevWarn | 0x0027; - static Warn_CharClassExplicitDenorm = (o: { lowestCh: number }, x?: ObjectWithCompileContext) => mx( - this.WARN_CharClassExplicitDenorm, x, + static Warn_CharClassExplicitDenorm = (o: { lowestCh: number }, compileContext?: ObjectWithCompileContext) => mx( + this.WARN_CharClassExplicitDenorm, compileContext, `File has character classes which include non-NFD characters(s), including ${def(o.lowestCh)}. These will not match any text.`, ); // Available: 0x0028 static ERROR_InvalidVariableIdentifier = SevError | 0x0029; - static Error_InvalidVariableIdentifier = (o: { id: string }, x?: ObjectWithCompileContext) => mx( - this.ERROR_InvalidVariableIdentifier, x, + static Error_InvalidVariableIdentifier = (o: { id: string }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_InvalidVariableIdentifier, compileContext, `Invalid variable identifier "${def(o.id)}". Identifiers must be between 1 and 32 characters, and can use A-Z, a-z, 0-9, and _.`, ); static ERROR_InvalidMarkerIdentifier = SevError | 0x002A; - static Error_InvalidMarkerIdentifier = (o: { id: string }, x?: ObjectWithCompileContext) => mx( - this.ERROR_InvalidMarkerIdentifier, x, + static Error_InvalidMarkerIdentifier = (o: { id: string }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_InvalidMarkerIdentifier, compileContext, `Invalid marker identifier "\m{${def(o.id)}}". Identifiers must be between 1 and 32 characters, and can use A-Z, a-z, 0-9, and _.`, ); static WARN_StringDenorm = SevWarn | 0x002B; - static Warn_StringDenorm = (o: { s: string }, x?: ObjectWithCompileContext) => mx( - this.WARN_StringDenorm, x, + static Warn_StringDenorm = (o: { s: string }, compileContext?: ObjectWithCompileContext) => mx( + this.WARN_StringDenorm, compileContext, `File contains string "${def(o.s)}" that is neither NFC nor NFD.`, ); static ERROR_DuplicateLayerWidth = SevError | 0x002C; - static Error_DuplicateLayerWidth = (o: { minDeviceWidth: number }, x?: ObjectWithCompileContext) => mx( - this.ERROR_DuplicateLayerWidth, x, + static Error_DuplicateLayerWidth = (o: { minDeviceWidth: number }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_DuplicateLayerWidth, compileContext, `Two or more layers have minDeviceWidth=${def(o.minDeviceWidth)}`, `Touch layers must have distinct widths.` ); static ERROR_InvalidLayerWidth = SevError | 0x002D; - static Error_InvalidLayerWidth = (o: { minDeviceWidth: number }, x?: ObjectWithCompileContext) => mx( - this.ERROR_InvalidLayerWidth, x, + static Error_InvalidLayerWidth = (o: { minDeviceWidth: number }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_InvalidLayerWidth, compileContext, `Invalid Layers minDeviceWidth=${def(o.minDeviceWidth)}`, `Width must be between 1-999 (millimeters), inclusive.` // sync with layr_max_minDeviceWidth / layr_max_maxDeviceWidth (from spec) ); @@ -285,8 +286,8 @@ export class LdmlCompilerMessages { // Available: 0x02E-0x2F static ERROR_InvalidQuadEscape = SevError | 0x0030; - static Error_InvalidQuadEscape = (o: { cp: string, recommended: string }, x?: ObjectWithCompileContext) => mx( - this.ERROR_InvalidQuadEscape, x, + static Error_InvalidQuadEscape = (o: { cp: string, recommended: string }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_InvalidQuadEscape, compileContext, `Invalid escape "${def(o.cp)}"`, `**Hint**: Use "${def(o.recommended)}"`, ); @@ -296,8 +297,8 @@ export class LdmlCompilerMessages { // This is a bit of a catch-all and represents messages bubbling up from the underlying regex engine static ERROR_UnparseableTransformFrom = SevErrorTransform | 0x00; - static Error_UnparseableTransformFrom = (o: { from: string, message: string }, x?: ObjectWithCompileContext) => mx( - this.ERROR_UnparseableTransformFrom, x, + static Error_UnparseableTransformFrom = (o: { from: string, message: string }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_UnparseableTransformFrom, compileContext, `Invalid transform from="${def(o.from)}": "${def(o.message)}"`, ); @@ -306,8 +307,8 @@ export class LdmlCompilerMessages { //------------------------------------------------------------------------------| static ERROR_IllegalTransformDollarsign = SevErrorTransform | 0x01; - static Error_IllegalTransformDollarsign = (o: { from: string }, x?: ObjectWithCompileContext) => mx( - this.ERROR_IllegalTransformDollarsign, x, + static Error_IllegalTransformDollarsign = (o: { from: string }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_IllegalTransformDollarsign, compileContext, `Invalid transform from="${def(o.from)}": Unescaped dollar-sign ($) is not valid transform syntax.`, ` **Hint**: Use \`\\$\` to match a literal dollar-sign. If this precedes a variable name, the variable name may not be valid (A-Z, a-z, 0-9, _, 32 @@ -315,36 +316,36 @@ export class LdmlCompilerMessages { `); static ERROR_TransformFromMatchesNothing = SevErrorTransform | 0x02; - static Error_TransformFromMatchesNothing = (o: { from: string }, x?: ObjectWithCompileContext) => mx( - this.ERROR_TransformFromMatchesNothing, x, + static Error_TransformFromMatchesNothing = (o: { from: string }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_TransformFromMatchesNothing, compileContext, `Invalid transfom from="${def(o.from)}": Matches an empty string.` ); static ERROR_IllegalTransformPlus = SevErrorTransform | 0x03; - static Error_IllegalTransformPlus = (o: { from: string }, x?: ObjectWithCompileContext) => mx( - this.ERROR_IllegalTransformPlus, x, + static Error_IllegalTransformPlus = (o: { from: string }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_IllegalTransformPlus, compileContext, `Invalid transform from="${def(o.from)}": Unescaped plus (+) is not valid transform syntax.`, ` **Hint**: Use \`\\+\` to match a literal plus. `); static ERROR_IllegalTransformAsterisk = SevErrorTransform | 0x04; - static Error_IllegalTransformAsterisk = (o: { from: string }, x?: ObjectWithCompileContext) =>mx( - this.ERROR_IllegalTransformAsterisk, x, + static Error_IllegalTransformAsterisk = (o: { from: string }, compileContext?: ObjectWithCompileContext) =>mx( + this.ERROR_IllegalTransformAsterisk, compileContext, `Invalid transform from="${def(o.from)}": Unescaped asterisk (*) is not valid transform syntax.`, ` **Hint**: Use \`\\*\` to match a literal asterisk. `); static ERROR_IllegalTransformToUset = SevErrorTransform | 0x05; - static Error_IllegalTransformToUset = (o: { to: string }, x?: ObjectWithCompileContext) => mx( - this.ERROR_IllegalTransformToUset, x, + static Error_IllegalTransformToUset = (o: { to: string }, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_IllegalTransformToUset, compileContext, `Invalid transform to="${def(o.to)}": Set variable (\\$[…]) cannot be used in 'to=' unless part of a map.`, ` **Hint**: If a map was meant, must use the form \`\`. `); static ERROR_UnparseableTransformTo = SevErrorTransform | 0x06; - static Error_UnparseableTransformTo = (o: {to: string, message: string}, x?: ObjectWithCompileContext) => mx( - this.ERROR_UnparseableTransformTo, x, + static Error_UnparseableTransformTo = (o: {to: string, message: string}, compileContext?: ObjectWithCompileContext) => mx( + this.ERROR_UnparseableTransformTo, compileContext, `Invalid transform to="${def(o.to)}": "${def(o.message)}"`, ); } diff --git a/developer/src/kmc-ldml/src/compiler/loca.ts b/developer/src/kmc-ldml/src/compiler/loca.ts index 19c2522a4a..d624dbaff1 100644 --- a/developer/src/kmc-ldml/src/compiler/loca.ts +++ b/developer/src/kmc-ldml/src/compiler/loca.ts @@ -1,6 +1,7 @@ import { constants } from "@keymanapp/ldml-keyboard-constants"; import { KMXPlus } from '@keymanapp/common-types'; -import { LDMLKeyboard, ObjectWithCompileContext } from '@keymanapp/developer-utils'; +import { LDMLKeyboard } from '@keymanapp/developer-utils'; +import { ObjectWithCompileContext } from "@keymanapp/common-types"; import { SectionCompiler } from "./section-compiler.js"; import { LdmlCompilerMessages } from "./ldml-compiler-messages.js"; @@ -71,7 +72,7 @@ export class LocaCompiler extends SectionCompiler { // yet include `getCanonicalLocales` but node 16 does include it so we can // safely use it. Also well supported in modern browsers. const canonicalLocales = (Intl as any).getCanonicalLocales(locales) as string[]; - result.locales = canonicalLocales.map(locale => sections.strs.allocString(locale, {x: this.contextForLocale(locale)})); + result.locales = canonicalLocales.map(locale => sections.strs.allocString(locale, {compileContext: this.contextForLocale(locale)})); if(result.locales.length < locales.length) { this.callbacks.reportMessage(LdmlCompilerMessages.Hint_OneOrMoreRepeatedLocales(this.keyboard3?.locales)); diff --git a/developer/src/kmc-ldml/src/compiler/meta.ts b/developer/src/kmc-ldml/src/compiler/meta.ts index e6cd6371ee..eeb7f4f46c 100644 --- a/developer/src/kmc-ldml/src/compiler/meta.ts +++ b/developer/src/kmc-ldml/src/compiler/meta.ts @@ -54,17 +54,17 @@ export class MetaCompiler extends SectionCompiler { public compile(sections: DependencySections): Meta { const result = new Meta(); result.author = sections.strs.allocString(this.keyboard3.info?.author, - {x: this.keyboard3.info}); + {compileContext: this.keyboard3.info}); result.conform = sections.strs.allocString(this.keyboard3.conformsTo, - {x: this.keyboard3}); + {compileContext: this.keyboard3}); result.layout = sections.strs.allocString(this.keyboard3.info?.layout, - {x: this.keyboard3.info}); + {compileContext: this.keyboard3.info}); result.name = sections.strs.allocString(this.keyboard3.info?.name, - {x: this.keyboard3.info}); + {compileContext: this.keyboard3.info}); result.indicator = sections.strs.allocString(this.keyboard3.info?.indicator, - {x: this.keyboard3.info}); + {compileContext: this.keyboard3.info}); result.version = sections.strs.allocString(this.keyboard3.version?.number ?? "0.0.0", - {x: this.keyboard3.version}); + {compileContext: this.keyboard3.version}); result.settings = (this.keyboard3.settings?.normalization == "disabled" ? KeyboardSettings.normalizationDisabled : 0); return result; diff --git a/developer/src/kmc-ldml/src/compiler/substitution-tracker.ts b/developer/src/kmc-ldml/src/compiler/substitution-tracker.ts index ef78b55685..9282655a64 100644 --- a/developer/src/kmc-ldml/src/compiler/substitution-tracker.ts +++ b/developer/src/kmc-ldml/src/compiler/substitution-tracker.ts @@ -1,5 +1,5 @@ import { LdmlKeyboardTypes } from "@keymanapp/common-types"; -import { ObjectWithCompileContext } from "@keymanapp/developer-utils"; +import { ObjectWithCompileContext } from '@keymanapp/common-types'; /** * Verb for SubstitutionTracker.add() @@ -40,33 +40,33 @@ export class SubstitutionTracker { * @param verb what kind of use we are adding * @param markers list of substitutions to add */ - add(verb: SubstitutionUse, markers: string[], x?: ObjectWithCompileContext) { + add(verb: SubstitutionUse, markers: string[], compileContext?: ObjectWithCompileContext) { if (!markers.length) { return; // skip if empty } - x = x || {}; // need at least an empty object + compileContext = compileContext || {}; // need at least an empty object if (verb == SubstitutionUse.emit) { markers.forEach((m) => { - this.emitted.set(m, x); - this.all.set(m,x); + this.emitted.set(m, compileContext); + this.all.set(m, compileContext); }); } else if (verb == SubstitutionUse.consume) { markers.forEach((m) => { - this.consumed.set(m,x); - this.all.set(m,x); + this.consumed.set(m, compileContext); + this.all.set(m, compileContext); }); } else if (verb == SubstitutionUse.match) { markers.forEach((m) => { - this.matched.set(m,x); - this.all.set(m,x); + this.matched.set(m, compileContext); + this.all.set(m, compileContext); }); } else if (verb == SubstitutionUse.variable) { markers.forEach((m) => { // we don't know, so add it to all three - this.matched.set(m,x); - this.emitted.set(m,x); - this.consumed.set(m,x); - this.all.set(m,x); + this.matched.set(m, compileContext); + this.emitted.set(m, compileContext); + this.consumed.set(m, compileContext); + this.all.set(m, compileContext); }); /* c8 skip next 3 */ } else { @@ -77,22 +77,22 @@ export class SubstitutionTracker { /** rollup of several substitution types */ export class Substitutions { - addSetAndStringSubtitution(verb: SubstitutionUse, str?: string, x?: ObjectWithCompileContext) { - this.set.add(verb, LdmlKeyboardTypes.VariableParser.allSetReferences(str), x); - this.addStringAndMarkerSubstitution(verb, str, x); + addSetAndStringSubtitution(verb: SubstitutionUse, str?: string, compileContext?: ObjectWithCompileContext) { + this.set.add(verb, LdmlKeyboardTypes.VariableParser.allSetReferences(str), compileContext); + this.addStringAndMarkerSubstitution(verb, str, compileContext); } /** add a string that can have string var substitutions or markers */ - addStringAndMarkerSubstitution(verb: SubstitutionUse, str?: string, x?: ObjectWithCompileContext) { - this.addMarkers(verb, str, x); - this.addStringSubstitution(verb, str, x); + addStringAndMarkerSubstitution(verb: SubstitutionUse, str?: string, compileContext?: ObjectWithCompileContext) { + this.addMarkers(verb, str, compileContext); + this.addStringSubstitution(verb, str, compileContext); } - addStringSubstitution(verb: SubstitutionUse, str?: string, x?: ObjectWithCompileContext) { - this.string.add(verb, LdmlKeyboardTypes.VariableParser.allStringReferences(str), x); + addStringSubstitution(verb: SubstitutionUse, str?: string, compileContext?: ObjectWithCompileContext) { + this.string.add(verb, LdmlKeyboardTypes.VariableParser.allStringReferences(str), compileContext); } /** add a string that's just markers */ - addMarkers(verb: SubstitutionUse, str?: string, x?: ObjectWithCompileContext) { - this.markers.add(verb, LdmlKeyboardTypes.MarkerParser.allReferences(str), x); - LdmlKeyboardTypes.MarkerParser.allBrokenReferences(str).forEach(m => this.badMarkers.set(m, x)); + addMarkers(verb: SubstitutionUse, str?: string, compileContext?: ObjectWithCompileContext) { + this.markers.add(verb, LdmlKeyboardTypes.MarkerParser.allReferences(str), compileContext); + LdmlKeyboardTypes.MarkerParser.allBrokenReferences(str).forEach(m => this.badMarkers.set(m, compileContext)); } // all valid markers markers: SubstitutionTracker; diff --git a/developer/src/kmc-ldml/src/compiler/tran.ts b/developer/src/kmc-ldml/src/compiler/tran.ts index 745eeb6d0f..0a3a781f6f 100644 --- a/developer/src/kmc-ldml/src/compiler/tran.ts +++ b/developer/src/kmc-ldml/src/compiler/tran.ts @@ -1,6 +1,7 @@ import { constants, SectionIdent } from "@keymanapp/ldml-keyboard-constants"; import { KMXPlus, LdmlKeyboardTypes, util } from '@keymanapp/common-types'; -import { CompilerCallbacks, LDMLKeyboard, ObjectWithCompileContext } from "@keymanapp/developer-utils"; +import { CompilerCallbacks, LDMLKeyboard } from "@keymanapp/developer-utils"; +import { ObjectWithCompileContext } from "@keymanapp/common-types"; import { SectionCompiler } from "./section-compiler.js"; import Bksp = KMXPlus.Bksp; @@ -142,7 +143,7 @@ export abstract class TransformCompiler DTD - this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidVariableIdentifier({id}, x)); + this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidVariableIdentifier({id}, compileContext)); return false; } return true; @@ -65,11 +66,11 @@ export class VarsCompiler extends SectionCompiler { * @param id id * @param x context */ - function addId(id: string, x: LDMLKeyboard.Variable): void { + function addId(id: string, compileContext: LDMLKeyboard.Variable): void { if (allIds.has(id)) { dups.add(id); } else { - allIds.set(id, x); + allIds.set(id, compileContext); } } @@ -270,7 +271,7 @@ export class VarsCompiler extends SectionCompiler { const allMarkers : string[] = Array.from(mt.all.keys()).filter(m => m !== LdmlKeyboardTypes.MarkerParser.ANY_MARKER_ID).sort(); result.markers = sections.list.allocList(allMarkers, { // pass the first object in the listr - x: mt.all.get(allMarkers[0]||'') + compileContext: mt.all.get(allMarkers[0]||'') // however, this is the string with the marker *id*. So, from the StrsCompiler // point of view, the value of this context would be if the marker ID had, say, // invalid Unicode in it for ERROR_IllegalCharacters - but, that wouldn't be a diff --git a/developer/src/kmc-ldml/test/helpers/compareXml.ts b/developer/src/kmc-ldml/test/helpers/compareXml.ts index 057986bdb2..4082a40ce2 100644 --- a/developer/src/kmc-ldml/test/helpers/compareXml.ts +++ b/developer/src/kmc-ldml/test/helpers/compareXml.ts @@ -1,6 +1,7 @@ import {assert} from 'chai'; import {readFileSync} from 'node:fs'; import { KeymanXMLReader, SymbolUtils } from "@keymanapp/developer-utils"; +import { ObjectWithCompileContext } from '@keymanapp/common-types'; /** * @@ -8,10 +9,10 @@ import { KeymanXMLReader, SymbolUtils } from "@keymanapp/developer-utils"; * @param expect path to expected XML * @param mutator optional function that will be applied to the parsed object */ -export function compareXml(actual : string, expect: string, mutator?: (input: any) => any) { +export function compareXml(actual : string, expect: string, mutator?: (input: ObjectWithCompileContext) => ObjectWithCompileContext) { if (!mutator) { // no-op - mutator = (x: any) => x; + mutator = (compileContext: ObjectWithCompileContext) => compileContext; } const reader = new KeymanXMLReader('keyboard3'); From c8ab9453489dfc7a90375e05dbe0d91442b52909 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Sat, 21 Jun 2025 16:47:38 -0500 Subject: [PATCH 067/262] chore(common): clarify docs on boxXmlArray - not related to #14117 but mention o[x] instead of o.x in jsdocs --- common/web/types/src/util/util.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/web/types/src/util/util.ts b/common/web/types/src/util/util.ts index be1a715b17..06cb91dd5d 100644 --- a/common/web/types/src/util/util.ts +++ b/common/web/types/src/util/util.ts @@ -3,7 +3,7 @@ export { MATCH_HEX_ESCAPE, CONTAINS_QUAD_ESCAPE, MATCH_QUAD_ESCAPE }; /** * xml2js will not place single-entry objects into arrays. Easiest way to fix - * this is to box them ourselves as needed. Ensures that o.x is an array. + * this is to box them ourselves as needed. Ensures that o[x] is always an array. * * @param o Object with property to box * @param x Name of element to box @@ -392,4 +392,4 @@ export class NFDAnalyzer extends StringAnalyzer { return null; } } -} \ No newline at end of file +} From c7895532ade619d119230aa57a681c75e16f7a9a Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Fri, 27 Jun 2025 14:01:32 -0400 Subject: [PATCH 068/262] auto: increment master version to 19.0.71 --- HISTORY.md | 5 +++++ VERSION.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 421bd68a1f..6de1a1d0a7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # Keyman Version History +## 19.0.70 alpha 2025-06-27 + +* docs(developer): Add details on Template choices (#14228) +* chore(common,developer): expand 'x' to 'compileContext' (#14218) + ## 19.0.69 alpha 2025-06-25 * maint(resources): support additional workflow events for pr-build-status (#14219) diff --git a/VERSION.md b/VERSION.md index 15ad51e2c1..caf0881918 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.70 \ No newline at end of file +19.0.71 \ No newline at end of file From 90b7c34df8db51158937cb9af9c9f120aa7cd35d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Sat, 28 Jun 2025 15:38:22 +1000 Subject: [PATCH 069/262] maint(web): cleanup rsync call from bash for Windows agents and refactor Refactors the Powershell script which built the .download_info file for web into the keyman-web-release.sh script, and then bypasses the path rewriting that msys does when calling a Windows executable (rsync). This was tricky because msys was mangling all the paths to Windows-style but we specifically need to reference one remote Unix-style path in the rsync call. As we wanted to move away from the Powershell script anyway, it was cleaner to resolve this in one go by moving the whole script to bash. The new tc-download-info.inc.sh script is a minimally generalized version of the download_info writing from keyman-linux-release.sh, and once the dust settles, it would be good to DRY this out. Note that this is still Windows-only, despite the rest of the build being basically platform-agnostic. I wanted to get this stable before we generalize, which should be fairly straightforward, as it's only the rsync call which will be touched. Tested locally with a drop-in Windows executable that just prints its arguments, to verify that path mangling is not occurring where we don't want it. The DEBUG lines in keyman-web-release.sh will be removed once we are confident this is correct. Fixes: #14225 --- .../teamcity/includes/tc-download-info.inc.sh | 50 ++++++++++++ resources/teamcity/web/keyman-web-release.sh | 29 ++++++- .../teamcity/web/zip-and-upload-artifacts.ps1 | 79 ------------------- 3 files changed, 77 insertions(+), 81 deletions(-) create mode 100644 resources/teamcity/includes/tc-download-info.inc.sh delete mode 100755 resources/teamcity/web/zip-and-upload-artifacts.ps1 diff --git a/resources/teamcity/includes/tc-download-info.inc.sh b/resources/teamcity/includes/tc-download-info.inc.sh new file mode 100644 index 0000000000..14b77dba9f --- /dev/null +++ b/resources/teamcity/includes/tc-download-info.inc.sh @@ -0,0 +1,50 @@ +# Keyman is copyright (C) SIL Global. MIT License. + +. "${KEYMAN_ROOT}/resources/build/jq.inc.sh" + +# +# Write ${UPLOAD_DIR}/${ARTIFACT_FILENAME}.download_info file for the target +# artifact +# +# Parameters: +# 1: UPLOAD_DIR Directory where artifact can be found +# 2: ARTIFACT_FILENAME Filename (without path) of artifact +# 3: ARTIFACT_NAME Descriptive name of artifact +# 4: ARTIFACT_TYPE File extension of artifact, without initial period (e.g. tar.gz) +# 5: PLATFORM Target platform for artifact +# +write_download_info() { + # TODO: DRY out with Linux download_info writer, others + local UPLOAD_DIR="$1" + local ARTIFACT_FILENAME="$2" + local ARTFIACT_NAME="$3" + local ARTIFACT_TYPE="$4" + local PLATFORM="$5" + + local DATE HASH SIZE DOWNLOAD_INFO + + # Construct .download_info + DATE=$(date +%F) + HASH=$(md5sum "${UPLOAD_DIR}/${ARTIFACT_FILENAME}" | cut -d ' ' -f 1) + SIZE=$(stat --print="%s" "${UPLOAD_DIR}/${ARTIFACT_FILENAME}") + + DOWNLOAD_INFO=$( + "$JQ" -n \ + --arg NAME "${ARTFIACT_NAME}" \ + --arg BUILD_NUMBER "${KEYMAN_VERSION}" \ + --arg DATE "${DATE}" \ + --arg PLATFORM "${PLATFORM}" \ + --arg KEYMAN_TIER "${KEYMAN_TIER}" \ + --arg FILENAME "${ARTIFACT_FILENAME}" \ + --arg ARTIFACT_TYPE "${ARTIFACT_TYPE}" \ + --arg HASH "${HASH}" \ + --arg BUILD_COUNTER "${KEYMAN_VERSION_PATCH}" \ + --arg SIZE "${SIZE}" \ + '{ + name: $NAME, version: $BUILD_NUMBER, date: $DATE, platform: $PLATFORM, + stability: $KEYMAN_TIER, file: $FILENAME, md5: $HASH, type: $ARTIFACT_TYPE, + build: $BUILD_COUNTER, size: $SIZE + }' + ) + echo "${DOWNLOAD_INFO}" | "$JQ" . >> "${UPLOAD_DIR}/${ARTIFACT_FILENAME}.download_info" +} \ No newline at end of file diff --git a/resources/teamcity/web/keyman-web-release.sh b/resources/teamcity/web/keyman-web-release.sh index d2aa1c378b..bd930fd0c9 100755 --- a/resources/teamcity/web/keyman-web-release.sh +++ b/resources/teamcity/web/keyman-web-release.sh @@ -17,6 +17,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${KEYMAN_ROOT}/resources/teamcity/web/web-actions.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-linux.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-download-info.inc.sh" ################################ Main script ################################ @@ -53,8 +54,32 @@ function _push_release_to_skeymancom() { function _zip_and_upload_artifacts() { builder_echo start "zip and upload artifacts" "Zipping and uploading artifacts" - # shellcheck disable=SC2154 - powershell -NonInteractive -ExecutionPolicy Bypass -File "${THIS_SCRIPT_PATH}/zip-and-upload-artifacts.ps1" + cd "${KEYMAN_ROOT}/web" + + local UPLOAD_DIR="build/upload/$KEYMAN_VERSION" + + ./ci.sh prepare:downloads.keyman.com + + write_download_info "$UPLOAD_DIR" "keymanweb-$KEYMAN_VERSION.zip" KeymanWeb zip web + + rsync_args=( + '-vrzltp' # verbose, recurse, zip, copy symlinks, preserve times, permissions + '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r' # map Windows security to host security + '--stats' # show statistics for log + "--rsync-path=\"${RSYNC_PATH}\"" # path on remote server + "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts" # use ssh + "${KEYMAN_VERSION}" # upload the whole build folder + "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/web/${KEYMAN_TIER}/" # target server + path + ) + + cd build/upload + + local CYGPATH_RSYNC_HOME=$(cygpath -w "$RSYNC_HOME") + # DEBUG: echo "${rsync_args[@]}" + # DEBUG: MSYS_NO_PATHCONV=1 "C:\\Users\\marc\\source\\repos\\doecho\\Debug\\doecho.exe" "${CYGPATH_RSYNC_HOME}\\rsync.exe" "${rsync_args[@]}" + MSYS_NO_PATHCONV=1 "${CYGPATH_RSYNC_HOME}\\rsync.exe" "${rsync_args[@]}" + + cd "${KEYMAN_ROOT}/web" builder_echo end "zip and upload artifacts" success "Finished zipping and uploading artifacts" } diff --git a/resources/teamcity/web/zip-and-upload-artifacts.ps1 b/resources/teamcity/web/zip-and-upload-artifacts.ps1 deleted file mode 100755 index 2506f5658a..0000000000 --- a/resources/teamcity/web/zip-and-upload-artifacts.ps1 +++ /dev/null @@ -1,79 +0,0 @@ -# Keyman is copyright (C) SIL Global. MIT License. -# -# This script should be the identical for nightly/beta/stable for a given platform -# -# Working directory should be ${KEYMAN_ROOT}/web -# -$ErrorActionPreference = "Stop"; - -$tier = Get-Content ..\TIER.md -$build_number = Get-Content ..\VERSION.md -$build_counter = ${build_number} -replace "^\d+\.\d+\.(\d+)$", '$1' - -$upload_path = "build\upload\${build_number}" -$zip = "${upload_path}\keymanweb-${build_number}.zip" - -$7Z_HOME = $env:7Z_HOME -$RSYNC_HOME = $env:RSYNC_HOME -$RSYNC_PATH = $env:RSYNC_PATH -$RSYNC_USER = $env:RSYNC_USER -$RSYNC_HOST = $env:RSYNC_HOST -$RSYNC_ROOT = $env:RSYNC_ROOT -$USERPROFILE = $env:USERPROFILE - -# Since shell-scripting doesn't like number-initial variables, we convert it to a friendlier name. -$env:SEVEN_Z_HOME=$7Z_HOME - -# PowerShell fun: `--` parameters will break a PowerShell command when not escaped in some form. -# We can build the command in string form first and then execute the string, fortunately. -$cmd = '"C:\Program Files\Git\bin\bash.exe" --init-file "c:\Program Files\Git\etc\profile" -l ./ci.sh prepare:downloads.keyman.com' -cmd /c ${cmd} - -$hash = get-filehash ${zip} -Algorithm MD5 - -# -# Construct .build_info -# - -$download_info = @" -{ - "name": "KeymanWeb", - "version": "${build_number}", - "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", - "platform": "web", - "stability": "${tier}", - "file": "keymanweb-${build_number}.zip", - "md5": "$(${hash}.Hash)", - "type": "zip", - "build": "${build_counter}" -} -"@ - -# WriteAllLines is needed to avoid BOM -[System.IO.File]::WriteAllLines("${pwd}\${upload_path}\keymanweb-${build_number}.zip.download_info", ${download_info}) -# ${download_info} | Out-File ${upload_path}\keymanweb-${build_number}.zip.download_info -Encoding utf8 - -# -# Upload with rsync to downloads.keyman.com -# (rsync requires that we are in the upload folder to get folders in -# sync correctly; it is possible to resolve this but easier to just cd.) -# - -$rsync_args = @( - '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions - '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security - '--stats', # show statistics for log - "--rsync-path=""${RSYNC_PATH}""", # path on remote server - "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh - "${build_number}", # upload the whole build folder - "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/web/${tier}/" # target server + path -) - -# Write-Output "rsync parameters:" ${rsync_args} - -cd "${upload_path}\.." -& ${RSYNC_HOME}\rsync.exe ${rsync_args} -if (${LASTEXITCODE} -ne 0) { throw "Exit code is ${LASTEXITCODE}" } -cd .. - -# EOF From 4937362fddc5883ddf5194d5418388a35308f65c Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 30 Jun 2025 15:50:11 +0200 Subject: [PATCH 070/262] fix(web): deploy `web/build/publish` folder This fixes a deployment problem which seems have been around since 17.0. We also have to deploy the `web/build/publish` folder which contains all the files necessary to load and run the test keyboards. The fix can only be tested after a release build has happened and the files have been deployed. Fixes: #14234 Build-bot: skip Test-bot: skip --- web/ci.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/web/ci.sh b/web/ci.sh index f3f70eef93..4291e5c71a 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -189,9 +189,10 @@ function prepare_downloads_keyman_com_action() { mkdir -p "${STATIC}" mkdir -p "${STATIC}/build" - cp -rf build/app "${STATIC}/build/app" - cp -rf build/engine "${STATIC}/build/engine" - cp -rf build/tools "${STATIC}/build/tools" + cp -rf build/app "${STATIC}/build/app" + cp -rf build/engine "${STATIC}/build/engine" + cp -rf build/publish "${STATIC}/build/publish" + cp -rf build/tools "${STATIC}/build/tools" # avoid build/upload, since that's the folder we're building! cp -f index.html "${STATIC}/index.html" From 2af491f999c37ce56fb9f3000501173fd0b17b39 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 30 Jun 2025 14:01:16 -0400 Subject: [PATCH 071/262] auto: increment master version to 19.0.72 --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 6de1a1d0a7..884c8e4b57 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 19.0.71 alpha 2025-06-30 + +* refactor(linux): rename linux agent specific functions (#14231) + ## 19.0.70 alpha 2025-06-27 * docs(developer): Add details on Template choices (#14228) diff --git a/VERSION.md b/VERSION.md index caf0881918..0816ff9708 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.71 \ No newline at end of file +19.0.72 \ No newline at end of file From a0c2abd2fb587fbb01afbbd21570a11d1c19d5a8 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 30 Jun 2025 20:34:25 -0400 Subject: [PATCH 072/262] auto: increment master version to 19.0.73 --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 884c8e4b57..1254bcbe79 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 19.0.72 alpha 2025-07-01 + +* maint(web): cleanup rsync call from bash for Windows agents and refactor (#14236) + ## 19.0.71 alpha 2025-06-30 * refactor(linux): rename linux agent specific functions (#14231) diff --git a/VERSION.md b/VERSION.md index 0816ff9708..c571ed6caf 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.72 \ No newline at end of file +19.0.73 \ No newline at end of file From 6345a0b9b3f106cf72bef7a823f424f2614ba60e Mon Sep 17 00:00:00 2001 From: rc-swag <58423624+rc-swag@users.noreply.github.com> Date: Tue, 1 Jul 2025 15:13:02 +1000 Subject: [PATCH 073/262] fix(windows): review comment Co-authored-by: Eberhard Beilharz --- windows/src/engine/tsysinfo/UfrmEmail.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/src/engine/tsysinfo/UfrmEmail.pas b/windows/src/engine/tsysinfo/UfrmEmail.pas index d3768c5b2b..ec096cc057 100644 --- a/windows/src/engine/tsysinfo/UfrmEmail.pas +++ b/windows/src/engine/tsysinfo/UfrmEmail.pas @@ -138,7 +138,7 @@ begin Upload; if Response.StatusCode = 413 then begin - ShowMessage('The diagnostic report file was too large: '+IntToStr(Response.StatusCode)+ 'Contact us via the Keyman Community Forum for help.'); + ShowMessage('The diagnostic report file was too large: '+IntToStr(Response.StatusCode)+ '. Contact us via the Keyman Community Forum for help.'); Exit; end; if Response.StatusCode <> 200 then From 0c2e9ca13ca76dcc5f69cffbcbc19a7347cb7307 Mon Sep 17 00:00:00 2001 From: Meng-Heng Date: Tue, 1 Jul 2025 16:24:28 +0700 Subject: [PATCH 074/262] docs: add ansi and iso context and image, change img tags, and polish for readibility --- .../docs/help/context/keyboard-editor.md | 75 ++++++++---------- .../help/images/ui/OnScreenKeyboard_ISO.png | Bin 0 -> 13169 bytes 2 files changed, 32 insertions(+), 43 deletions(-) create mode 100644 developer/docs/help/images/ui/OnScreenKeyboard_ISO.png diff --git a/developer/docs/help/context/keyboard-editor.md b/developer/docs/help/context/keyboard-editor.md index c445bb7280..fc072b552c 100644 --- a/developer/docs/help/context/keyboard-editor.md +++ b/developer/docs/help/context/keyboard-editor.md @@ -13,13 +13,10 @@ multiple source files, which are all managed within this one editor. The following image shows the first tab of the editor, the Details tab, for a brand new keyboard, with those three initial tabs. -![Keyboard Editor - New file, Details tab](../images/ui/frmKeymanWizard_New.png) +Keyboard Editor - New file, Details tab -> ### Note -Users of earlier versions of Keyman Developer may initially have trouble -finding tabs such as the Icon tab or the On-Screen tab, as they are not -initially visible. The Features grid on the Details tab allows you to -add these extra features into the keyboard. +> [!Note] +> Users of earlier versions of Keyman Developer may initially have trouble finding tabs such as the Icon tab or the On-Screen tab, as they are not initially visible. The Features grid on the Details tab allows you to add these extra features into the keyboard. ## Keyboard component files @@ -47,7 +44,7 @@ because that reflects both the Details and the Layout tab. ## Details tab -![Keyboard Editor - Details tab](../images/ui/frmKeymanWizard_Details.png) +Keyboard Editor - Details tab The Details tab grows as you add more options to a keyboard. The fields here are: @@ -102,7 +99,7 @@ Features ## Layout tab -![Keyboard Editor - Layout tab, Design view](../images/ui/frmKeymanWizard_Layout_Design.png) +Keyboard Editor - Layout tab, Design view The Layout tab gives you a simple interface to quickly create a keyboard using a visual representation of a desktop/laptop computer keyboard. You @@ -124,13 +121,11 @@ There are two ways to assign characters to the keys: onto the appropriate key. This will set the key to output that character. -> ### Hint - To add the character to a key with existing characters, hold -Ctrl while dropping it onto the key. +> [!TIP] +> To add the character to a key with existing characters, hold Ctrl while dropping it onto the key. - -> ### Note - Any key that does not have a character assigned to it will output what the selected Windows layout specifies. +> [!Note] +> Any key that does not have a character assigned to it will output what the selected Windows layout specifies. Distinguish between left and right Ctrl/Alt : A Keyman keyboard can treat left and right Ctrl and Alt identically, @@ -151,9 +146,7 @@ Display 102nd Key (as on European keyboards) You can press and release Ctrl to select another key on the keyboard using your keyboard. -
- -![Keyboard Editor - Layout tab, Code view](../images/ui/frmKeymanWizard_Layout_Code.png) +Keyboard Editor - Layout tab, Code view The Code view shows the source code of the keyboard file. This is where all the information in the Details and Layout tab is stored, and @@ -165,7 +158,7 @@ editor shown within this view. ## On-Screen tab -![Keyboard Editor - On-Screen tab](../images/ui/OnScreenKeyboard.png) +Keyboard Editor - On-Screen tab This tab allows you to edit the visual representation of your keyboard layout. The content on this tab is stored in the .kvks file associated @@ -213,17 +206,27 @@ Distinguish between left and right Ctrl/Alt : If checked, treats the left and right Ctrl/Alt as separate layers Display 102nd Key (as on European keyboards) -: European keyboards have one extra key that is not on US keyboards. +: European keyboards (ISO layout) have one extra key that is not on US keyboards (ANSI layout). This key is positioned to the right of the left shift key. Some other keyboards have additional keys; these are not shown on the - layout designer. If you have a European layout selected as your + layout designer. + + If you have a European layout selected as your Windows layout, the 102nd key will always be visible in the designer; ensure you select this checkbox if you want the 102nd key to always be visible to end users of the layout, irrespective of - their selected base layout. When a European layout is selected as + their selected base layout. + + When a European layout is selected as your Windows layout, the shape of the Enter key will also change to take two rows, and the backslash key will move down one row, but not - otherwise change in behaviour. + otherwise change in behaviour. + + Keyboard Editor - On-Screen tab with ISO layout + *See the difference between the two layouts in Keyman Developer.* + + > [!NOTE] + > To ensure compatibility with both ANSI and ISO physical keyboards, you should tick the "Display 102nd Key (as on European keyboards)" checkbox in Keyman Developer. Auto-fill underlying layout : When the Fill from layout button is clicked, if this option is @@ -232,7 +235,7 @@ Auto-fill underlying layout ## Touch Layout tab -![Keyboard Editor - Touch Layout tab, Design view](../images/ui/TouchLayout_Design.png) +Keyboard Editor - Touch Layout tab, Design view The Touch Layout tab is used to create the visible representation of the keyboard layout for touch devices. It works similarly to the On Screen @@ -251,7 +254,7 @@ including iPhone and iPad, in different orientations, to allow you to visualize the keyboard layout before you load it onto a device. The following image shows all aspects of the touch design view. -![Keyboard Editor - Touch Layout tab, Design view closeup](../images/ui/TouchLayout_Design_2.png) +Keyboard Editor - Touch Layout tab, Design view closeup ### Left sidebar controls @@ -265,20 +268,6 @@ Template... current layout are not in the new template, their definitions will be lost. Therefore, selecting an appropriate template early in the development process is suggested. - - - template-basic: has only a phone layout (4 rows of keys), with three layers: default (K_A through K_Z - plus some punctuation), shift (Shift K_A through Shift K_Z, plus some punctuation), numeric (K_0 through - K_9 unshifted and shifted, plus other non-alphanumerice keys). - - - template-latin: has both phone and tablet layouts (which are identical except for some key spacing, - each with 4 rows of keys), with four layers: default, shift, numeric, symbol. The first three layers - are similar to those of template-basic, with the addition of longpress keys for accented characters. - The symbol layer has many symbols not usually seen on a physical keyboard. - - - template-traditional: has both phone (4 rows of keys) and tablet (5 rows of keys) layouts. The phone - layout is similar to the template-basic layout. The tablet layout has eight layers: default, shift, ctrl, - shift-ctrl, alt, shift-alt, ctrl-alt, shift-ctrl-alt. The default layer has the keys from the default layer - of the desktop layout, the shift layer has the keys from the shift layer of the desktop layout, and so on. Import from On Screen : If you have an existing On-Screen Keyboard for your keyboard layout, @@ -468,7 +457,7 @@ dragged between the main keyboard area and the long press area. The bar below the long press area contains the corresponding controls for the long press keys. -![Keyboard Editor - Touch Layout tab, Code view](../images/ui/frmKeymanWizard_TouchLayout_Code.png) +Keyboard Editor - Touch Layout tab, Code view The Code view allows you to edit the JSON source of the touch layout file. This makes certain operations simpler, such as batched rearranging @@ -481,7 +470,7 @@ The format of the layout file is described in [Creating a touch keyboard layout Underneath the keyboard area is the Long-press, Flicks, and Multitaps controls. -![Long-press, Flicks, and Multitaps layout](../images/ui/LongPress_Flicks_Multitaps.png) +Long-press, Flicks, and Multitaps layout Red circle with an X : This button to the top right of the key will delete the key from the @@ -508,7 +497,7 @@ the selected key for different Gesture Type. The only difference between this co ## Icon tab -![Keyboard Editor - Icon tab](../images/ui/frmKeymanWizard_Icon.png) +Keyboard Editor - Icon tab The icon tab allows you to edit the icon associated with the keyboard. An external icon editor will allow you to create more sophisticated icons with alpha transparency and multiple resolutions, but this will cover the standard 16x16 256 colour icon, which is all that many keyboards require. The controls at the top left are: @@ -574,7 +563,7 @@ This tab allows you to edit the [named constants](/developer/language/guide/cons ## Build tab -![Keyboard Editor - Build tab](../images/ui/BuildTab.png) +Keyboard Editor - Build tab A set of common controls is across the top of this tab: @@ -645,4 +634,4 @@ web page. The Send to email function makes it easier to open the addresses on your mobile device without having to enter them manually. -Learn more about [testing on the web and mobile devices](../guides/test/). +Learn more about [testing on the web and mobile devices](../guides/test/). \ No newline at end of file diff --git a/developer/docs/help/images/ui/OnScreenKeyboard_ISO.png b/developer/docs/help/images/ui/OnScreenKeyboard_ISO.png new file mode 100644 index 0000000000000000000000000000000000000000..64e3f6ef7e20fa61275a8fe27f7ca56aab155b49 GIT binary patch literal 13169 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 C-cS+% literal 0 HcmV?d00001 From f324431754374be5aa8103668e505be30407d4db Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 1 Jul 2025 16:21:21 +0200 Subject: [PATCH 075/262] fix(web): change env variable name The build agents have the env variable `SEVENZ_HOME` set instead of `SEVEN_Z_HOME`. That name is also used elsewhere, so this PR changes it to the more widespread name. Fixes: #14242 Follow-up-of: #14225 Build-bot: skip Test-bot: skip --- web/ci.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/ci.sh b/web/ci.sh index 4291e5c71a..f6a92b943b 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -154,14 +154,14 @@ function prepare_downloads_keyman_com_action() { mkdir -p "${UPLOAD_PATH}" - # On Windows, we use 7-zip (SEVEN_Z_HOME env var). On other platforms, we use zip. + # On Windows, we use 7-zip (SEVENZ_HOME env var). On other platforms, we use zip. COMPRESS_CMD= COMPRESS_ADD= - # Marc's preference; use $SEVEN_Z_HOME and have the BAs set up with THAT as an env var. - if [[ ! -z "${SEVEN_Z_HOME+x}" ]]; then - COMPRESS_CMD="${SEVEN_Z_HOME}/7z" + # Marc's preference; use $SEVENZ_HOME and have the BAs set up with THAT as an env var. + if [[ ! -z "${SEVENZ_HOME+x}" ]]; then + COMPRESS_CMD="${SEVENZ_HOME}/7z" COMPRESS_ADD="a -bd -bb0 -r" # add, hide progress, log level 0, recursive fi From 20896e14f49a7cafbe35b837aa4167709066eb3a Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Tue, 1 Jul 2025 14:01:38 -0400 Subject: [PATCH 076/262] auto: increment master version to 19.0.74 --- HISTORY.md | 5 +++++ VERSION.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 1254bcbe79..f82e1b05f9 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # Keyman Version History +## 19.0.73 alpha 2025-07-01 + +* fix(web): deploy `web/build/publish` folder (#14237) +* fix(web): change env variable name (#14244) + ## 19.0.72 alpha 2025-07-01 * maint(web): cleanup rsync call from bash for Windows agents and refactor (#14236) diff --git a/VERSION.md b/VERSION.md index c571ed6caf..f9c16c3f13 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.73 \ No newline at end of file +19.0.74 \ No newline at end of file From 3011a0816d95037584d77d3208e9eb4d6f051c83 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 2 Jul 2025 08:12:35 +1000 Subject: [PATCH 077/262] maint(web): adjust rsync path --- resources/teamcity/web/keyman-web-release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/teamcity/web/keyman-web-release.sh b/resources/teamcity/web/keyman-web-release.sh index bd930fd0c9..d0089e2d1f 100755 --- a/resources/teamcity/web/keyman-web-release.sh +++ b/resources/teamcity/web/keyman-web-release.sh @@ -66,7 +66,7 @@ function _zip_and_upload_artifacts() { '-vrzltp' # verbose, recurse, zip, copy symlinks, preserve times, permissions '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r' # map Windows security to host security '--stats' # show statistics for log - "--rsync-path=\"${RSYNC_PATH}\"" # path on remote server + "--rsync-path=${RSYNC_PATH}" # path on remote server "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts" # use ssh "${KEYMAN_VERSION}" # upload the whole build folder "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/web/${KEYMAN_TIER}/" # target server + path From c5dc3e583d51030a3dc5d3aa4f9096b4c3a5bdbe Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 2 Jul 2025 08:18:24 +1000 Subject: [PATCH 078/262] chore: remove unnecessary comment --- resources/teamcity/linux/keyman-linux-release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/teamcity/linux/keyman-linux-release.sh b/resources/teamcity/linux/keyman-linux-release.sh index 14457a54fb..4abf705a22 100755 --- a/resources/teamcity/linux/keyman-linux-release.sh +++ b/resources/teamcity/linux/keyman-linux-release.sh @@ -129,7 +129,7 @@ function _publish_to_downloads() { # "-vrzltp " # verbose, recurse, zip, copy symlinks, preserve times, permissions # "--perms " # perfectly matches existing file permissions on the build agent # "--stats " # show statistics for log - # "--rsync-path=\"sudo -u vu2009 rsync\" # path on remote server + # "--rsync-path= # path on remote server # "--rsh=ssh " # use ssh # The actual rsync call. From feed26832d258d00fb2add4428398544ba15121c Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Tue, 1 Jul 2025 18:22:10 -0400 Subject: [PATCH 079/262] auto: increment master version to 19.0.75 --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index f82e1b05f9..a59bc803dd 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 19.0.74 alpha 2025-07-01 + +* maint(web): adjust rsync path (#14249) + ## 19.0.73 alpha 2025-07-01 * fix(web): deploy `web/build/publish` folder (#14237) diff --git a/VERSION.md b/VERSION.md index f9c16c3f13..02157961f3 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.74 \ No newline at end of file +19.0.75 \ No newline at end of file From 307ae207905b8a558dbf915717c9b4814a37802f Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 2 Jul 2025 10:29:21 +0200 Subject: [PATCH 080/262] refactor(web): move helper functions to beginning of function The previous code showed a warning in the Firefox console: "Unreachable code after return statement". Moving the helper functions to the beginning of the surrounding function solves it. Also make some variables `const`. --- .../templates/src/trie-model.ts | 30 +++++++-------- .../wordbreakers/src/main/default/index.ts | 38 +++++++++---------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/web/src/engine/predictive-text/templates/src/trie-model.ts b/web/src/engine/predictive-text/templates/src/trie-model.ts index e33c47ce2b..66e276920c 100644 --- a/web/src/engine/predictive-text/templates/src/trie-model.ts +++ b/web/src/engine/predictive-text/templates/src/trie-model.ts @@ -303,6 +303,18 @@ export default class TrieModel implements LexicalModel { } predict(transform: Transform, context: Context): Distribution { + //#region Helper function + function makeDistribution(suggestions: WithOutcome[]): Distribution { + const distribution: Distribution = []; + + for (const s of suggestions) { + distribution.push({ sample: s, p: s.p }); + } + + return distribution; + } + //#endregion Helper function + // Special-case the empty buffer/transform: return the top suggestions. if (!transform.insert && !context.left && !context.right && context.startOfBuffer && context.endOfBuffer) { return makeDistribution(this._trie.firstN(MAX_SUGGESTIONS).map(({text, p}) => ({ @@ -316,14 +328,14 @@ export default class TrieModel implements LexicalModel { } // Compute the results of the keystroke: - let newContext = applyTransform(transform, context); + const newContext = applyTransform(transform, context); // Computes the different in word length after applying the transform above. - let leftDelOffset = transform.deleteLeft - KMWString.length(transform.insert); + const leftDelOffset = transform.deleteLeft - KMWString.length(transform.insert); // All text to the left of the cursor INCLUDING anything that has // just been typed. - let prefix = getLastPreCaretToken(this.breakWords, newContext); + const prefix = getLastPreCaretToken(this.breakWords, newContext); // Return suggestions from the trie. return makeDistribution(this._trie.lookup(prefix).map(({text, p}) => @@ -336,18 +348,6 @@ export default class TrieModel implements LexicalModel { }, p ))); - - /* Helper */ - - function makeDistribution(suggestions: WithOutcome[]): Distribution { - let distribution: Distribution = []; - - for(let s of suggestions) { - distribution.push({sample: s, p: s.p}); - } - - return distribution; - } } get wordbreaker(): WordBreakingFunction { diff --git a/web/src/engine/predictive-text/wordbreakers/src/main/default/index.ts b/web/src/engine/predictive-text/wordbreakers/src/main/default/index.ts index 390ba9806a..3036a2b23d 100644 --- a/web/src/engine/predictive-text/wordbreakers/src/main/default/index.ts +++ b/web/src/engine/predictive-text/wordbreakers/src/main/default/index.ts @@ -293,6 +293,24 @@ function isNonSpace(chunk: string, options?: DefaultWordBreakerOptions): boolean * @param text Text to find word boundaries in. */ function findBoundaries(text: string, options?: DefaultWordBreakerOptions): number[] { + //#region Internal utility functions + /** + * Returns the position of the start of the next scalar value. This jumps + * over surrogate pairs. + * + * If asked for the character AFTER the end of the string, this always + * returns the length of the string. + */ + function positionAfter(pos: number): number { + if (pos >= text.length) { + return text.length; + } else if (isStartOfSurrogatePair(text[pos])) { + return pos + 2; + } + return pos + 1; + } + //#endregion Internal utility functions + // WB1 and WB2: no boundaries if given an empty string. if (text.length === 0) { // There are no boundaries in an empty string! @@ -321,7 +339,7 @@ function findBoundaries(text: string, options?: DefaultWordBreakerOptions): numb // left, and some look at what's to the right of right. So // keep track of this! - let boundaries = []; + const boundaries = []; let rightPos: number; let lookaheadPos = 0; // lookahead, one scalar value to the right of right. @@ -526,24 +544,6 @@ function findBoundaries(text: string, options?: DefaultWordBreakerOptions): numb } while (rightPos < text.length); return boundaries; - - ///// Internal utility functions ///// - - /** - * Returns the position of the start of the next scalar value. This jumps - * over surrogate pairs. - * - * If asked for the character AFTER the end of the string, this always - * returns the length of the string. - */ - function positionAfter(pos: number): number { - if (pos >= text.length) { - return text.length; - } else if (isStartOfSurrogatePair(text[pos])) { - return pos + 2; - } - return pos + 1; - } } function isStartOfSurrogatePair(character: string) { From bde4db43ae74b7da264b8db494a35ce40d72e2fc Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 1 Jul 2025 18:32:38 +0200 Subject: [PATCH 081/262] refactor(common): improve downloads include script - add shebang so that shellcheck knows what shell we use - fix line endings --- .../teamcity/includes/tc-download-info.inc.sh | 104 +++++++++--------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/resources/teamcity/includes/tc-download-info.inc.sh b/resources/teamcity/includes/tc-download-info.inc.sh index 14b77dba9f..25755af877 100644 --- a/resources/teamcity/includes/tc-download-info.inc.sh +++ b/resources/teamcity/includes/tc-download-info.inc.sh @@ -1,50 +1,54 @@ -# Keyman is copyright (C) SIL Global. MIT License. - -. "${KEYMAN_ROOT}/resources/build/jq.inc.sh" - -# -# Write ${UPLOAD_DIR}/${ARTIFACT_FILENAME}.download_info file for the target -# artifact -# -# Parameters: -# 1: UPLOAD_DIR Directory where artifact can be found -# 2: ARTIFACT_FILENAME Filename (without path) of artifact -# 3: ARTIFACT_NAME Descriptive name of artifact -# 4: ARTIFACT_TYPE File extension of artifact, without initial period (e.g. tar.gz) -# 5: PLATFORM Target platform for artifact -# -write_download_info() { - # TODO: DRY out with Linux download_info writer, others - local UPLOAD_DIR="$1" - local ARTIFACT_FILENAME="$2" - local ARTFIACT_NAME="$3" - local ARTIFACT_TYPE="$4" - local PLATFORM="$5" - - local DATE HASH SIZE DOWNLOAD_INFO - - # Construct .download_info - DATE=$(date +%F) - HASH=$(md5sum "${UPLOAD_DIR}/${ARTIFACT_FILENAME}" | cut -d ' ' -f 1) - SIZE=$(stat --print="%s" "${UPLOAD_DIR}/${ARTIFACT_FILENAME}") - - DOWNLOAD_INFO=$( - "$JQ" -n \ - --arg NAME "${ARTFIACT_NAME}" \ - --arg BUILD_NUMBER "${KEYMAN_VERSION}" \ - --arg DATE "${DATE}" \ - --arg PLATFORM "${PLATFORM}" \ - --arg KEYMAN_TIER "${KEYMAN_TIER}" \ - --arg FILENAME "${ARTIFACT_FILENAME}" \ - --arg ARTIFACT_TYPE "${ARTIFACT_TYPE}" \ - --arg HASH "${HASH}" \ - --arg BUILD_COUNTER "${KEYMAN_VERSION_PATCH}" \ - --arg SIZE "${SIZE}" \ - '{ - name: $NAME, version: $BUILD_NUMBER, date: $DATE, platform: $PLATFORM, - stability: $KEYMAN_TIER, file: $FILENAME, md5: $HASH, type: $ARTIFACT_TYPE, - build: $BUILD_COUNTER, size: $SIZE - }' - ) - echo "${DOWNLOAD_INFO}" | "$JQ" . >> "${UPLOAD_DIR}/${ARTIFACT_FILENAME}.download_info" -} \ No newline at end of file +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. + +# shellcheck disable=SC2154 +. "${KEYMAN_ROOT}/resources/build/jq.inc.sh" + +# +# Write ${UPLOAD_DIR}/${ARTIFACT_FILENAME}.download_info file for the target +# artifact +# +# Parameters: +# 1: UPLOAD_DIR Directory where artifact can be found +# 2: ARTIFACT_FILENAME Filename (without path) of artifact +# 3: ARTIFACT_NAME Descriptive name of artifact +# 4: ARTIFACT_TYPE File extension of artifact, without initial period (e.g. tar.gz) +# 5: PLATFORM Target platform for artifact +# +write_download_info() { + # TODO: DRY out with Linux download_info writer, others + local UPLOAD_DIR="$1" + local ARTIFACT_FILENAME="$2" + local ARTFIACT_NAME="$3" + local ARTIFACT_TYPE="$4" + local PLATFORM="$5" + + local DATE HASH SIZE DOWNLOAD_INFO + + # Construct .download_info + DATE=$(date +%F) + # shellcheck disable=SC2312 + HASH=$(md5sum "${UPLOAD_DIR}/${ARTIFACT_FILENAME}" | cut -d ' ' -f 1) + SIZE=$(stat --print="%s" "${UPLOAD_DIR}/${ARTIFACT_FILENAME}") + + # shellcheck disable=SC2016 + DOWNLOAD_INFO=$( + "${JQ}" -n \ + --arg NAME "${ARTFIACT_NAME}" \ + --arg BUILD_NUMBER "${KEYMAN_VERSION}" \ + --arg DATE "${DATE}" \ + --arg PLATFORM "${PLATFORM}" \ + --arg KEYMAN_TIER "${KEYMAN_TIER}" \ + --arg FILENAME "${ARTIFACT_FILENAME}" \ + --arg ARTIFACT_TYPE "${ARTIFACT_TYPE}" \ + --arg HASH "${HASH}" \ + --arg BUILD_COUNTER "${KEYMAN_VERSION_PATCH}" \ + --arg SIZE "${SIZE}" \ + '{ + name: $NAME, version: $BUILD_NUMBER, date: $DATE, platform: $PLATFORM, + stability: $KEYMAN_TIER, file: $FILENAME, md5: $HASH, type: $ARTIFACT_TYPE, + build: $BUILD_COUNTER, size: $SIZE + }' + ) + echo "${DOWNLOAD_INFO}" | "${JQ}" . >> "${UPLOAD_DIR}/${ARTIFACT_FILENAME}.download_info" +} From 8ea81be6214c64634d71834e9e5e6d45f4deafd2 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 2 Jul 2025 11:51:25 +0200 Subject: [PATCH 082/262] maint(web): move rsync code to shared functions Build-bot: skip Test-bot: skip --- resources/teamcity/includes/tc-helpers.inc.sh | 39 +++++++++++++++++++ resources/teamcity/web/keyman-web-release.sh | 24 +++--------- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/resources/teamcity/includes/tc-helpers.inc.sh b/resources/teamcity/includes/tc-helpers.inc.sh index c2d2253010..8a56f7e595 100644 --- a/resources/teamcity/includes/tc-helpers.inc.sh +++ b/resources/teamcity/includes/tc-helpers.inc.sh @@ -66,3 +66,42 @@ set_variables_for_emscripten() { export EMSCRIPTEN_BASE="${EMSCRIPTEN_BASE:-${HOME}/emsdk/upstream/emscripten}" export KEYMAN_USE_EMSDK=1 } + +_tc_rsync() { + local rsync_args CYGPATH_RSYNC_HOME SOURCE DESTINATION + SOURCE=$1 + DESTINATION=$2 + + if ! is_windows; then + echo "This function is currently only supported on Windows." + return 1 + fi + + rsync_args=( + '-vrzltp' # verbose, recurse, zip, copy symlinks, preserve times, permissions + '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r' # map Windows security to host security + '--stats' # show statistics for log + "--rsync-path=${RSYNC_PATH}" # path on remote server + "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts" # use ssh + "${SOURCE}" + "${DESTINATION}" + ) + + CYGPATH_RSYNC_HOME=$(cygpath -w "${RSYNC_HOME}") + MSYS_NO_PATHCONV=1 "${CYGPATH_RSYNC_HOME}\\rsync.exe" "${rsync_args[@]}" +} + +# Download file or directory $1 from the remote server to $2. +tc_rsync_download() { + # shellcheck disable=SC2154 + _tc_rsync \ + "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/$1" \ + "$2" +} + +# Upload file or directory $1 to the remote server at $2. +tc_rsync_upload() { + _tc_rsync \ + "$1" \ + "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/$2" +} diff --git a/resources/teamcity/web/keyman-web-release.sh b/resources/teamcity/web/keyman-web-release.sh index d0089e2d1f..7d44d2d9de 100755 --- a/resources/teamcity/web/keyman-web-release.sh +++ b/resources/teamcity/web/keyman-web-release.sh @@ -56,31 +56,17 @@ function _zip_and_upload_artifacts() { cd "${KEYMAN_ROOT}/web" - local UPLOAD_DIR="build/upload/$KEYMAN_VERSION" + local UPLOAD_DIR="build/upload/${KEYMAN_VERSION}" ./ci.sh prepare:downloads.keyman.com - write_download_info "$UPLOAD_DIR" "keymanweb-$KEYMAN_VERSION.zip" KeymanWeb zip web + write_download_info "${UPLOAD_DIR}" "keymanweb-${KEYMAN_VERSION}.zip" KeymanWeb zip web - rsync_args=( - '-vrzltp' # verbose, recurse, zip, copy symlinks, preserve times, permissions - '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r' # map Windows security to host security - '--stats' # show statistics for log - "--rsync-path=${RSYNC_PATH}" # path on remote server - "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts" # use ssh - "${KEYMAN_VERSION}" # upload the whole build folder - "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/web/${KEYMAN_TIER}/" # target server + path + ( + cd build/upload + tc_rsync_upload "${KEYMAN_VERSION}" "web/${KEYMAN_TIER}" ) - cd build/upload - - local CYGPATH_RSYNC_HOME=$(cygpath -w "$RSYNC_HOME") - # DEBUG: echo "${rsync_args[@]}" - # DEBUG: MSYS_NO_PATHCONV=1 "C:\\Users\\marc\\source\\repos\\doecho\\Debug\\doecho.exe" "${CYGPATH_RSYNC_HOME}\\rsync.exe" "${rsync_args[@]}" - MSYS_NO_PATHCONV=1 "${CYGPATH_RSYNC_HOME}\\rsync.exe" "${rsync_args[@]}" - - cd "${KEYMAN_ROOT}/web" - builder_echo end "zip and upload artifacts" success "Finished zipping and uploading artifacts" } From 52943600dcbfd9cc828ad790cbd563e1b2c784cf Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 2 Jul 2025 11:51:25 +0200 Subject: [PATCH 083/262] maint(web): move rsync code to shared functions Build-bot: skip Test-bot: skip --- resources/teamcity/includes/tc-helpers.inc.sh | 39 +++++++++++++++++++ resources/teamcity/web/keyman-web-release.sh | 24 +++--------- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/resources/teamcity/includes/tc-helpers.inc.sh b/resources/teamcity/includes/tc-helpers.inc.sh index 04ab2bcc3c..69305ad9ab 100644 --- a/resources/teamcity/includes/tc-helpers.inc.sh +++ b/resources/teamcity/includes/tc-helpers.inc.sh @@ -80,3 +80,42 @@ upload_help() { builder_echo end "upload help" success "Finished uploading new ${PRODUCT} help to help.keyman.com" } + +_tc_rsync() { + local rsync_args CYGPATH_RSYNC_HOME SOURCE DESTINATION + SOURCE=$1 + DESTINATION=$2 + + if ! is_windows; then + echo "This function is currently only supported on Windows." + return 1 + fi + + rsync_args=( + '-vrzltp' # verbose, recurse, zip, copy symlinks, preserve times, permissions + '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r' # map Windows security to host security + '--stats' # show statistics for log + "--rsync-path=${RSYNC_PATH}" # path on remote server + "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts" # use ssh + "${SOURCE}" + "${DESTINATION}" + ) + + CYGPATH_RSYNC_HOME=$(cygpath -w "${RSYNC_HOME}") + MSYS_NO_PATHCONV=1 "${CYGPATH_RSYNC_HOME}\\rsync.exe" "${rsync_args[@]}" +} + +# Download file or directory $1 from the remote server to $2. +tc_rsync_download() { + # shellcheck disable=SC2154 + _tc_rsync \ + "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/$1" \ + "$2" +} + +# Upload file or directory $1 to the remote server at $2. +tc_rsync_upload() { + _tc_rsync \ + "$1" \ + "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/$2" +} diff --git a/resources/teamcity/web/keyman-web-release.sh b/resources/teamcity/web/keyman-web-release.sh index d3bf7a2bed..a79dff946b 100755 --- a/resources/teamcity/web/keyman-web-release.sh +++ b/resources/teamcity/web/keyman-web-release.sh @@ -56,31 +56,17 @@ function _zip_and_upload_artifacts() { cd "${KEYMAN_ROOT}/web" - local UPLOAD_DIR="build/upload/$KEYMAN_VERSION" + local UPLOAD_DIR="build/upload/${KEYMAN_VERSION}" ./ci.sh prepare:downloads.keyman.com - write_download_info "$UPLOAD_DIR" "keymanweb-$KEYMAN_VERSION.zip" KeymanWeb zip web + write_download_info "${UPLOAD_DIR}" "keymanweb-${KEYMAN_VERSION}.zip" KeymanWeb zip web - rsync_args=( - '-vrzltp' # verbose, recurse, zip, copy symlinks, preserve times, permissions - '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r' # map Windows security to host security - '--stats' # show statistics for log - "--rsync-path=${RSYNC_PATH}" # path on remote server - "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts" # use ssh - "${KEYMAN_VERSION}" # upload the whole build folder - "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/web/${KEYMAN_TIER}/" # target server + path + ( + cd build/upload + tc_rsync_upload "${KEYMAN_VERSION}" "web/${KEYMAN_TIER}" ) - cd build/upload - - local CYGPATH_RSYNC_HOME=$(cygpath -w "$RSYNC_HOME") - # DEBUG: echo "${rsync_args[@]}" - # DEBUG: MSYS_NO_PATHCONV=1 "C:\\Users\\marc\\source\\repos\\doecho\\Debug\\doecho.exe" "${CYGPATH_RSYNC_HOME}\\rsync.exe" "${rsync_args[@]}" - MSYS_NO_PATHCONV=1 "${CYGPATH_RSYNC_HOME}\\rsync.exe" "${rsync_args[@]}" - - cd "${KEYMAN_ROOT}/web" - builder_echo end "zip and upload artifacts" success "Finished zipping and uploading artifacts" } From 8bb0be2f8f426f6829d468f45ebe74ba363426dc Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 2 Jul 2025 16:49:54 +0200 Subject: [PATCH 084/262] maint(windows): replace Powershell scripts Refactors the Powershell scripts that download and upload files and create .download_info files. This change is based on #14236/#14254 which bypasses the path rewriting that msys does when calling Windows rsync. Part of this change was done for developer in #14255. Related: #14236, #14255 --- .../includes/download-symbol-server-index.ps1 | 63 ---- .../teamcity/includes/publish-new-symbols.ps1 | 39 --- resources/teamcity/includes/tc-windows.inc.sh | 21 +- .../windows/keyman-windows-release.sh | 50 +++- ...ublish-windows-to-downloads-keyman-com.ps1 | 279 ------------------ 5 files changed, 57 insertions(+), 395 deletions(-) delete mode 100644 resources/teamcity/includes/download-symbol-server-index.ps1 delete mode 100644 resources/teamcity/includes/publish-new-symbols.ps1 delete mode 100644 resources/teamcity/windows/publish-windows-to-downloads-keyman-com.ps1 diff --git a/resources/teamcity/includes/download-symbol-server-index.ps1 b/resources/teamcity/includes/download-symbol-server-index.ps1 deleted file mode 100644 index 5e84be2c05..0000000000 --- a/resources/teamcity/includes/download-symbol-server-index.ps1 +++ /dev/null @@ -1,63 +0,0 @@ -# Keyman is copyright (C) SIL Global. MIT License. -# -# This script should be the identical for nightly/beta/stable for a given platform -# - -$ErrorActionPreference = "Stop" - -$RSYNC_HOME = $env:RSYNC_HOME -$RSYNC_PATH = $env:RSYNC_PATH -$RSYNC_USER = $env:RSYNC_USER -$RSYNC_HOST = $env:RSYNC_HOST -$RSYNC_ROOT = $env:RSYNC_ROOT -$USERPROFILE = $env:USERPROFILE - -New-Item -Force -ItemType Directory symbols\000admin -cd symbols\000admin - -# -# Download the symbol server index files with rsync from downloads.keyman.com -# (rsync requires that we are in the symbols folder to get folders in -# sync correctly; it is possible to resolve this but easier to just cd.) -# - -$rsync_args = @( - '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions - '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security - '--stats', # show statistics for log - "--rsync-path=""${RSYNC_PATH}""", # path on remote server - "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh - "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/symbols/000admin/lastid.txt", # target server + path - "." # download the whole symbols 000Admin folder -) - -& ${RSYNC_HOME}\rsync.exe ${rsync_args} -if (${LASTEXITCODE} -ne 0) { throw "Exit code is ${LASTEXITCODE}" } - -$rsync_args = @( - '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions - '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security - '--stats', # show statistics for log - "--rsync-path=""${RSYNC_PATH}""", # path on remote server - "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh - "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/symbols/000admin/history.txt", # target server + path - "." # download the whole symbols 000Admin folder -) - -& ${RSYNC_HOME}\rsync.exe ${rsync_args} -if (${LASTEXITCODE} -ne 0) { throw "Exit code is ${LASTEXITCODE}" } - -$rsync_args = @( - '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions - '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security - '--stats', # show statistics for log - "--rsync-path=""${RSYNC_PATH}""", # path on remote server - "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh - "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/symbols/000admin/server.txt", # target server + path - "." # download the whole symbols 000Admin folder -) - -& ${RSYNC_HOME}\rsync.exe ${rsync_args} -if (${LASTEXITCODE} -ne 0) { throw "Exit code is ${LASTEXITCODE}" } - -# EOF diff --git a/resources/teamcity/includes/publish-new-symbols.ps1 b/resources/teamcity/includes/publish-new-symbols.ps1 deleted file mode 100644 index 7fab9e1280..0000000000 --- a/resources/teamcity/includes/publish-new-symbols.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -# Keyman is copyright (C) SIL Global. MIT License. -# -# This script should be the identical for nightly/beta/stable for a given platform -# - -$ErrorActionPreference = "Stop" - -$RSYNC_HOME = $env:RSYNC_HOME -$RSYNC_PATH = $env:RSYNC_PATH -$RSYNC_USER = $env:RSYNC_USER -$RSYNC_HOST = $env:RSYNC_HOST -$RSYNC_ROOT = $env:RSYNC_ROOT -$USERPROFILE = $env:USERPROFILE - -# Rename 000Admin to 000admin - -ren 000Admin 000admin_ -ren 000admin_ 000admin - -# -# Upload with rsync to downloads.keyman.com -# (rsync requires that we are in the symbols folder to get folders in -# sync correctly; it is possible to resolve this but easier to just cd.) -# - -$rsync_args = @( - '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions - '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security - '--stats', # show statistics for log - "--rsync-path=""${RSYNC_PATH}""", # path on remote server - "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh - ".", # upload the whole symbols folder - "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/symbols/" # target server + path -) - -& ${RSYNC_HOME}\rsync.exe ${rsync_args} -if (${LASTEXITCODE} -ne 0) { throw "Exit code is ${LASTEXITCODE}" } - -# EOF diff --git a/resources/teamcity/includes/tc-windows.inc.sh b/resources/teamcity/includes/tc-windows.inc.sh index 689a068327..b8d840a61b 100644 --- a/resources/teamcity/includes/tc-windows.inc.sh +++ b/resources/teamcity/includes/tc-windows.inc.sh @@ -4,26 +4,27 @@ download_symbol_server_index() { # Download symbol server index from symbol server builder_echo start "download symbol server index" "Downloading symbol server index" - ( - # shellcheck disable=SC2164 - cd "${KEYMAN_ROOT}/.." # shellcheck disable=SC2154 - powershell -NonInteractive -ExecutionPolicy Bypass -File "${KEYMAN_ROOT}/resources/teamcity/includes/download-symbol-server-index.ps1" - ) + mkdir -p "${LOCAL_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}" + # shellcheck disable=SC2164 + cd "${LOCAL_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}" + # shellcheck disable=SC2154 + tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/lastid.txt" "." + tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/history.txt" "." + tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/server.txt" "." + ) builder_echo end "download symbol server index" success "Finished downloading symbol server index" } publish_new_symbols() { # Publish new symbols to symbol server builder_echo start "publish new symbols" "Publishing new symbols to symbol server" - ( - cd "${KEYMAN_ROOT}/../symbols" - # shellcheck disable=SC2154 - powershell -NonInteractive -ExecutionPolicy Bypass -File "${KEYMAN_ROOT}/resources/teamcity/includes/publish-new-symbols.ps1" + # shellcheck disable=SC2164 + cd "${LOCAL_SYMBOLS_PATH}" + tc_rsync_upload "." "${REMOTE_SYMBOLS_PATH}" ) - builder_echo end "publish new symbols" success "Finished publishing new symbols to symbol server" } diff --git a/resources/teamcity/windows/keyman-windows-release.sh b/resources/teamcity/windows/keyman-windows-release.sh index 0a92044a6f..4a6f531064 100755 --- a/resources/teamcity/windows/keyman-windows-release.sh +++ b/resources/teamcity/windows/keyman-windows-release.sh @@ -14,6 +14,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" # shellcheck disable=SC2154 . "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-download-info.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-windows.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/windows/windows-actions.inc.sh" @@ -41,16 +42,57 @@ if ! is_windows; then exit 1 fi +# TODO: move to parameters (#14202) +LOCAL_SYMBOLS_PATH="${KEYMAN_ROOT}/../symbols" +REMOTE_SYMBOLS_PATH="windows/symbols" +SYMBOLS_SUBDIR="000admin" + function _publish_to_downloads_keyman_com() { # Publish to downloads.keyman.com builder_echo start "publish to downloads.keyman.com" "Publishing release to downloads.keyman.com" - ( cd "${KEYMAN_ROOT}/windows" - # shellcheck disable=SC2154 - powershell -NonInteractive -ExecutionPolicy Bypass -File "${THIS_SCRIPT_PATH}/publish-windows-to-downloads-keyman-com.ps1" - ) + local UPLOAD_PATH KEYMAN_EXE KEYMAN_DESKTOP_MSI SETUP_EXE SETUP_REDIST_ZIP DEBUG_ZIP + local FIRSTVOICES_MSI FIRSTVOICES_EXE + + # shellcheck disable=SC2154 + UPLOAD_PATH="upload/${KEYMAN_VERSION}" + KEYMAN_EXE="keyman-${KEYMAN_VERSION}.exe" + KEYMAN_DESKTOP_MSI="keymandesktop.msi" + SETUP_EXE="setup.exe" + SETUP_REDIST_ZIP="setup-redist.zip" + FIRSTVOICES_MSI="firstvoices.msi" + FIRSTVOICES_EXE="firstvoices-${KEYMAN_VERSION}.exe" + DEBUG_ZIP="debug-${KEYMAN_VERSION}.zip" + + rm -rf "${UPLOAD_PATH}" + mkdir -p "${UPLOAD_PATH}" + + cp "release/${KEYMAN_VERSION}/${KEYMAN_EXE}" "${UPLOAD_PATH}" + cp "release/${KEYMAN_VERSION}/${KEYMAN_DESKTOP_MSI}" "${UPLOAD_PATH}" + cp "release/${KEYMAN_VERSION}/${SETUP_EXE}" "${UPLOAD_PATH}" + cp "release/${KEYMAN_VERSION}/${SETUP_REDIST_ZIP}" "${UPLOAD_PATH}" + cp "release/${KEYMAN_VERSION}/${FIRSTVOICES_EXE}" "${UPLOAD_PATH}" + cp "release/${KEYMAN_VERSION}/${FIRSTVOICES_MSI}" "${UPLOAD_PATH}" + + write_download_info "${UPLOAD_PATH}" "${KEYMAN_EXE}" "Keyman for Windows" exe win + write_download_info "${UPLOAD_PATH}" "${KEYMAN_DESKTOP_MSI}" "Keyman for Windows MSI installer" msi win + write_download_info "${UPLOAD_PATH}" "${SETUP_EXE}" "Keyman for Windows setup bootstrap" exe win + write_download_info "${UPLOAD_PATH}" "${SETUP_REDIST_ZIP}" "Keyman for Windows setup bootstrap (unsigned for bundling)" zip win + + write_download_info "${UPLOAD_PATH}" "${FIRSTVOICES_EXE}" "FirstVoices Keyboards" exe win + write_download_info "${UPLOAD_PATH}" "${FIRSTVOICES_MSI}" "FirstVoices Keyboards MSI installer" msi win + + if [[ -f "release/${KEYMAN_VERSION}/${DEBUG_ZIP}" ]]; then + cp "release/${KEYMAN_VERSION}/${DEBUG_ZIP}" "${UPLOAD_PATH}" + write_download_info "${UPLOAD_PATH}" "${DEBUG_ZIP}" "Keyman Desktop and Keyman Developer debug files" zip win + fi + + cd upload + # shellcheck disable=SC2154 + tc_rsync_upload "${KEYMAN_VERSION}" "windows/${KEYMAN_TIER}" + ) builder_echo end "publish to downloads.keyman.com" success "Finished publishing release to downloads.keyman.com" } diff --git a/resources/teamcity/windows/publish-windows-to-downloads-keyman-com.ps1 b/resources/teamcity/windows/publish-windows-to-downloads-keyman-com.ps1 deleted file mode 100644 index 3703557b7a..0000000000 --- a/resources/teamcity/windows/publish-windows-to-downloads-keyman-com.ps1 +++ /dev/null @@ -1,279 +0,0 @@ -# Keyman is copyright (C) SIL Global. MIT License. - -$ErrorActionPreference = "Stop" - -$tier = Get-Content ..\TIER.md -$build_number = Get-Content ..\VERSION.md -$build_counter = ${build_number} -replace "^\d+\.\d+\.(\d+)$", '$1' -$msi_major_version = ${build_number} -replace "^(\d+)\.(\d+)\.(\d+)$", '$1$2' - -if ( ${tier} -eq "alpha" -or ${tier} -eq "beta") { - # $version_tag = ${tier} - $version_with_tag = ${build_number} # we may go with ${build_number} + "-" + $version_tag in the future one day -} -else { - $version_with_tag = ${build_number} -} - -$upload_path = "upload\${build_number}" - -# Keyman Desktop installer -$keyman_desktop_exe = "keyman-${version_with_tag}.exe" - -# Files used for building bundled keyboard + Keyman installers (placed in Desktop folder) -if (${msi_major_version} -eq "100") { - $keyman_desktop_msi = "keymandesktop${msi_major_version}.msi" -} -else { - $keyman_desktop_msi = "keymandesktop.msi" -} -$setup_exe = "setup.exe" -$setup_redist_zip = "setup-redist.zip" - -$firstvoices_msi = "firstvoices.msi" -$firstvoices_exe = "firstvoices-${version_with_tag}.exe" - -# Debug files -$debug_zip = "debug-${build_number}.zip" - -$7Z_HOME = $env:7Z_HOME -$RSYNC_HOME = $env:RSYNC_HOME -$RSYNC_PATH = $env:RSYNC_PATH -$RSYNC_USER = $env:RSYNC_USER -$RSYNC_HOST = $env:RSYNC_HOST -$RSYNC_ROOT = $env:RSYNC_ROOT -$USERPROFILE = $env:USERPROFILE - -########################################################################################### -## Desktop upload -########################################################################################### - -# -# Preparation -# - -if ((Test-Path ${upload_path}) -ne 0) { - Remove-Item -Path ${upload_path} -Recurse -} - -mkdir ${upload_path} - -# -# Copy source files -# - -copy release\${build_number}\${keyman_desktop_exe} ${upload_path}\${keyman_desktop_exe} -copy release\${build_number}\${keyman_desktop_msi} ${upload_path}\${keyman_desktop_msi} -copy release\${build_number}\${setup_exe} ${upload_path}\${setup_exe} -if ((Test-Path release\${build_number}\${setup_redist_zip}) -ne 0) { - # Keyman 16+ setup-redist.zip is an archive of the setup.exe installer for - # Keyman for Windows, unsigned for use in bundling scenarios. - copy release\${build_number}\${setup_redist_zip} ${upload_path}\${setup_redist_zip} -} -# -# Construct keyman_desktop.download_info -# - -$hash = get-filehash ${upload_path}\${keyman_desktop_exe} -Algorithm MD5 - -$keyman_desktop_download_info = @" -{ - "name": "Keyman for Windows", - "version": "${version_with_tag}", - "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", - "platform": "win", - "stability": "${tier}", - "file": "keyman-${version_with_tag}.exe", - "md5": "$(${hash}.Hash)", - "type": "exe", - "build": "${build_counter}" -} -"@ - -[System.IO.File]::WriteAllLines("$pwd\${upload_path}\${keyman_desktop_exe}.download_info", ${keyman_desktop_download_info}) - -# -# Construct keyman_desktop_msi.download_info -# - -$hash = get-filehash ${upload_path}\${keyman_desktop_msi} -Algorithm MD5 - -$keyman_desktop_msi_download_info = @" -{ - "name": "Keyman for Windows MSI installer", - "version": "${version_with_tag}", - "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", - "platform": "win", - "stability": "${tier}", - "file": "${keyman_desktop_msi}", - "md5": "$(${hash}.Hash)", - "type": "msi", - "build": "${build_counter}" -} -"@ - -[System.IO.File]::WriteAllLines("$pwd\${upload_path}\${keyman_desktop_msi}.download_info", ${keyman_desktop_msi_download_info}) - -# -# Construct setup.exe.download_info -# - -$hash = get-filehash ${upload_path}\${setup_exe} -Algorithm MD5 - -$setup_exe_download_info = @" -{ - "name": "Keyman for Windows setup bootstrap", - "version": "${version_with_tag}", - "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", - "platform": "win", - "stability": "${tier}", - "file": "${setup_exe}", - "md5": "$(${hash}.Hash)", - "type": "exe", - "build": "${build_counter}" -} -"@ - -[System.IO.File]::WriteAllLines("$pwd\${upload_path}\${setup_exe}.download_info", ${setup_exe_download_info}) - -if ((Test-Path release\${build_number}\${setup_redist_zip}) -ne 0) { - - # - # Construct setup-redist.zip.download_info - # - - $hash = get-filehash ${upload_path}\${setup_redist_zip} -Algorithm MD5 - - $setup_redist_zip_download_info = @" -{ - "name": "Keyman for Windows setup bootstrap (unsigned for bundling)", - "version": "${version_with_tag}", - "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", - "platform": "win", - "stability": "${tier}", - "file": "${setup_redist_zip}", - "md5": "$(${hash}.Hash)", - "type": "zip", - "build": "${build_counter}" -} -"@ - - [System.IO.File]::WriteAllLines("$pwd\${upload_path}\${setup_redist_zip}.download_info", ${setup_redist_zip_download_info}) -} - -########################################################################################### -## FirstVoices upload -########################################################################################### - -# -# Copy source files -# - -copy release\${build_number}\${firstvoices_exe} ${upload_path}\${firstvoices_exe} -copy release\${build_number}\${firstvoices_msi} ${upload_path}\${firstvoices_msi} - -# -# Construct firstvoices.download_info -# - -$hash = get-filehash ${upload_path}\${firstvoices_exe} -Algorithm MD5 - -$firstvoices_download_info = @" -{ - "name": "FirstVoices Keyboards", - "version": "${version_with_tag}", - "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", - "platform": "win", - "stability": "${tier}", - "file": "${firstvoices_exe}", - "md5": "$(${hash}.Hash)", - "type": "exe", - "build": "${build_counter}" -} -"@ - -[System.IO.File]::WriteAllLines("$pwd\${upload_path}\${firstvoices_exe}.download_info", ${firstvoices_download_info}) - -# -# Construct firstvoices_msi.download_info -# - -$hash = get-filehash ${upload_path}\${firstvoices_msi} -Algorithm MD5 - -$firstvoices_msi_download_info = @" -{ - "name": "FirstVoices Keyboards MSI installer", - "version": "${version_with_tag}", - "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", - "platform": "win", - "stability": "${tier}", - "file": "${firstvoices_msi}", - "md5": "$(${hash}.Hash)", - "type": "msi", - "build": "${build_counter}" -} -"@ - -[System.IO.File]::WriteAllLines("$pwd\${upload_path}\${firstvoices_msi}.download_info", ${firstvoices_msi_download_info}) - -################# -# DEBUG.ZIP -################# - -if ((Test-Path release\${build_number}\${debug_zip}) -ne 0) { - # - # Copy source files - # - - copy release\${build_number}\${debug_zip} ${upload_path}\${debug_zip} - - # - # Construct debug-.download_info - # - - $hash = get-filehash ${upload_path}\${debug_zip} -Algorithm MD5 - - $debug_zip_download_info = @" -{ - "name": "Keyman Desktop and Keyman Developer debug files", - "version": "${version_with_tag}", - "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", - "platform": "win", - "stability": "${tier}", - "file": "debug-${build_number}.zip", - "md5": "$(${hash}.Hash)", - "type": "zip", - "build": "${build_counter}" -} -"@ - - [System.IO.File]::WriteAllLines("$pwd\${upload_path}\${debug_zip}.download_info", ${debug_zip_download_info}) -} - -# -# Upload with rsync to downloads.keyman.com -# (rsync requires that we are in the upload folder to get folders in -# sync correctly; it is possible to resolve this but easier to just cd.) -# - -$rsync_args = @( - '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions - '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security - '--stats', # show statistics for log - "--rsync-path='${RSYNC_PATH}'", # path on remote server - "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh - "${build_number}", # upload the whole build folder - "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/${tier}/" # target server + path -) - -# Write-Output "rsync parameters:" ${rsync_args} - -cd upload -& ${RSYNC_HOME}\rsync.exe ${rsync_args} -if ( ${LASTEXITCODE} -ne 0 ) { - Write-Output "rsync failed." - exit 99 -} -cd .. - -# EOF From fe2024a13f84426de1e1e303b6bdd23f23fc579e Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 24 Jun 2025 17:24:38 +0200 Subject: [PATCH 085/262] maint(android): add TC build scripts for Keyman for Android There is one difference compared to the old build configurations on TC: previously we had a separate build step to build and test FV that would run only if environment variables were set. This change combines building and testing FV with building the engine and the app. If the environment variables are set we pass an additional parameter to the TC build script. Fixes: #14188 Part-of: #13399 Build-bot: release android Test-bot: skip --- .../teamcity/android/android-actions.inc.sh | 15 ++ .../android/keyman-android-release.sh | 128 ++++++++++++++++++ .../android/keyman-android-test-samples.sh | 68 ++++++++++ .../teamcity/android/keyman-android-test.sh | 45 ++++++ resources/teamcity/includes/tc-helpers.inc.sh | 2 +- 5 files changed, 257 insertions(+), 1 deletion(-) create mode 100755 resources/teamcity/android/android-actions.inc.sh create mode 100755 resources/teamcity/android/keyman-android-release.sh create mode 100755 resources/teamcity/android/keyman-android-test-samples.sh create mode 100755 resources/teamcity/android/keyman-android-test.sh diff --git a/resources/teamcity/android/android-actions.inc.sh b/resources/teamcity/android/android-actions.inc.sh new file mode 100755 index 0000000000..630c12e239 --- /dev/null +++ b/resources/teamcity/android/android-actions.inc.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. + +android_clean_action() { + builder_echo start "clean" "Cleaning artifact directories" + # shellcheck disable=SC2154 + "${KEYMAN_ROOT}/android/build.sh" clean + builder_echo end "clean" success "Finished cleaning artifact directories" +} + +android_build_action() { + builder_echo start "build" "Building Keyman for Android" + "${KEYMAN_ROOT}/android/build.sh" configure build,test:${TARGETS} --debug --ci + builder_echo end "build" success "Finished building Keyman for Android" +} diff --git a/resources/teamcity/android/keyman-android-release.sh b/resources/teamcity/android/keyman-android-release.sh new file mode 100755 index 0000000000..54035bd0ff --- /dev/null +++ b/resources/teamcity/android/keyman-android-release.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. +# +# TC build script to build release of Keyman for Android. + +# shellcheck disable=SC2164 +# shellcheck disable=SC1091 + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +# shellcheck disable=SC2154 +. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-download-info.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/android/android-actions.inc.sh" + +################################ Main script ################################ + +builder_describe \ + "Build release of Keyman for Android" \ + "all run all actions" \ + "build configure and build Keyman for Android" \ + "publish publish release" \ + "--fv additionally build FV Android" \ + "--rsync-path=RSYNC_PATH rsync path on remote server" \ + "--rsync-user=RSYNC_USER rsync user on remote server" \ + "--rsync-host=RSYNC_HOST rsync host on remote server" \ + "--rsync-root=RSYNC_ROOT rsync root on remote server" \ + "--help.keyman.com=HELP_KEYMAN_COM path to help.keyman.com repository" + +builder_parse "$@" + +cd "${KEYMAN_ROOT}/android" + +function _publish_to_downloads_keyman_com() { + # Publish to downloads.keyman.com + builder_echo start "publish to downloads.keyman.com" "Publishing release to downloads.keyman.com" + + local UPLOAD_PATH KEYMAN_ENGINE_ANDROID_ZIP KEYMAN_APK FIRSTVOICES_APK + local COMPRESS_CMD + + # shellcheck disable=SC2154 + UPLOAD_PATH="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" + + # shellcheck disable=SC2154 + COMPRESS_CMD="${SEVENZ_HOME}/7z" + + rm -rf "${UPLOAD_PATH}" + mkdir -p "${UPLOAD_PATH}" + + ( + cd KMAPro/kMAPro/libs + "${COMPRESS_CMD}" a -bd -bb0 "../../../${UPLOAD_PATH}/${KEYMAN_ENGINE_ANDROID_ZIP}" keyman-engine.aar ../../../Samples '-xr!build.sh' + ) + + cp "KMAPro/kMAPro/build/outputs/apk/release/${KEYMAN_APK}" "${UPLOAD_PATH}" + + 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 + + if [[ -d "../oem/firstvoices/android/app/build/outputs/apk/release" ]]; then + cp "../oem/firstvoices/android/app/build/outputs/apk/release/${FIRSTVOICES_APK}" "${UPLOAD_PATH}" + write_download_info "${UPLOAD_PATH}" "${FIRSTVOICES_APK}" "FirstVoices Keyboards" apk android + fi + + ( + cd upload + # shellcheck disable=SC2154 + tc_rsync_upload "${KEYMAN_VERSION}" "android/${KEYMAN_TIER}" + ) + + builder_echo end "publish to downloads.keyman.com" success "Finished publishing release to downloads.keyman.com" +} + +function _publish_to_playstore() { + builder_echo start "publish to Google Play Store" "Publishing release to Google Play Store" + + "${KEYMAN_ROOT}/android/build.sh" "publish:${PUBTARGETS}" --ci + + builder_echo end "publish to Google Play Store" success "Finished publishing release to Google Play Store" +} + +function do_build() { + android_build_action +} + +function do_publish() { + if ! is_windows; then + # requires Powershell, so currently only supported on Windows + builder_echo error "This script is intended to be run on Windows only." + return 1 + fi + + export RSYNC_PATH + export RSYNC_USER + export RSYNC_HOST + export RSYNC_ROOT + + _publish_to_downloads_keyman_com + _publish_to_playstore + upload_help "Keyman for Android" android +} + +if builder_has_option --fv; then + TARGETS="engine,app,fv" + PUBTARGETS="app,fv" +else + # shellcheck disable=SC2034 + TARGETS="engine,app" + PUBTARGETS="app" +fi + +if builder_has_action all; then + android_clean_action + do_build + do_publish +else + builder_run_action clean android_clean_action + builder_run_action build do_build + builder_run_action publish do_publish +fi diff --git a/resources/teamcity/android/keyman-android-test-samples.sh b/resources/teamcity/android/keyman-android-test-samples.sh new file mode 100755 index 0000000000..4306e744cc --- /dev/null +++ b/resources/teamcity/android/keyman-android-test-samples.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. +# +# TC build script for Samples and Test projects + +# shellcheck disable=SC2164 +# shellcheck disable=SC1091 + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +# shellcheck disable=SC2154 +. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" +. "${KEYMAN_ROOT}/resources/teamcity/android/android-actions.inc.sh" + +################################ Main script ################################ + +builder_describe \ + "Build KMSample1, KMSample2, and Test projects" \ + "all run all actions" \ + "clean clean artifact directories" \ + "build configure, build and test Keyman for Android" + +builder_parse "$@" + +cd "${KEYMAN_ROOT}/android" + +function _build_kmea() { + builder_echo start "build" "Configuring and building KMEA" + "${KEYMAN_ROOT}/android/build.sh" configure:engine build:engine --debug --ci + builder_echo end "build" success "Finished configuring and building KMEA" +} + +function _build_kmsample1() { + builder_echo start "build" "Building KMSample1" + "${KEYMAN_ROOT}/android/build.sh" build:sample1 --debug --ci + builder_echo end "build" success "Finished building KMSample1" +} + +function _build_kmsample2() { + builder_echo start "build" "Building KMSample2" + "${KEYMAN_ROOT}/android/build.sh" build:sample2 --debug --ci + builder_echo end "build" success "Finished building KMSample2" +} + +function _build_keyboardharness() { + builder_echo start "build" "Building KeyboardHarness" + "${KEYMAN_ROOT}/android/build.sh" build:keyboardharness --debug --ci + builder_echo end "build" success "Finished building KeyboardHarness" +} + +function do_build() { + _build_kmea + _build_kmsample1 + _build_kmsample2 + _build_keyboardharness +} + +if builder_has_action all; then + android_clean_action + do_build +else + builder_run_action clean android_clean_action + builder_run_action build do_build +fi diff --git a/resources/teamcity/android/keyman-android-test.sh b/resources/teamcity/android/keyman-android-test.sh new file mode 100755 index 0000000000..00b282e0fd --- /dev/null +++ b/resources/teamcity/android/keyman-android-test.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. +# +# TC build script for Keyman Android/Test + +# shellcheck disable=SC2164 +# shellcheck disable=SC1091 + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +# shellcheck disable=SC2154 +. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" +. "${KEYMAN_ROOT}/resources/teamcity/android/android-actions.inc.sh" + +################################ Main script ################################ + +builder_describe \ + "Build and run tests for Keyman for Android" \ + "--fv additionally build and Test FV Android" \ + "all run all actions" \ + "clean clean artifact directories" \ + "build configure, build and test Keyman for Android" + +builder_parse "$@" + +cd "${KEYMAN_ROOT}/android" + +if builder_has_option --fv; then + TARGETS="engine,app,fv" +else + # shellcheck disable=SC2034 + TARGETS="engine,app" +fi + +if builder_has_action all; then + android_clean_action + android_build_action +else + builder_run_action clean android_clean_action + builder_run_action build android_build_action +fi diff --git a/resources/teamcity/includes/tc-helpers.inc.sh b/resources/teamcity/includes/tc-helpers.inc.sh index 69305ad9ab..b2f12b4a98 100644 --- a/resources/teamcity/includes/tc-helpers.inc.sh +++ b/resources/teamcity/includes/tc-helpers.inc.sh @@ -73,7 +73,7 @@ upload_help() { builder_echo start "upload help" "Uploading new ${PRODUCT} help to help.keyman.com" ( - # shellcheck disable=SC2164 + # shellcheck disable=SC2164,SC2154 cd "${KEYMAN_ROOT}/resources/build" "${KEYMAN_ROOT}/resources/build/help-keyman-com.sh" "${PRODUCT_PATH}" ) From e82a873cff15cc72fc5a93b09f81138d591b9b31 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 2 Jul 2025 18:40:33 +0200 Subject: [PATCH 086/262] maint(linux): use `tc_rsync_upload` and `write_download_info` functions Also make `_tc_rsync` function cross-platform. A comparison might show that we omit `--perms` when calling rsync with this change. However the short form (`-p`) is already included, so we don't have to additional add the long form `--perms`. --- resources/teamcity/includes/tc-helpers.inc.sh | 42 ++++++++++----- .../teamcity/linux/keyman-linux-release.sh | 54 +++---------------- 2 files changed, 38 insertions(+), 58 deletions(-) diff --git a/resources/teamcity/includes/tc-helpers.inc.sh b/resources/teamcity/includes/tc-helpers.inc.sh index b2f12b4a98..7ec2c3d98a 100644 --- a/resources/teamcity/includes/tc-helpers.inc.sh +++ b/resources/teamcity/includes/tc-helpers.inc.sh @@ -82,27 +82,45 @@ upload_help() { } _tc_rsync() { - local rsync_args CYGPATH_RSYNC_HOME SOURCE DESTINATION + local rsync_args SOURCE DESTINATION SOURCE=$1 DESTINATION=$2 - if ! is_windows; then - echo "This function is currently only supported on Windows." - return 1 - fi - rsync_args=( '-vrzltp' # verbose, recurse, zip, copy symlinks, preserve times, permissions - '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r' # map Windows security to host security '--stats' # show statistics for log "--rsync-path=${RSYNC_PATH}" # path on remote server - "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts" # use ssh - "${SOURCE}" - "${DESTINATION}" ) - CYGPATH_RSYNC_HOME=$(cygpath -w "${RSYNC_HOME}") - MSYS_NO_PATHCONV=1 "${CYGPATH_RSYNC_HOME}\\rsync.exe" "${rsync_args[@]}" + if is_windows; then + rsync_args+=( + '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r' # map Windows security to host security + "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts" # use ssh + ) + else + rsync_args+=( + "--rsh=ssh" # use ssh + ) + fi + + rsync_args+=( + "${SOURCE}" + "${DESTINATION}" + ) + + if is_windows; then + local CYGPATH_RSYNC_HOME + CYGPATH_RSYNC_HOME=$(cygpath -w "${RSYNC_HOME}") + MSYS_NO_PATHCONV=1 "${CYGPATH_RSYNC_HOME}\\rsync.exe" "${rsync_args[@]}" + else + local RSYNC=rsync + if is_macos; then + RSYNC=/usr/local/bin/rsync + [[ -f /opt/homebrew/bin/rsync ]] && RSYNC=/opt/homebrew/bin/rsync + fi + + ${RSYNC} "${rsync_args[@]}" + fi } # Download file or directory $1 from the remote server to $2. diff --git a/resources/teamcity/linux/keyman-linux-release.sh b/resources/teamcity/linux/keyman-linux-release.sh index 4abf705a22..b9a9c47efb 100755 --- a/resources/teamcity/linux/keyman-linux-release.sh +++ b/resources/teamcity/linux/keyman-linux-release.sh @@ -11,6 +11,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" # shellcheck disable=SC2154 . "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-actions.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-download-info.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-linux.inc.sh" @@ -31,7 +32,6 @@ builder_describe \ "--rsync-host=RSYNC_HOST rsync host on remote server" \ "--rsync-root=RSYNC_ROOT rsync root on remote server" - builder_parse "$@" cd "${KEYMAN_ROOT}/linux" @@ -84,58 +84,20 @@ function _sign_source_tarball() { function _publish_to_downloads() { builder_echo start "publish to downloads" "Publish to downloads.keyman.com" - local DATE UPLOAD_BASE UPLOAD_FOLDER UPLOAD_DIR - local ARTIFACTS NAMES - local TAR_GZ HASH SIZE DOWNLOAD_INFO + local UPLOAD_DIR KEYMAN_TGZ - DATE=$(date +%F) - - UPLOAD_BASE="upload" - UPLOAD_FOLDER="${KEYMAN_VERSION}" - UPLOAD_DIR="${UPLOAD_BASE}/${UPLOAD_FOLDER}" + UPLOAD_DIR="upload/${KEYMAN_VERSION}" + KEYMAN_TGZ="keyman-${KEYMAN_VERSION}.tar.gz" # Set permissions as required on download site builder_echo "Setting upload file permissions for downloads.keyman.com" + chmod g+w "${UPLOAD_DIR}" chmod a+rx "${UPLOAD_DIR}" - chmod g+w "${UPLOAD_DIR}" - - chmod g+rw "${UPLOAD_DIR}"/* + chmod g+w "${UPLOAD_DIR}"/* chmod a+r "${UPLOAD_DIR}"/* - ARTIFACTS=("keyman-${KEYMAN_VERSION}.tar.gz") - NAMES=("Keyman for Linux") - - for i in "${!ARTIFACTS[@]}"; do - TAR_GZ=${ARTIFACTS[${i}]} - # Construct .download_info - HASH=$(md5sum "${UPLOAD_DIR}/${TAR_GZ}" | cut -d ' ' -f 1) - SIZE=$(stat --print="%s" "${UPLOAD_DIR}/${TAR_GZ}") - # TODO: Truncate NAME - DOWNLOAD_INFO=$( jq -n \ - --arg NAME "${NAMES[${i}]}" \ - --arg BUILD_NUMBER "${KEYMAN_VERSION}" \ - --arg DATE "${DATE}" \ - --arg KEYMAN_TIER "${KEYMAN_TIER}" \ - --arg FILENAME "${TAR_GZ}" \ - --arg HASH "${HASH}" \ - --arg BUILD_COUNTER "${KEYMAN_VERSION_PATCH}" \ - --arg SIZE "${SIZE}" \ - '{name: $NAME, version: $BUILD_NUMBER, date: $DATE, platform: "linux", stability: $KEYMAN_TIER, file: $FILENAME, md5: $HASH, type: "tar.gz", build: $BUILD_COUNTER, size: $SIZE}' ) - echo "${DOWNLOAD_INFO}" | jq . >> "${UPLOAD_DIR}/${TAR_GZ}.download_info" - done - - # Expanded, documented form of the arguments - # ========================================== - # "-vrzltp " # verbose, recurse, zip, copy symlinks, preserve times, permissions - # "--perms " # perfectly matches existing file permissions on the build agent - # "--stats " # show statistics for log - # "--rsync-path= # path on remote server - # "--rsh=ssh " # use ssh - - # The actual rsync call. - # We run into weird quote-based issues if we don't do a monolithic call as seen below, at least at present. - builder_echo "Performing rsync call" - rsync -vrzltp --perms --stats --rsync-path="${RSYNC_PATH}" --rsh=ssh "${UPLOAD_DIR}" "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/linux/${KEYMAN_TIER}" + write_download_info "${UPLOAD_DIR}" "${KEYMAN_TGZ}" "Keyman for Linux" tar.gz linux + tc_rsync_upload "${UPLOAD_DIR}" "linux/${KEYMAN_TIER}" builder_echo end "publish to downloads" success "Publish to downloads.keyman.com" } From 9c6838096eaf2184380f5e645cc6eb8ae5af18d5 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 25 Jun 2025 17:32:49 +0200 Subject: [PATCH 087/262] maint(ios): add TC build scripts for iOS Fixes: #14189 Part-of: #13399 Build-bot: release ios Test-bot: skip --- ios/build.sh | 4 +- resources/teamcity/includes/tc-mac.inc.sh | 16 ++ resources/teamcity/ios/ios-actions.inc.sh | 15 + resources/teamcity/ios/keyman-ios-release.sh | 268 ++++++++++++++++++ .../teamcity/ios/keyman-ios-test-samples.sh | 39 +++ resources/teamcity/ios/keyman-ios-test.sh | 118 ++++++++ 6 files changed, 458 insertions(+), 2 deletions(-) create mode 100644 resources/teamcity/includes/tc-mac.inc.sh create mode 100755 resources/teamcity/ios/ios-actions.inc.sh create mode 100755 resources/teamcity/ios/keyman-ios-release.sh create mode 100755 resources/teamcity/ios/keyman-ios-test-samples.sh create mode 100755 resources/teamcity/ios/keyman-ios-test.sh diff --git a/ios/build.sh b/ios/build.sh index 78bd320b23..f260b07f8b 100755 --- a/ios/build.sh +++ b/ios/build.sh @@ -16,8 +16,8 @@ builder_describe "Builds Keyman Engine and the Keyman app for use on iOS devices ":engine Builds KeymanEngine.xcframework, usable by our main app and by third-party apps" \ ":app=keyman Builds the Keyman app for iOS platforms" \ ":help Online documentation" \ - ":sample1=Samples/KMSample1 Builds the first KeymanEngine sample app" \ - ":sample2=Samples/KMSample2 Builds the second KeymanEngine sample app" \ + ":sample1=samples/KMSample1 Builds the first KeymanEngine sample app" \ + ":sample2=samples/KMSample2 Builds the second KeymanEngine sample app" \ ":fv=../oem/firstvoices/ios Builds OEM FirstVoices for iOS platforms" \ "--sim-artifact+ Also outputs a simulator-friendly test artifact corresponding to the build" diff --git a/resources/teamcity/includes/tc-mac.inc.sh b/resources/teamcity/includes/tc-mac.inc.sh new file mode 100644 index 0000000000..c7997bc651 --- /dev/null +++ b/resources/teamcity/includes/tc-mac.inc.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. + +ba_mac_unlock_keychain() { + if [[ -z "${MAC_BUILDAGENT_PASSWORD}" ]]; then + echo "Error: MAC_BUILDAGENT_PASSWORD environment variable is not set." + exit 1 + fi + security unlock-keychain -p "${MAC_BUILDAGENT_PASSWORD}" login.keychain +} + +ba_mac_clean_xcode_derived_data() { + builder_echo start "clean" "Cleaning XCode DerivedData mess" + rm -rf "${HOME}/Library/Developer/Xcode/DerivedData" + builder_echo end "clean" success "Finished cleaning XCode DerivedData mess" +} diff --git a/resources/teamcity/ios/ios-actions.inc.sh b/resources/teamcity/ios/ios-actions.inc.sh new file mode 100755 index 0000000000..00b82b7f5c --- /dev/null +++ b/resources/teamcity/ios/ios-actions.inc.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. + +ios_build() { + builder_echo start "build" "Building KeymanEngine + Keyman for iOS" + # shellcheck disable=SC2154 + "${KEYMAN_ROOT}/ios/ci.sh" build + builder_echo end "build" success "Finished building KeymanEngine + Keyman for iOS" +} + +ios_capture_build_artifacts() { + builder_echo start "prep-release" "Using prep-release script to capture build artifacts" + "${KEYMAN_ROOT}/ios/tools/prepRelease.sh" + builder_echo end "prep-release" success "Finished capturing build artifacts" +} diff --git a/resources/teamcity/ios/keyman-ios-release.sh b/resources/teamcity/ios/keyman-ios-release.sh new file mode 100755 index 0000000000..e5fe7346e0 --- /dev/null +++ b/resources/teamcity/ios/keyman-ios-release.sh @@ -0,0 +1,268 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. +# +# TC build script for Keyman iOS/Release +# +# Requires: env variable $MAC_BUILDAGENT_PASSWORD set + +# shellcheck disable=SC2164 +# shellcheck disable=SC1091 + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +# shellcheck disable=SC2154 +. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-mac.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/ios/ios-actions.inc.sh" + +################################ Main script ################################ + +builder_describe \ + "Build KeymanEngine and Keyman for iOS" \ + "all run all actions" \ + "clean clean artifact directories" \ + "build build KeymanEngine and Keyman for iOS" \ + "publish deploy Keyman for iOS" \ + "--sil-itc-team=SIL_ITC_TEAM_ID SIL Fastlane ITC Team ID to use for TestFlight upload" \ + "--sil-api-key-id=SIL_API_KEY_ID SIL Fastlane API Key ID to use for TestFlight upload" \ + "--sil-itc-provider=SIL_ITC_PROVIDER SIL Fastlane ITC Provider to use for TestFlight upload" \ + "--sil-review-info=SIL_REVIEW_INFO SIL Fastlane Beta App Review Info to use for TestFlight upload" \ + "--fv additionally build FirstVoices app" \ + "--fv-itc-team=FV_ITC_TEAM_ID FirstVoices Fastlane ITC Team ID to use for TestFlight upload" \ + "--fv-api-key-id=FV_API_KEY_ID FirstVoices Fastlane API Key ID to use for TestFlight upload" \ + "--fv-itc-provider=FV_ITC_PROVIDER FirstVoices Fastlane ITC Provider to use for TestFlight upload" \ + "--fv-review-info=FV_REVIEW_INFO FirstVoices Fastlane Beta App Review Info to use for TestFlight upload" \ + "--rsync-path=RSYNC_PATH rsync path on remote server" \ + "--rsync-user=RSYNC_USER rsync user on remote server" \ + "--rsync-host=RSYNC_HOST rsync host on remote server" \ + "--rsync-root=RSYNC_ROOT rsync root on remote server" \ + "--help.keyman.com=HELP_KEYMAN_COM path to help.keyman.com repository" + + +builder_parse "$@" + +cd "${KEYMAN_ROOT}/ios" + +function _publish_to_downloads_keyman_com() { + # Publish to downloads.keyman.com + builder_echo start "publish to downloads.keyman.com" "Publishing release to downloads.keyman.com" + ( + local KMEI_DST KEYMANAPP_IPA_DST KEYMAN_CHANGELOG UPLOAD_BASE UPLOAD_FOLDER UPLOAD_DIR + + # Note that the `rsync` tool that exists on Macs by default is a very old version and must be updated to a newer version, e.g. by using Homebrew. + KMEI_DST="keyman-engine-ios-${KEYMAN_VERSION}.zip" + KEYMANAPP_IPA_DST="keyman-ios-${KEYMAN_VERSION}.ipa" + # shellcheck disable=SC2154 + KEYMAN_CHANGELOG="changelog-${KEYMAN_VERSION}-${KEYMAN_TIER}.txt" + + UPLOAD_BASE="upload" + UPLOAD_FOLDER="${KEYMAN_VERSION}" + UPLOAD_DIR="${UPLOAD_BASE}/${UPLOAD_FOLDER}" + + # The upload directory has already been created, + # but the permissions aren't quite in place yet. + + builder_echo "Setting upload file permissions for downloads.keyman.com" + chmod g+w "${UPLOAD_DIR}" + chmod a+rx "${UPLOAD_DIR}" + + chmod g+rw "${UPLOAD_DIR}/${KMEI_DST}" "${UPLOAD_DIR}/${KMEI_DST}.download_info" "${UPLOAD_DIR}/${KEYMANAPP_IPA_DST}" "${UPLOAD_DIR}/${KEYMANAPP_IPA_DST}.download_info" "${UPLOAD_DIR}/${KEYMAN_CHANGELOG}" + chmod a+r "${UPLOAD_DIR}/${KMEI_DST}" "${UPLOAD_DIR}/${KMEI_DST}.download_info" "${UPLOAD_DIR}/${KEYMANAPP_IPA_DST}" "${UPLOAD_DIR}/${KEYMANAPP_IPA_DST}.download_info" "${UPLOAD_DIR}/${KEYMAN_CHANGELOG}" + + if builder_has_option --fv; then + FIRSTVOICESAPP_IPA_DST="firstvoices-ios-${KEYMAN_VERSION}.ipa" + chmod g+rw "${UPLOAD_DIR}/${FIRSTVOICESAPP_IPA_DST}" "${UPLOAD_DIR}/${FIRSTVOICESAPP_IPA_DST}.download_info" + chmod a+r "${UPLOAD_DIR}/${FIRSTVOICESAPP_IPA_DST}" "${UPLOAD_DIR}/${FIRSTVOICESAPP_IPA_DST}.download_info" + fi + + builder_echo "Performing rsync call" + cd "${UPLOAD_BASE}" + tc_rsync_upload "${UPLOAD_FOLDER}" "ios/${KEYMAN_TIER}" + ) + builder_echo end "publish to downloads.keyman.com" success "Finished publishing release to downloads.keyman.com" +} + +function __do_upload_to_appstore() { + ( + local APP_IPA_DST CHANGELOG_PATH changelog FASTLANE_ITC_TEAM_ID + local FASTLANE_ITC_PROVIDER="$1" + local FASTLANE_API_KEY_ID="$2" + + export FASTLANE_ITC_TEAM_ID="$3" + export LC_ALL=en_US.UTF-8 + export LANG=en_US.UTF-8 + + KEYMAN_CHANGELOG="changelog-${KEYMAN_VERSION}-${KEYMAN_TIER}.txt" + CHANGELOG_PATH="upload/${KEYMAN_VERSION}/${KEYMAN_CHANGELOG}" + + APP_IPA_DST="$4-ios-${KEYMAN_VERSION}.ipa" + + changelog=$(cat "${CHANGELOG_PATH}") + + builder_echo "Uploading to the App Store with the following changelog:" + builder_echo "${changelog}" + builder_echo "" + + cd "upload/${KEYMAN_VERSION}" + + # Construct Deliverfile for Fastlane metadata upload. + cat > Deliverfile << _EOF +release_notes( + 'en-US' => <<-eos +${changelog} +eos +_EOF + + if builder_has_option --fv; then + cat >> Deliverfile << _EOF + 'en-CA' => <<-eos +${changelog} +eos +) + +copyright "#{Time.now.year} SIL Global, First Peoples' Cultural Foundation" +_EOF + else + cat >> Deliverfile << _EOF +) + +copyright "#{Time.now.year} SIL Global" +_EOF + fi + + # -f: Force - skips a visual report Fastlane likes to generate otherwise. (Apple's standing advice is to skip when doing CI.) + # shellcheck disable=SC2154 + fastlane deliver \ + --api_key_path "${HOME}/fastlane/${FASTLANE_API_KEY_ID}.json" \ + -f true \ + -u buildagent@keyman.com \ + --submit_for_review true \ + --automatic_release true \ + --precheck_include_in_app_purchases false \ + --verbose \ + --itc_provider "${FASTLANE_ITC_PROVIDER}" \ + --submission_information "{\"add_id_info_uses_idfa\": false}" \ + --ipa "${APP_IPA_DST}" + ) +} + +function _publish_to_appstore() { + builder_echo start "push to AppStore" "Pushing release to AppStore for deployment" + # shellcheck disable=SC2154 + __do_upload_to_appstore "${SIL_ITC_PROVIDER}" "${SIL_API_KEY_ID}" "${SIL_ITC_TEAM_ID}" keyman + builder_echo end "push to AppStore" success "Finished pushing release to AppStore for deployment" +} + +function _publish_to_appstore_fv() { + if ! builder_has_option --fv; then + builder_echo "Skipping FirstVoices upload to AppStore as --fv option is not set" + return + fi + + builder_echo start "push FV to AppStore" "Pushing FirstVoices release to AppStore for deployment" + # shellcheck disable=SC2154 + __do_upload_to_appstore "${FV_ITC_PROVIDER}" "${FV_API_KEY_ID}" "${FV_ITC_TEAM_ID}" firstvoices + builder_echo end "push FV to AppStore" success "Finished pushing FirstVoices release to AppStore for deployment" +} + +function __do_upload_to_fastlane() { + ( + local APP_IPA_DST PILOT_GROUPS BETA_APP_DESCRIPTION FASTLANE_ITC_TEAM_ID + local FASTLANE_ITC_PROVIDER="$1" + local FASTLANE_API_KEY_ID="$2" + local BETA_APP_REVIEW_INFO="$3" + + export FASTLANE_ITC_TEAM_ID="$4" + export LC_ALL=en_US.UTF-8 + export LANG=en_US.UTF-8 + + KEYMAN_CHANGELOG="changelog-${KEYMAN_VERSION}-${KEYMAN_TIER}.txt" + CHANGELOG_PATH="upload/${KEYMAN_VERSION}/${KEYMAN_CHANGELOG}" + + APP_IPA_DST="$5-ios-${KEYMAN_VERSION}.ipa" + BETA_APP_DESCRIPTION="$6" + + changelog=$(cat "${CHANGELOG_PATH}") + + builder_echo "Uploading to Fastlane with the following changelog:" + builder_echo "${changelog}" + builder_echo "" + + cd "upload/${KEYMAN_VERSION}" + + # Sets the upload to distribute to the appropriate group of external testers. + + if [[ "${KEYMAN_TIER}" = "beta" ]]; then + export PILOT_GROUPS="Beta" + elif [[ "${KEYMAN_TIER}" = "alpha" ]]; then + export PILOT_GROUPS="Alpha" + fi + + cd "upload/${KEYMAN_VERSION}" + command -v fastlane + # shellcheck disable=SC2154 + fastlane pilot upload \ + --api_key_path "${HOME}/fastlane/${FASTLANE_API_KEY_ID}.json" \ + --changelog "${changelog}" \ + -g "${PILOT_GROUPS}" \ + --itc_provider "${FASTLANE_ITC_PROVIDER}" \ + --verbose \ + --ipa "${APP_IPA_DST}" \ + --beta_app_review_info "${BETA_APP_REVIEW_INFO}" \ + --beta_app_description "${BETA_APP_DESCRIPTION}" + ) +} + +function _publish_to_fastlane_for_testing() { + builder_echo start "upload to FastLane" "Uploading build to FastLane for testing-deployment" + # shellcheck disable=SC2154 + __do_upload_to_fastlane "${SIL_ITC_PROVIDER}" "${SIL_API_KEY_ID}" "${SIL_REVIEW_INFO}" "${SIL_ITC_TEAM_ID}" keyman "Keyman for iPhone and iPad pre-release, see release notes for details" + builder_echo end "upload to FastLane" success "Finished uploading build to FastLane for testing-deployment" +} + +function _publish_to_fastlane_for_testing_fv() { + if ! builder_has_option --fv; then + builder_echo "Skipping FirstVoices upload to FastLane as --fv option is not set" + return + fi + + builder_echo start "upload to FV FastLane" "Uploading FirstVoices build to FastLane for testing-deployment" + # shellcheck disable=SC2154 + __do_upload_to_fastlane "${FV_ITC_PROVIDER}" "${FV_API_KEY_ID}" "${FV_REVIEW_INFO}" "${FV_ITC_TEAM_ID}" firstvoices "FirstVoices Keyboards for iPhone and iPad pre-release, see release notes for details" + builder_echo end "upload to FV FastLane" success "Finished uploading FirstVoices build to FastLane for testing-deployment" +} + +function do_build() { + ios_build + ios_capture_build_artifacts +} + +function do_publish() { + _publish_to_downloads_keyman_com + upload_help "Keyman for iOS" ios + + if [[ "${KEYMAN_TIER}" == "stable" ]]; then + _publish_to_appstore + _publish_to_appstore_fv + else + _publish_to_fastlane_for_testing + _publish_to_fastlane_for_testing_fv + fi +} + +ba_mac_unlock_keychain + +if builder_has_action all; then + ba_mac_clean_xcode_derived_data + do_build + do_publish +else + builder_run_action clean ba_mac_clean_xcode_derived_data + builder_run_action build do_build + builder_run_action publish do_publish +fi diff --git a/resources/teamcity/ios/keyman-ios-test-samples.sh b/resources/teamcity/ios/keyman-ios-test-samples.sh new file mode 100755 index 0000000000..d50a390177 --- /dev/null +++ b/resources/teamcity/ios/keyman-ios-test-samples.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. +# +# TC build script for Keyman iOS/Test Samples and Test Projects + +# shellcheck disable=SC2164 +# shellcheck disable=SC1091 + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +################################ Main script ################################ + +builder_describe \ + "Build samples and test projects for Keyman for iOS" \ + "all build test samples" + +builder_parse "$@" + +# shellcheck disable=SC2154 +cd "${KEYMAN_ROOT}/ios" + +function _build_sample1() { + builder_echo start "kmsample1" "Building KMSample1" + "${KEYMAN_ROOT}/ios/samples/KMSample1/build.sh" clean configure build --debug + builder_echo end "kmsample1" success "Finished building KMSample1" +} + +function _build_sample2() { + builder_echo start "kmsample2" "Building KMSample2" + "${KEYMAN_ROOT}/ios/samples/KMSample2/build.sh" clean configure build --debug + builder_echo end "kmsample2" success "Finished building KMSample2" +} + +_build_sample1 +_build_sample2 diff --git a/resources/teamcity/ios/keyman-ios-test.sh b/resources/teamcity/ios/keyman-ios-test.sh new file mode 100755 index 0000000000..9f918b8d8f --- /dev/null +++ b/resources/teamcity/ios/keyman-ios-test.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. +# +# TC build script for Keyman iOS/Test +# +# Requires: env variable $MAC_BUILDAGENT_PASSWORD set + +# shellcheck disable=SC2164 +# shellcheck disable=SC1091 + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +# shellcheck disable=SC2154 +. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-mac.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/ios/ios-actions.inc.sh" + +################################ Main script ################################ + +builder_describe \ + "Build KeymanEngine and Keyman for iOS" \ + "all run all actions" \ + "clean clean artifact directories" \ + "build build KeymanEngine and Keyman for iOS" \ + "--pr-number=PR_NUMBER Currently building PR" \ + "--sil-itc-team=SIL_ITC_TEAM_ID SIL Fastlane ITC Team ID to use for TestFlight upload" \ + "--sil-api-key-id=SIL_API_KEY_ID SIL Fastlane API Key ID to use for TestFlight upload" \ + "--sil-itc-provider=SIL_ITC_PROVIDER SIL Fastlane ITC Provider to use for TestFlight upload" \ + "--fv additionally build FirstVoices app" \ + "--fv-itc-team=FV_ITC_TEAM_ID FirstVoices Fastlane ITC Team ID to use for TestFlight upload" \ + "--fv-api-key-id=FV_API_KEY_ID FirstVoices Fastlane API Key ID to use for TestFlight upload" \ + "--fv-itc-provider=FV_ITC_PROVIDER FirstVoices Fastlane ITC Provider to use for TestFlight upload" + + +builder_parse "$@" + +cd "${KEYMAN_ROOT}/ios" + +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" + + cd "upload/${BUILD_NUMBER}" + command -v fastlane + # shellcheck disable=SC2154 + fastlane pilot upload \ + --distribute_external false \ + --notify_external_testers false \ + --submit_beta_review false \ + --api_key_path "${HOME}/fastlane/${FASTLANE_API_KEY_ID}.json" \ + -g "PR Builds" \ + --itc_provider "${FASTLANE_ITC_PROVIDER}" \ + --verbose \ + --ipa "${APP_IPA_DST}" + ) +} + +function _upload_to_testflight_pr_area() { + # shellcheck disable=SC2154 + if ! [[ "${PR_NUMBER}" =~ ^[0-9]+$ ]]; then + builder_echo "Ignoring TestFlight upload for non-PR build" + return + fi + + builder_echo start "upload to testflight" "Uploading build to TestFlight (PR area)" + # shellcheck disable=SC2154 + __do_upload_to_testflight "${SIL_ITC_PROVIDER}" "${SIL_API_KEY_ID}" "${SIL_ITC_TEAM_ID}" keyman + builder_echo end "upload to testflight" success "Finished uploading build to TestFlight (PR area)" +} + +function _upload_to_testflight_pr_area_fv() { + # shellcheck disable=SC2154 + if ! [[ "${PR_NUMBER}" =~ ^[0-9]+$ ]]; then + builder_echo "Ignoring TestFlight upload for non-PR build" + return + fi + + if ! builder_has_option --fv; then + builder_echo "Skipping FirstVoices upload to TestFlight as --fv option is not set" + return + fi + + builder_echo start "upload to testflight" "Uploading FirstVoices build to TestFlight (PR area) - FirstVoices" + # shellcheck disable=SC2154 + __do_upload_to_testflight "${FV_ITC_PROVIDER}" "${FV_API_KEY_ID}" "${FV_ITC_TEAM_ID}" firstvoices + builder_echo end "upload to testflight" success "Finished uploading FirstVoices build to TestFlight (PR area) - FirstVoices" +} + +function do_build() { + ios_build + ios_capture_build_artifacts + _upload_to_testflight_pr_area + _upload_to_testflight_pr_area_fv +} + +ba_mac_unlock_keychain + +if builder_has_action all; then + ba_mac_clean_xcode_derived_data + do_build +else + builder_run_action clean ba_mac_clean_xcode_derived_data + builder_run_action build do_build +fi From 02ca6222bd7e08169cf05216ed7bbd9119bf9ff8 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 30 Jun 2025 17:12:01 +0200 Subject: [PATCH 088/262] maint(mac): add TC build scripts for macOS Fixes: #14190 Part-of: #13399 Build-bot: release mac Test-bot: skip --- resources/teamcity/includes/tc-mac.inc.sh | 8 ++ .../teamcity/macos/keyman-macos-release.sh | 88 +++++++++++++++++++ resources/teamcity/macos/keyman-macos-test.sh | 42 +++++++++ resources/teamcity/macos/macos-actions.inc.sh | 14 +++ 4 files changed, 152 insertions(+) create mode 100755 resources/teamcity/macos/keyman-macos-release.sh create mode 100755 resources/teamcity/macos/keyman-macos-test.sh create mode 100755 resources/teamcity/macos/macos-actions.inc.sh diff --git a/resources/teamcity/includes/tc-mac.inc.sh b/resources/teamcity/includes/tc-mac.inc.sh index c7997bc651..911fec97ff 100644 --- a/resources/teamcity/includes/tc-mac.inc.sh +++ b/resources/teamcity/includes/tc-mac.inc.sh @@ -14,3 +14,11 @@ ba_mac_clean_xcode_derived_data() { rm -rf "${HOME}/Library/Developer/Xcode/DerivedData" builder_echo end "clean" success "Finished cleaning XCode DerivedData mess" } + +ba_mac_unmount_volumes_keyman() { + builder_echo start "unmount" "Unmounting Keyman volumes" + if [[ -d /Volumes/Keyman ]]; then + hdiutil detach /Volumes/Keyman; + fi + builder_echo end success "unmount" "Finished unmounting Keyman volumes" +} diff --git a/resources/teamcity/macos/keyman-macos-release.sh b/resources/teamcity/macos/keyman-macos-release.sh new file mode 100755 index 0000000000..1fad300c60 --- /dev/null +++ b/resources/teamcity/macos/keyman-macos-release.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. +# +# TC build script for Keyman macOS/Release +# +# Requires: env variable $MAC_BUILDAGENT_PASSWORD set + +# shellcheck disable=SC2164 +# shellcheck disable=SC1091 + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +# shellcheck disable=SC2154 +. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-mac.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/macos/macos-actions.inc.sh" + +################################ Main script ################################ + +builder_describe \ + "Build release of Keyman for macOS" \ + "all run all actions" \ + "clean clean artifact directories" \ + "build build Keyman for macOS" \ + "publish publish Keyman for macOS" \ + "--rsync-path=RSYNC_PATH rsync path on remote server" \ + "--rsync-user=RSYNC_USER rsync user on remote server" \ + "--rsync-host=RSYNC_HOST rsync host on remote server" \ + "--rsync-root=RSYNC_ROOT rsync root on remote server" \ + "--help.keyman.com=HELP_KEYMAN_COM path to help.keyman.com repository" + +builder_parse "$@" + +cd "${KEYMAN_ROOT}/mac" + +function _publish_to_downloads_keyman_com() { + builder_echo start "publish to downloads.keyman.com" "Publishing release to downloads.keyman.com" + + local UPLOAD_DIR + + # Note that the `rsync` tool that exists on Macs by default is a very old version and must be updated to a newer version, e.g. by using Homebrew. + + UPLOAD_DIR="Keyman4MacIM/output/upload/${KEYMAN_VERSION}" + + # Set permissions as required on download site + builder_echo "Setting upload file permissions for downloads.keyman.com" + chmod g+w "${UPLOAD_DIR}" + chmod a+rx "${UPLOAD_DIR}" + + chmod g+rw "${UPLOAD_DIR}"/* + chmod a+r "${UPLOAD_DIR}"/* + + builder_echo "Performing rsync call" + # shellcheck disable=SC2154 + tc_rsync_upload "${UPLOAD_DIR}" "mac/${KEYMAN_TIER}" + + builder_echo end "publish to downloads.keyman.com" success "Finished publishing release to downloads.keyman.com" +} + +function _build_publish() { + builder_echo start "publish" "Publishing Keyman for macOS" + # shellcheck disable=SC2154 + "${KEYMAN_ROOT}/mac/build.sh" publish + builder_echo end success "publish" "Finished publishing Keyman for macOS" +} + +function _do_publish() { + _build_publish + _publish_to_downloads_keyman_com + upload_help "Keyman for macOS" mac +} + +ba_mac_unlock_keychain + +if builder_has_action all; then + macos_clean_action + macos_build_action + do_publish +else + builder_run_action clean macos_clean_action + builder_run_action build macos_build_action + builder_run_action publish do_publish +fi diff --git a/resources/teamcity/macos/keyman-macos-test.sh b/resources/teamcity/macos/keyman-macos-test.sh new file mode 100755 index 0000000000..cea4e8ab86 --- /dev/null +++ b/resources/teamcity/macos/keyman-macos-test.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. +# +# TC build script for Keyman macOS/Test +# +# Requires: env variable $MAC_BUILDAGENT_PASSWORD set + +# shellcheck disable=SC2164 +# shellcheck disable=SC1091 + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +# shellcheck disable=SC2154 +. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-mac.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/macos/macos-actions.inc.sh" + +################################ Main script ################################ + +builder_describe \ + "Build Keyman for macOS" \ + "all run all actions" \ + "clean clean artifact directories" \ + "build build Keyman for macOS" + +builder_parse "$@" + +cd "${KEYMAN_ROOT}/mac" + +ba_mac_unlock_keychain + +if builder_has_action all; then + macos_clean_action + macos_build_action +else + builder_run_action clean macos_clean_action + builder_run_action build macos_build_action +fi diff --git a/resources/teamcity/macos/macos-actions.inc.sh b/resources/teamcity/macos/macos-actions.inc.sh new file mode 100755 index 0000000000..a463db4612 --- /dev/null +++ b/resources/teamcity/macos/macos-actions.inc.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. + +macos_clean_action() { + ba_mac_clean_xcode_derived_data + ba_mac_unmount_volumes_keyman +} + +macos_build_action() { + builder_echo start "build" "Building Keyman for macOS" + # shellcheck disable=SC2154 + "${KEYMAN_ROOT}/mac/build.sh" configure build test publish + builder_echo end success "build" "Finished building Keyman for macOS" +} From f38eaa129518ee3340f61ff0b6466de50436196a Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 2 Jul 2025 12:42:52 +0200 Subject: [PATCH 089/262] maint(developer): replace Powershell scripts Refactors the Powershell scripts that download and upload files and create .download_info files. This change is based on #14236/#14254 which bypasses the path rewriting that msys does when calling Windows rsync. Fixes: #14226 Related: #14236 Build-bot: skip Test-bot: skip --- .../download-symbol-server-index.ps1 | 63 ----- .../developer/keyman-developer-release.sh | 73 ++++-- .../developer/publish-new-symbols.ps1 | 39 --- .../publish-to-downloads-keyman-com.ps1 | 223 ------------------ 4 files changed, 58 insertions(+), 340 deletions(-) delete mode 100644 resources/teamcity/developer/download-symbol-server-index.ps1 delete mode 100644 resources/teamcity/developer/publish-new-symbols.ps1 delete mode 100644 resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 diff --git a/resources/teamcity/developer/download-symbol-server-index.ps1 b/resources/teamcity/developer/download-symbol-server-index.ps1 deleted file mode 100644 index 5e84be2c05..0000000000 --- a/resources/teamcity/developer/download-symbol-server-index.ps1 +++ /dev/null @@ -1,63 +0,0 @@ -# Keyman is copyright (C) SIL Global. MIT License. -# -# This script should be the identical for nightly/beta/stable for a given platform -# - -$ErrorActionPreference = "Stop" - -$RSYNC_HOME = $env:RSYNC_HOME -$RSYNC_PATH = $env:RSYNC_PATH -$RSYNC_USER = $env:RSYNC_USER -$RSYNC_HOST = $env:RSYNC_HOST -$RSYNC_ROOT = $env:RSYNC_ROOT -$USERPROFILE = $env:USERPROFILE - -New-Item -Force -ItemType Directory symbols\000admin -cd symbols\000admin - -# -# Download the symbol server index files with rsync from downloads.keyman.com -# (rsync requires that we are in the symbols folder to get folders in -# sync correctly; it is possible to resolve this but easier to just cd.) -# - -$rsync_args = @( - '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions - '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security - '--stats', # show statistics for log - "--rsync-path=""${RSYNC_PATH}""", # path on remote server - "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh - "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/symbols/000admin/lastid.txt", # target server + path - "." # download the whole symbols 000Admin folder -) - -& ${RSYNC_HOME}\rsync.exe ${rsync_args} -if (${LASTEXITCODE} -ne 0) { throw "Exit code is ${LASTEXITCODE}" } - -$rsync_args = @( - '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions - '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security - '--stats', # show statistics for log - "--rsync-path=""${RSYNC_PATH}""", # path on remote server - "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh - "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/symbols/000admin/history.txt", # target server + path - "." # download the whole symbols 000Admin folder -) - -& ${RSYNC_HOME}\rsync.exe ${rsync_args} -if (${LASTEXITCODE} -ne 0) { throw "Exit code is ${LASTEXITCODE}" } - -$rsync_args = @( - '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions - '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security - '--stats', # show statistics for log - "--rsync-path=""${RSYNC_PATH}""", # path on remote server - "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh - "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/symbols/000admin/server.txt", # target server + path - "." # download the whole symbols 000Admin folder -) - -& ${RSYNC_HOME}\rsync.exe ${rsync_args} -if (${LASTEXITCODE} -ne 0) { throw "Exit code is ${LASTEXITCODE}" } - -# EOF diff --git a/resources/teamcity/developer/keyman-developer-release.sh b/resources/teamcity/developer/keyman-developer-release.sh index 5aa8608a23..8d0cf55f8c 100755 --- a/resources/teamcity/developer/keyman-developer-release.sh +++ b/resources/teamcity/developer/keyman-developer-release.sh @@ -13,6 +13,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE # shellcheck disable=SC2154 +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-download-info.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" ################################ Main script ################################ @@ -33,6 +34,11 @@ builder_parse "$@" # shellcheck disable=SC2154 cd "${KEYMAN_ROOT}/developer/src" +# TODO: move to parameters (#14202) +LOCAL_SYMBOLS_PATH="${KEYMAN_ROOT}/../symbols" +REMOTE_SYMBOLS_PATH="windows/symbols" +SYMBOLS_SUBDIR="000admin" + function _build_developer() { builder_echo start "build developer" "Building Keyman Developer" @@ -60,24 +66,24 @@ function _publish_sentry() { function _download_symbol_server_index() { # Download symbol server index from symbol server builder_echo start "download symbol server index" "Downloading symbol server index" + ( + mkdir -p "${LOCAL_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}" + cd "${LOCAL_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}" - cd "${KEYMAN_ROOT}/.." - # shellcheck disable=SC2154 - powershell -NonInteractive -ExecutionPolicy Bypass -File "${THIS_SCRIPT_PATH}/download-symbol-server-index.ps1" - cd "${KEYMAN_ROOT}/developer/src" - + tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/lastid.txt" "." + tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/history.txt" "." + tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/server.txt" "." + ) builder_echo end "download symbol server index" success "Finished downloading symbol server index" } function _publish_new_symbols() { # Publish new symbols to symbol server builder_echo start "publish new symbols" "Publishing new symbols to symbol server" - - cd "${KEYMAN_ROOT}/../symbols" - # shellcheck disable=SC2154 - powershell -NonInteractive -ExecutionPolicy Bypass -File "${THIS_SCRIPT_PATH}/publish-new-symbols.ps1" - cd "${KEYMAN_ROOT}/developer/src" - + ( + cd "${LOCAL_SYMBOLS_PATH}" + tc_rsync_upload "." "${REMOTE_SYMBOLS_PATH}" + ) builder_echo end "publish new symbols" success "Finished publishing new symbols to symbol server" } @@ -85,10 +91,47 @@ function _publish_to_downloads_keyman_com() { # Publish to downloads.keyman.com builder_echo start "publish to downloads.keyman.com" "Publishing release to downloads.keyman.com" - cd "${KEYMAN_ROOT}/developer" - # shellcheck disable=SC2154 - powershell -NonInteractive -ExecutionPolicy Bypass -File "${THIS_SCRIPT_PATH}/publish-to-downloads-keyman-com.ps1" - cd "${KEYMAN_ROOT}/developer/src" + ( + cd "${KEYMAN_ROOT}/developer" + + local UPLOAD_PATH KEYBOARDS_PATH KMCOMP_ZIP DEVELOPER_EXE DEBUG_ZIP COMPRESS_CMD + # shellcheck disable=SC2154 + UPLOAD_PATH="upload/${KEYMAN_VERSION}" + KEYBOARDS_PATH="${UPLOAD_PATH}/keyboards" + KMCOMP_ZIP="kmcomp-${KEYMAN_VERSION}.zip" + DEVELOPER_EXE="keymandeveloper-${KEYMAN_VERSION}.exe" + DEBUG_ZIP="debug-${KEYMAN_VERSION}.zip" + + rm -rf "${UPLOAD_PATH}" + mkdir -p "${UPLOAD_PATH}" + + ( + cd bin + cp "${KEYMAN_ROOT}/common/schemas/keyboard_info/keyboard_info.schema.json" . + + # shellcheck disable=SC2154 + COMPRESS_CMD="${SEVENZ_HOME}/7z" + + "${COMPRESS_CMD}" a -bd -bb0 "../${UPLOAD_PATH}/${KMCOMP_ZIP}" kmconvert.exe keyboard_info.schema.json xml/layoutbuilder/*.keyman-touch-layout projects/ server/ + ) + + cp "release/${KEYMAN_VERSION}/${DEVELOPER_EXE}" "${UPLOAD_PATH}/" + + write_download_info "${UPLOAD_PATH}" "${DEVELOPER_EXE}" "Keyman Developer" exe win + write_download_info "${UPLOAD_PATH}" "${KMCOMP_ZIP}" "Keyman Developer Command-Line Compiler" zip win + + mkdir -p "${KEYBOARDS_PATH}" + cp -r "${KEYMAN_ROOT}/common/test/keyboards"/*/build/*.kmp "${KEYBOARDS_PATH}/" + + if [[ -f "release/${KEYMAN_VERSION}/${DEBUG_ZIP}" ]]; then + cp "release/${KEYMAN_VERSION}/${DEBUG_ZIP}" "${UPLOAD_PATH}/" + write_download_info "${UPLOAD_PATH}" "${DEBUG_ZIP}" "Keyman Developer debug files" zip win + fi + + cd upload + # shellcheck disable=SC2154 + tc_rsync_upload "${KEYMAN_VERSION}" "developer/${KEYMAN_TIER}" + ) builder_echo end "publish to downloads.keyman.com" success "Finished publishing release to downloads.keyman.com" } diff --git a/resources/teamcity/developer/publish-new-symbols.ps1 b/resources/teamcity/developer/publish-new-symbols.ps1 deleted file mode 100644 index 7fab9e1280..0000000000 --- a/resources/teamcity/developer/publish-new-symbols.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -# Keyman is copyright (C) SIL Global. MIT License. -# -# This script should be the identical for nightly/beta/stable for a given platform -# - -$ErrorActionPreference = "Stop" - -$RSYNC_HOME = $env:RSYNC_HOME -$RSYNC_PATH = $env:RSYNC_PATH -$RSYNC_USER = $env:RSYNC_USER -$RSYNC_HOST = $env:RSYNC_HOST -$RSYNC_ROOT = $env:RSYNC_ROOT -$USERPROFILE = $env:USERPROFILE - -# Rename 000Admin to 000admin - -ren 000Admin 000admin_ -ren 000admin_ 000admin - -# -# Upload with rsync to downloads.keyman.com -# (rsync requires that we are in the symbols folder to get folders in -# sync correctly; it is possible to resolve this but easier to just cd.) -# - -$rsync_args = @( - '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions - '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security - '--stats', # show statistics for log - "--rsync-path=""${RSYNC_PATH}""", # path on remote server - "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh - ".", # upload the whole symbols folder - "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/windows/symbols/" # target server + path -) - -& ${RSYNC_HOME}\rsync.exe ${rsync_args} -if (${LASTEXITCODE} -ne 0) { throw "Exit code is ${LASTEXITCODE}" } - -# EOF diff --git a/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 b/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 deleted file mode 100644 index 75bd18ba8c..0000000000 --- a/resources/teamcity/developer/publish-to-downloads-keyman-com.ps1 +++ /dev/null @@ -1,223 +0,0 @@ -# Keyman is copyright (C) SIL Global. MIT License. - -$ErrorActionPreference = "Stop" - -$tier = Get-Content ..\TIER.md -$build_number = Get-Content ..\VERSION.md -$build_counter = ${build_number} -replace "^\d+\.\d+\.(\d+)$", '$1' -#$msi_major_version = ${build_number} -replace "^(\d+)\.(\d+)\.(\d+)$", '$1$2' - -if ( ${tier} -eq "alpha" -or ${tier} -eq "beta") { - $version_tag = ${tier} - $version_with_tag = ${build_number} # we may go with ${build_number} + "-" + ${version_tag} in the future one day -} else { - $version_tag = "" - $version_with_tag = ${build_number} -} - -$upload_path = "upload\${build_number}" -$keyboards_path = "${upload_path}\keyboards" - -# Keyman Developer installers -$kmcomp_zip = "kmcomp-${version_with_tag}.zip" -$keyman_developer_exe = "keymandeveloper-${version_with_tag}.exe" - -#$setup_exe = "setup.exe" - -# Debug files -$debug_zip = "debug-${build_number}.zip" - -$SEVENZ_HOME = $env:SEVENZ_HOME -$RSYNC_HOME = $env:RSYNC_HOME -$RSYNC_PATH = $env:RSYNC_PATH -$RSYNC_USER = $env:RSYNC_USER -$RSYNC_HOST = $env:RSYNC_HOST -$RSYNC_ROOT = $env:RSYNC_ROOT -$USERPROFILE = $env:USERPROFILE - -########################################################################################### -## Developer upload -########################################################################################### - -# -# Preparation -# - -if((Test-Path ${upload_path}) -ne 0) { - Remove-Item -Path ${upload_path} -Recurse -} - -mkdir ${upload_path} - -# -# Build Keyman Compiler WINE archive -# - -if((Test-Path ..\release\${kmcomp_zip}) -ne 0) { - copy ..\release\${kmcomp_zip} ..\${upload_path}\${kmcomp_zip} -} else { - cd bin - if((Test-Path ..\..\common\schemas\keyboard_info\keyboard_info.source.json) -ne 0) { - # Keyman versions through -16.0 - copy ..\..\common\schemas\keyboard_info\keyboard_info.source.json . - copy ..\..\common\schemas\keyboard_info\keyboard_info.distribution.json . - & "${SEVENZ_HOME}\7z.exe" a -bd -bb0 ..\${upload_path}\${kmcomp_zip} kmcomp.exe kmcmpdll.dll kmcomp.x64.exe kmcmpdll.x64.dll kmconvert.exe keyboard_info.source.json keyboard_info.distribution.json xml\layoutbuilder\*.keyman-touch-layout projects\ - - # Add Keyman Developer Server to the archive (15.0 late alpha - after 171) - if((Test-Path ..\src\server\build) -ne 0) { - copy ..\src\server\build\ server\ -Recurse - & "${SEVENZ_HOME}\7z.exe" a -bd -bb0 ..\${upload_path}\${kmcomp_zip} server\ - } - } else { - # Keyman versions 17.0+; note, use npm install for most modules - copy ..\..\common\schemas\keyboard_info\keyboard_info.schema.json . - & "${SEVENZ_HOME}\7z.exe" a -bd -bb0 ..\${upload_path}\${kmcomp_zip} kmconvert.exe keyboard_info.schema.json xml\layoutbuilder\*.keyman-touch-layout projects\ server\ - } - cd .. -} - -# -# Copy source files -# - -copy release\${build_number}\${keyman_developer_exe} ${upload_path}\${keyman_developer_exe} - -# -# Construct keyman_developer.download_info -# - -$hash = get-filehash ${upload_path}\${keyman_developer_exe} -Algorithm MD5 - -$keyman_developer_download_info = @" -{ - "name": "Keyman Developer", - "version": "${version_with_tag}", - "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", - "platform": "win", - "stability": "${tier}", - "file": "keymandeveloper-${version_with_tag}.exe", - "md5": "$(${hash}.Hash)", - "type": "exe", - "build": "${build_counter}" -} -"@ - -[System.IO.File]::WriteAllLines("${pwd}\${upload_path}\${keyman_developer_exe}.download_info", ${keyman_developer_download_info}) - -# -# Construct kmcomp.download_info -# - -$hash = get-filehash ${upload_path}\${kmcomp_zip} -Algorithm MD5 - -$kmcomp_download_info = @" -{ - "name": "Keyman Developer Command-Line Compiler", - "version": "${version_with_tag}", - "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", - "platform": "win", - "stability": "${tier}", - "file": "kmcomp-${version_with_tag}.zip", - "md5": "$(${hash}.Hash)", - "type": "zip", - "build": "${build_counter}" -} -"@ - -[System.IO.File]::WriteAllLines("${pwd}\${upload_path}\${kmcomp_zip}.download_info", ${kmcomp_download_info}) - -# -# Copy common/test/keyboards/*/build/*.kmp to keyboards/ -# -if((Test-Path ..\common\test\keyboards) -ne 0) { - mkdir ${keyboards_path} - Copy-Item ..\common\test\keyboards\*\build\*.kmp ${keyboards_path}\ -} - -# -# Upload with rsync to downloads.keyman.com -# (rsync requires that we are in the upload folder to get folders in -# sync correctly; it is possible to resolve this but easier to just cd.) -# - -$rsync_args = @( - '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions - '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security - '--stats', # show statistics for log - "--rsync-path=""${RSYNC_PATH}""", # path on remote server - "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh - "${build_number}", # upload the whole build folder - "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/developer/${tier}/" # target server + path -) - -# Write-Output "rsync parameters:" ${rsync_args} - -cd upload -& ${RSYNC_HOME}\rsync.exe ${rsync_args} -if( ${LASTEXITCODE} -ne 0 ) { - Write-Output "rsync failed." - exit 99 -} -cd .. - -################# -# DEBUG.ZIP -################# - -if((Test-Path release\${build_number}\${debug_zip}) -ne 0) { - # - # Copy source files - # - - copy release\${build_number}\${debug_zip} ${upload_path}\${debug_zip} - - # - # Construct debug-.download_info - # - - $hash = get-filehash ${upload_path}\${debug_zip} -Algorithm MD5 - - $debug_zip_download_info = @" -{ - "name": "Keyman Developer debug files", - "version": "${version_with_tag}", - "date": "$([DateTime]::Now.ToString("yyyy-MM-dd"))", - "platform": "win", - "stability": "${tier}", - "file": "debug-${build_number}.zip", - "md5": "$(${hash}.Hash)", - "type": "zip", - "build": "${build_counter}" -} -"@ - - [System.IO.File]::WriteAllLines("${pwd}\${upload_path}\${debug_zip}.download_info", ${debug_zip_download_info}) -} - -# -# Upload with rsync to downloads.keyman.com -# (rsync requires that we are in the upload folder to get folders in -# sync correctly; it is possible to resolve this but easier to just cd.) -# - -$rsync_args = @( - '-vrzltp', # verbose, recurse, zip, copy symlinks, preserve times, permissions - '--chmod=Dug=rwx,Do=rx,Fug=rw,Fo=r', # map Windows security to host security - '--stats', # show statistics for log - "--rsync-path=""${RSYNC_PATH}""", # path on remote server - "--rsh=${RSYNC_HOME}\ssh -i ${USERPROFILE}\.ssh\id_rsa -o UserKnownHostsFile=${USERPROFILE}\.ssh\known_hosts", # use ssh - "${build_number}", # upload the whole build folder - "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/developer/${tier}/" # target server + path -) - -# Write-Output "rsync parameters:" ${rsync_args} - -cd upload -& ${RSYNC_HOME}\rsync.exe ${rsync_args} -if( ${LASTEXITCODE} -ne 0 ) { - Write-Output "rsync failed." - exit 99 -} -cd .. - -# EOF From c9a5042a5c9f0d19d681518016aa80602b39211e Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Wed, 2 Jul 2025 14:01:41 -0400 Subject: [PATCH 090/262] auto: increment master version to 19.0.76 --- HISTORY.md | 5 +++++ VERSION.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index a59bc803dd..c76f95dbb9 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # Keyman Version History +## 19.0.75 alpha 2025-07-02 + +* feat(resources): support ignoring unknown options in builder (#14216) +* maint(common): consolidate 7Z_HOME, SEVENZ_HOME, SEVEN_Z_HOME env vars (#14162) + ## 19.0.74 alpha 2025-07-01 * maint(web): adjust rsync path (#14249) diff --git a/VERSION.md b/VERSION.md index 02157961f3..ba0827f2cf 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.75 \ No newline at end of file +19.0.76 \ No newline at end of file From 6c4b3b3d1050890cd5c36c7d9bc6086dc1378bcb Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 3 Jul 2025 12:41:43 +0200 Subject: [PATCH 091/262] Update resources/teamcity/windows/keyman-windows-release.sh Co-authored-by: Marc Durdin --- resources/teamcity/windows/keyman-windows-release.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/teamcity/windows/keyman-windows-release.sh b/resources/teamcity/windows/keyman-windows-release.sh index 4a6f531064..7d1b79b4e0 100755 --- a/resources/teamcity/windows/keyman-windows-release.sh +++ b/resources/teamcity/windows/keyman-windows-release.sh @@ -84,6 +84,7 @@ function _publish_to_downloads_keyman_com() { write_download_info "${UPLOAD_PATH}" "${FIRSTVOICES_EXE}" "FirstVoices Keyboards" exe win write_download_info "${UPLOAD_PATH}" "${FIRSTVOICES_MSI}" "FirstVoices Keyboards MSI installer" msi win + # TODO: is this still needed? if [[ -f "release/${KEYMAN_VERSION}/${DEBUG_ZIP}" ]]; then cp "release/${KEYMAN_VERSION}/${DEBUG_ZIP}" "${UPLOAD_PATH}" write_download_info "${UPLOAD_PATH}" "${DEBUG_ZIP}" "Keyman Desktop and Keyman Developer debug files" zip win From d981e0a5c0cb1191aa2136bd926669fa9551e96c Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 3 Jul 2025 12:45:08 +0200 Subject: [PATCH 092/262] Apply suggestions from code review Co-authored-by: Marc Durdin --- resources/teamcity/common/common-test-core-wasm.sh | 2 +- resources/teamcity/common/common-test-core.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/teamcity/common/common-test-core-wasm.sh b/resources/teamcity/common/common-test-core-wasm.sh index ceeefd83b2..5108bcf3a9 100755 --- a/resources/teamcity/common/common-test-core-wasm.sh +++ b/resources/teamcity/common/common-test-core-wasm.sh @@ -19,4 +19,4 @@ builder_describe \ builder_parse "$@" -builder_run_action build "${KEYMAN_ROOT}/core/build.sh" configure:wasm build:wasm test:wasm +builder_run_action all "${KEYMAN_ROOT}/core/build.sh" configure,build,test:wasm diff --git a/resources/teamcity/common/common-test-core.sh b/resources/teamcity/common/common-test-core.sh index 236e4c2cee..57bc8a1c2c 100755 --- a/resources/teamcity/common/common-test-core.sh +++ b/resources/teamcity/common/common-test-core.sh @@ -34,9 +34,9 @@ else fi function do_all() { - "${KEYMAN_ROOT}/core/build.sh" configure:${ARCH} build:${ARCH} test:${ARCH} + "${KEYMAN_ROOT}/core/build.sh" configure,build,test:${ARCH} if is_windows; then - "${KEYMAN_ROOT}/core/build.sh" configure:x64 build:x64 test:x64 + "${KEYMAN_ROOT}/core/build.sh" configure,build,test:x64 fi } From 5969d8e6938b3bbca8501981cd0b4a242f7ab7bc Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 3 Jul 2025 12:47:52 +0200 Subject: [PATCH 093/262] Apply suggestions from code review Co-authored-by: Marc Durdin --- web/src/engine/predictive-text/templates/src/trie-model.ts | 4 +--- .../predictive-text/wordbreakers/src/main/default/index.ts | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/web/src/engine/predictive-text/templates/src/trie-model.ts b/web/src/engine/predictive-text/templates/src/trie-model.ts index 66e276920c..d22dc91e58 100644 --- a/web/src/engine/predictive-text/templates/src/trie-model.ts +++ b/web/src/engine/predictive-text/templates/src/trie-model.ts @@ -303,8 +303,7 @@ export default class TrieModel implements LexicalModel { } predict(transform: Transform, context: Context): Distribution { - //#region Helper function - function makeDistribution(suggestions: WithOutcome[]): Distribution { + const makeDistribution = function(suggestions: WithOutcome[]): Distribution { const distribution: Distribution = []; for (const s of suggestions) { @@ -313,7 +312,6 @@ export default class TrieModel implements LexicalModel { return distribution; } - //#endregion Helper function // Special-case the empty buffer/transform: return the top suggestions. if (!transform.insert && !context.left && !context.right && context.startOfBuffer && context.endOfBuffer) { diff --git a/web/src/engine/predictive-text/wordbreakers/src/main/default/index.ts b/web/src/engine/predictive-text/wordbreakers/src/main/default/index.ts index 3036a2b23d..c807c4ab2a 100644 --- a/web/src/engine/predictive-text/wordbreakers/src/main/default/index.ts +++ b/web/src/engine/predictive-text/wordbreakers/src/main/default/index.ts @@ -293,7 +293,6 @@ function isNonSpace(chunk: string, options?: DefaultWordBreakerOptions): boolean * @param text Text to find word boundaries in. */ function findBoundaries(text: string, options?: DefaultWordBreakerOptions): number[] { - //#region Internal utility functions /** * Returns the position of the start of the next scalar value. This jumps * over surrogate pairs. @@ -301,7 +300,7 @@ function findBoundaries(text: string, options?: DefaultWordBreakerOptions): numb * If asked for the character AFTER the end of the string, this always * returns the length of the string. */ - function positionAfter(pos: number): number { + const positionAfter = function(pos: number): number { if (pos >= text.length) { return text.length; } else if (isStartOfSurrogatePair(text[pos])) { @@ -309,7 +308,6 @@ function findBoundaries(text: string, options?: DefaultWordBreakerOptions): numb } return pos + 1; } - //#endregion Internal utility functions // WB1 and WB2: no boundaries if given an empty string. if (text.length === 0) { From e3ad0975a93e136340aaf724309715d6ff01f9d7 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 3 Jul 2025 13:26:29 +0200 Subject: [PATCH 094/262] maint(android): address code review comments Co-authored-by: Marc Durdin --- .../teamcity/android/android-actions.inc.sh | 5 ++- .../android/keyman-android-release.sh | 15 +++++---- .../android/keyman-android-test-samples.sh | 33 +++---------------- .../teamcity/android/keyman-android-test.sh | 4 +-- 4 files changed, 18 insertions(+), 39 deletions(-) diff --git a/resources/teamcity/android/android-actions.inc.sh b/resources/teamcity/android/android-actions.inc.sh index 630c12e239..4e0273c5fc 100755 --- a/resources/teamcity/android/android-actions.inc.sh +++ b/resources/teamcity/android/android-actions.inc.sh @@ -10,6 +10,9 @@ android_clean_action() { android_build_action() { builder_echo start "build" "Building Keyman for Android" - "${KEYMAN_ROOT}/android/build.sh" configure build,test:${TARGETS} --debug --ci + local TARGETS="$1" + + # REVIEW: is it deliberate that we `configure` all targets but only `build,test` `$TARGETS`? + "${KEYMAN_ROOT}/android/build.sh" configure build,test:"${TARGETS}" --debug --ci builder_echo end "build" success "Finished building Keyman for Android" } diff --git a/resources/teamcity/android/keyman-android-release.sh b/resources/teamcity/android/keyman-android-release.sh index 54035bd0ff..31dd0d9c4e 100755 --- a/resources/teamcity/android/keyman-android-release.sh +++ b/resources/teamcity/android/keyman-android-release.sh @@ -20,6 +20,11 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ################################ Main script ################################ +# ENHANCE: In the future if we ever have OEM builds other than FV, we could go +# with `--oem=OEM` instead of `--fv` where $OEM could be a comma separated +# list, e.g. `firstvoices`. +# See https://github.com/keymanapp/keyman/pull/14222/files#r2181208750. + builder_describe \ "Build release of Keyman for Android" \ "all run all actions" \ @@ -87,13 +92,9 @@ function _publish_to_playstore() { builder_echo end "publish to Google Play Store" success "Finished publishing release to Google Play Store" } -function do_build() { - android_build_action -} - function do_publish() { if ! is_windows; then - # requires Powershell, so currently only supported on Windows + # currently only tested on Windows, TODO: test cross-platform builder_echo error "This script is intended to be run on Windows only." return 1 fi @@ -119,10 +120,10 @@ fi if builder_has_action all; then android_clean_action - do_build + android_build_action "${TARGETS}" do_publish else builder_run_action clean android_clean_action - builder_run_action build do_build + builder_run_action build android_build_action "${TARGETS}" builder_run_action publish do_publish fi diff --git a/resources/teamcity/android/keyman-android-test-samples.sh b/resources/teamcity/android/keyman-android-test-samples.sh index 4306e744cc..ad429df2f0 100755 --- a/resources/teamcity/android/keyman-android-test-samples.sh +++ b/resources/teamcity/android/keyman-android-test-samples.sh @@ -22,41 +22,16 @@ builder_describe \ "Build KMSample1, KMSample2, and Test projects" \ "all run all actions" \ "clean clean artifact directories" \ - "build configure, build and test Keyman for Android" + "build configure and build Keyman for Android samples" builder_parse "$@" cd "${KEYMAN_ROOT}/android" -function _build_kmea() { - builder_echo start "build" "Configuring and building KMEA" - "${KEYMAN_ROOT}/android/build.sh" configure:engine build:engine --debug --ci - builder_echo end "build" success "Finished configuring and building KMEA" -} - -function _build_kmsample1() { - builder_echo start "build" "Building KMSample1" - "${KEYMAN_ROOT}/android/build.sh" build:sample1 --debug --ci - builder_echo end "build" success "Finished building KMSample1" -} - -function _build_kmsample2() { - builder_echo start "build" "Building KMSample2" - "${KEYMAN_ROOT}/android/build.sh" build:sample2 --debug --ci - builder_echo end "build" success "Finished building KMSample2" -} - -function _build_keyboardharness() { - builder_echo start "build" "Building KeyboardHarness" - "${KEYMAN_ROOT}/android/build.sh" build:keyboardharness --debug --ci - builder_echo end "build" success "Finished building KeyboardHarness" -} - function do_build() { - _build_kmea - _build_kmsample1 - _build_kmsample2 - _build_keyboardharness + "${KEYMAN_ROOT}/android/build.sh" \ + configure,build:kmea,kmsample1,kmsample2,keyboardharness \ + --debug --ci } if builder_has_action all; then diff --git a/resources/teamcity/android/keyman-android-test.sh b/resources/teamcity/android/keyman-android-test.sh index 00b282e0fd..8a531a2bfe 100755 --- a/resources/teamcity/android/keyman-android-test.sh +++ b/resources/teamcity/android/keyman-android-test.sh @@ -38,8 +38,8 @@ fi if builder_has_action all; then android_clean_action - android_build_action + android_build_action "${TARGETS}" else builder_run_action clean android_clean_action - builder_run_action build android_build_action + builder_run_action build android_build_action "${TARGETS}" fi From 1bb3f96702fc876efb9e96779b057fe010396850 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 3 Jul 2025 13:32:45 +0200 Subject: [PATCH 095/262] Apply suggestions from code review Co-authored-by: Marc Durdin --- resources/teamcity/ios/keyman-ios-test-samples.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/resources/teamcity/ios/keyman-ios-test-samples.sh b/resources/teamcity/ios/keyman-ios-test-samples.sh index d50a390177..0146c7498f 100755 --- a/resources/teamcity/ios/keyman-ios-test-samples.sh +++ b/resources/teamcity/ios/keyman-ios-test-samples.sh @@ -35,5 +35,7 @@ function _build_sample2() { builder_echo end "kmsample2" success "Finished building KMSample2" } -_build_sample1 -_build_sample2 +if builder_has_action all; then + _build_sample1 + _build_sample2 +fi From 841fd3efa1fd998477682201985ec3ec74ebdb7b Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 3 Jul 2025 17:13:17 +0200 Subject: [PATCH 096/262] maint(android): fix previous commit --- resources/teamcity/android/keyman-android-test-samples.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/teamcity/android/keyman-android-test-samples.sh b/resources/teamcity/android/keyman-android-test-samples.sh index ad429df2f0..2698ad914a 100755 --- a/resources/teamcity/android/keyman-android-test-samples.sh +++ b/resources/teamcity/android/keyman-android-test-samples.sh @@ -30,7 +30,7 @@ cd "${KEYMAN_ROOT}/android" function do_build() { "${KEYMAN_ROOT}/android/build.sh" \ - configure,build:kmea,kmsample1,kmsample2,keyboardharness \ + configure,build:engine,sample1,sample2,keyboardharness \ --debug --ci } From ecd3f9a1fa947e5659d5a0f5af365af3e7b59581 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 3 Jul 2025 17:38:41 +0200 Subject: [PATCH 097/262] maint(android): remove `--ci` parameter from android builds `--ci` is deprecated since the current code discovers automatically when we're building on CI, so we don't have to pass that option when calling the `android/build.sh` script. Also remove the option itself since we're no longer using it. Build-bot: build android Test-bot: skip --- android/build.sh | 1 - resources/teamcity/android/android-actions.inc.sh | 2 +- resources/teamcity/android/keyman-android-test-samples.sh | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/android/build.sh b/android/build.sh index 12017e2c6b..caeddb5b51 100755 --- a/android/build.sh +++ b/android/build.sh @@ -39,7 +39,6 @@ builder_describe \ test \ "publish Publishes symbols to Sentry and the APKs to the Play Store." \ "archive Copy release artifacts to upload/ and rsync to downloads.keyman" \ - "--ci+ Deprecated build option. Remove in 20.0" \ --upload-sentry+ \ ":engine=KMEA Keyman Engine for Android" \ ":app=KMAPro Keyman for Android" \ diff --git a/resources/teamcity/android/android-actions.inc.sh b/resources/teamcity/android/android-actions.inc.sh index 4e0273c5fc..e6337d46bc 100755 --- a/resources/teamcity/android/android-actions.inc.sh +++ b/resources/teamcity/android/android-actions.inc.sh @@ -13,6 +13,6 @@ android_build_action() { local TARGETS="$1" # REVIEW: is it deliberate that we `configure` all targets but only `build,test` `$TARGETS`? - "${KEYMAN_ROOT}/android/build.sh" configure build,test:"${TARGETS}" --debug --ci + "${KEYMAN_ROOT}/android/build.sh" configure build,test:"${TARGETS}" --debug builder_echo end "build" success "Finished building Keyman for Android" } diff --git a/resources/teamcity/android/keyman-android-test-samples.sh b/resources/teamcity/android/keyman-android-test-samples.sh index 2698ad914a..1faa1b6c66 100755 --- a/resources/teamcity/android/keyman-android-test-samples.sh +++ b/resources/teamcity/android/keyman-android-test-samples.sh @@ -31,7 +31,7 @@ cd "${KEYMAN_ROOT}/android" function do_build() { "${KEYMAN_ROOT}/android/build.sh" \ configure,build:engine,sample1,sample2,keyboardharness \ - --debug --ci + --debug } if builder_has_action all; then From 5a1bf527272e20ea78d93811ccbf85d9e60ab0f8 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 3 Jul 2025 13:10:42 +0200 Subject: [PATCH 098/262] refactor(resources): replace shebang for include files Include files are not directly executable, so it doesn't make sense to have the shebang line. However, shellcheck needs to know what shell we use. Turns out there is a shellcheck line that we can add instead of the shebang line. Follow-up-of: #14253 Build-bot: skip Test-bot: skip --- resources/teamcity/android/android-actions.inc.sh | 2 +- resources/teamcity/developer/developer-actions.inc.sh | 2 +- resources/teamcity/includes/tc-actions.inc.sh | 2 +- resources/teamcity/includes/tc-download-info.inc.sh | 2 +- resources/teamcity/includes/tc-helpers.inc.sh | 2 +- resources/teamcity/includes/tc-linux.inc.sh | 2 +- resources/teamcity/includes/tc-mac.inc.sh | 2 +- resources/teamcity/includes/tc-windows.inc.sh | 2 +- resources/teamcity/ios/ios-actions.inc.sh | 2 +- resources/teamcity/macos/macos-actions.inc.sh | 2 +- resources/teamcity/web/web-actions.inc.sh | 2 +- resources/teamcity/windows/windows-actions.inc.sh | 1 + 12 files changed, 12 insertions(+), 11 deletions(-) diff --git a/resources/teamcity/android/android-actions.inc.sh b/resources/teamcity/android/android-actions.inc.sh index 4e0273c5fc..18cfc59ef5 100755 --- a/resources/teamcity/android/android-actions.inc.sh +++ b/resources/teamcity/android/android-actions.inc.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +# shellcheck shell=bash # Keyman is copyright (C) SIL Global. MIT License. android_clean_action() { diff --git a/resources/teamcity/developer/developer-actions.inc.sh b/resources/teamcity/developer/developer-actions.inc.sh index 66cb82756a..c7fef7a5f4 100644 --- a/resources/teamcity/developer/developer-actions.inc.sh +++ b/resources/teamcity/developer/developer-actions.inc.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +# shellcheck shell=bash # Keyman is copyright (C) SIL Global. MIT License. developer_install_dependencies_on_linux_action() { diff --git a/resources/teamcity/includes/tc-actions.inc.sh b/resources/teamcity/includes/tc-actions.inc.sh index 0834c32f43..ed06865178 100644 --- a/resources/teamcity/includes/tc-actions.inc.sh +++ b/resources/teamcity/includes/tc-actions.inc.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +# shellcheck shell=bash # Keyman is copyright (C) SIL Global. MIT License. # Run the clean action in the `linux` directory.` diff --git a/resources/teamcity/includes/tc-download-info.inc.sh b/resources/teamcity/includes/tc-download-info.inc.sh index 25755af877..c1e596a394 100644 --- a/resources/teamcity/includes/tc-download-info.inc.sh +++ b/resources/teamcity/includes/tc-download-info.inc.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +# shellcheck shell=bash # Keyman is copyright (C) SIL Global. MIT License. # shellcheck disable=SC2154 diff --git a/resources/teamcity/includes/tc-helpers.inc.sh b/resources/teamcity/includes/tc-helpers.inc.sh index 7ec2c3d98a..5a02face40 100644 --- a/resources/teamcity/includes/tc-helpers.inc.sh +++ b/resources/teamcity/includes/tc-helpers.inc.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +# shellcheck shell=bash # Keyman is copyright (C) SIL Global. MIT License. # Returns 0 if we're running on Ubuntu. diff --git a/resources/teamcity/includes/tc-linux.inc.sh b/resources/teamcity/includes/tc-linux.inc.sh index 8427ba765e..8e8db447a0 100644 --- a/resources/teamcity/includes/tc-linux.inc.sh +++ b/resources/teamcity/includes/tc-linux.inc.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +# shellcheck shell=bash # Keyman is copyright (C) SIL Global. MIT License. # Returns 0 if the OS version is greater than or equal to the specified version. diff --git a/resources/teamcity/includes/tc-mac.inc.sh b/resources/teamcity/includes/tc-mac.inc.sh index 911fec97ff..69d805bb0a 100644 --- a/resources/teamcity/includes/tc-mac.inc.sh +++ b/resources/teamcity/includes/tc-mac.inc.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +# shellcheck shell=bash # Keyman is copyright (C) SIL Global. MIT License. ba_mac_unlock_keychain() { diff --git a/resources/teamcity/includes/tc-windows.inc.sh b/resources/teamcity/includes/tc-windows.inc.sh index b8d840a61b..9d7a637025 100644 --- a/resources/teamcity/includes/tc-windows.inc.sh +++ b/resources/teamcity/includes/tc-windows.inc.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +# shellcheck shell=bash # Keyman is copyright (C) SIL Global. MIT License. download_symbol_server_index() { diff --git a/resources/teamcity/ios/ios-actions.inc.sh b/resources/teamcity/ios/ios-actions.inc.sh index 00b82b7f5c..acf904fe0f 100755 --- a/resources/teamcity/ios/ios-actions.inc.sh +++ b/resources/teamcity/ios/ios-actions.inc.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +# shellcheck shell=bash # Keyman is copyright (C) SIL Global. MIT License. ios_build() { diff --git a/resources/teamcity/macos/macos-actions.inc.sh b/resources/teamcity/macos/macos-actions.inc.sh index a463db4612..84095beac7 100755 --- a/resources/teamcity/macos/macos-actions.inc.sh +++ b/resources/teamcity/macos/macos-actions.inc.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +# shellcheck shell=bash # Keyman is copyright (C) SIL Global. MIT License. macos_clean_action() { diff --git a/resources/teamcity/web/web-actions.inc.sh b/resources/teamcity/web/web-actions.inc.sh index 60665e01f2..e464c78940 100644 --- a/resources/teamcity/web/web-actions.inc.sh +++ b/resources/teamcity/web/web-actions.inc.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +# shellcheck shell=bash # Keyman is copyright (C) SIL Global. MIT License. web_install_dependencies_on_linux_action() { diff --git a/resources/teamcity/windows/windows-actions.inc.sh b/resources/teamcity/windows/windows-actions.inc.sh index e8553bef0f..3b5699447c 100755 --- a/resources/teamcity/windows/windows-actions.inc.sh +++ b/resources/teamcity/windows/windows-actions.inc.sh @@ -1,3 +1,4 @@ +# shellcheck shell=bash # Keyman is copyright (C) SIL Global. MIT License. windows_build_action() { From 69eede0a2871026b3125bfbf8ea2ec8c70ac3c1f Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 3 Jul 2025 18:48:47 +0200 Subject: [PATCH 099/262] maint(resources): use `tc_` prefix for all `tc-*.inc.sh` functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - added `tc_` prefix to functions in `includes/tc-*.inc.sh` - moved `includes/tc-actions.inc.sh` → `linux/linux-actions.inc.sh` - didn't change `write_download_info`. That will be done as part of #14259. - didn't change `is_*`. That will be done as part of #14109. - removed unnecessary functions that only call another function. - moved functions from `tc-windows.inc.sh` → `tc-helpers.inc.sh` because they are no longer Windows-specific (although currently only used for Windows). Fixes: #14201 Test-bot: skip --- .../android/keyman-android-release.sh | 2 +- .../developer/developer-actions.inc.sh | 4 +- .../developer/keyman-developer-release.sh | 31 +--------- .../keyman-developer-test-linux-macos.sh | 9 ++- resources/teamcity/includes/tc-helpers.inc.sh | 58 ++++++++++++++----- resources/teamcity/includes/tc-linux.inc.sh | 15 +---- resources/teamcity/includes/tc-windows.inc.sh | 30 ---------- resources/teamcity/ios/keyman-ios-release.sh | 2 +- .../teamcity/linux/keyman-linux-release.sh | 6 +- .../linux/keyman-linux-test-integration.sh | 17 ++++-- resources/teamcity/linux/keyman-linux-test.sh | 6 +- .../linux-actions.inc.sh} | 11 +--- .../teamcity/macos/keyman-macos-release.sh | 2 +- resources/teamcity/web/keyman-web-release.sh | 6 +- resources/teamcity/web/keyman-web-test.sh | 4 +- .../windows/keyman-windows-release.sh | 7 +-- 16 files changed, 86 insertions(+), 124 deletions(-) delete mode 100644 resources/teamcity/includes/tc-windows.inc.sh rename resources/teamcity/{includes/tc-actions.inc.sh => linux/linux-actions.inc.sh} (91%) diff --git a/resources/teamcity/android/keyman-android-release.sh b/resources/teamcity/android/keyman-android-release.sh index 31dd0d9c4e..bd0aa14b44 100755 --- a/resources/teamcity/android/keyman-android-release.sh +++ b/resources/teamcity/android/keyman-android-release.sh @@ -106,7 +106,7 @@ function do_publish() { _publish_to_downloads_keyman_com _publish_to_playstore - upload_help "Keyman for Android" android + tc_upload_help "Keyman for Android" android } if builder_has_option --fv; then diff --git a/resources/teamcity/developer/developer-actions.inc.sh b/resources/teamcity/developer/developer-actions.inc.sh index 66cb82756a..73e1a325df 100644 --- a/resources/teamcity/developer/developer-actions.inc.sh +++ b/resources/teamcity/developer/developer-actions.inc.sh @@ -9,8 +9,8 @@ developer_install_dependencies_on_linux_action() { builder_echo start "install dependencies" "Installing dependencies" ba_linux_check_and_install_packages devscripts jq meson - install_nvm - install_emscripten + ba_linux_install_nvm + ba_linux_install_emscripten builder_echo end "install dependencies" success "Finished installing dependencies" } diff --git a/resources/teamcity/developer/keyman-developer-release.sh b/resources/teamcity/developer/keyman-developer-release.sh index ca42536a4b..59d9f763c5 100755 --- a/resources/teamcity/developer/keyman-developer-release.sh +++ b/resources/teamcity/developer/keyman-developer-release.sh @@ -15,7 +15,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" # shellcheck disable=SC2154 . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-download-info.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" -. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-windows.inc.sh" ################################ Main script ################################ @@ -64,30 +63,6 @@ function _publish_sentry() { builder_echo end "publish sentry" success "Finished publishing debug information files to Sentry" } -function _download_symbol_server_index() { - # Download symbol server index from symbol server - builder_echo start "download symbol server index" "Downloading symbol server index" - ( - mkdir -p "${LOCAL_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}" - cd "${LOCAL_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}" - - tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/lastid.txt" "." - tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/history.txt" "." - tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/server.txt" "." - ) - builder_echo end "download symbol server index" success "Finished downloading symbol server index" -} - -function _publish_new_symbols() { - # Publish new symbols to symbol server - builder_echo start "publish new symbols" "Publishing new symbols to symbol server" - ( - cd "${LOCAL_SYMBOLS_PATH}" - tc_rsync_upload "." "${REMOTE_SYMBOLS_PATH}" - ) - builder_echo end "publish new symbols" success "Finished publishing new symbols to symbol server" -} - function _publish_to_downloads_keyman_com() { # Publish to downloads.keyman.com builder_echo start "publish to downloads.keyman.com" "Publishing release to downloads.keyman.com" @@ -155,10 +130,10 @@ function publish_action() { export RSYNC_ROOT _publish_sentry - download_symbol_server_index - publish_new_symbols + tc_download_symbol_server_index + tc_publish_new_symbols _publish_to_downloads_keyman_com - upload_help "api documentation" developer + tc_upload_help "api documentation" developer } if builder_has_action all; then diff --git a/resources/teamcity/developer/keyman-developer-test-linux-macos.sh b/resources/teamcity/developer/keyman-developer-test-linux-macos.sh index c273873fe0..d6db7a4c87 100755 --- a/resources/teamcity/developer/keyman-developer-test-linux-macos.sh +++ b/resources/teamcity/developer/keyman-developer-test-linux-macos.sh @@ -14,7 +14,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" # shellcheck disable=SC2154 . "${KEYMAN_ROOT}/resources/teamcity/developer/developer-actions.inc.sh" -. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-actions.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-linux.inc.sh" @@ -46,15 +45,15 @@ fi if builder_has_action all; then developer_install_dependencies_on_linux_action - set_variables_for_nvm - set_variables_for_emscripten + tc_set_variables_for_nvm + tc_set_variables_for_emscripten build_developer_action else builder_run_action configure developer_install_dependencies_on_linux_action - set_variables_for_nvm - set_variables_for_emscripten + tc_set_variables_for_nvm + tc_set_variables_for_emscripten builder_run_action build build_developer_action fi diff --git a/resources/teamcity/includes/tc-helpers.inc.sh b/resources/teamcity/includes/tc-helpers.inc.sh index 7ec2c3d98a..94a233ccba 100644 --- a/resources/teamcity/includes/tc-helpers.inc.sh +++ b/resources/teamcity/includes/tc-helpers.inc.sh @@ -29,18 +29,26 @@ is_macos() { fi } -install_nvm() { +# Returns 0 if the OS version is greater than or equal to the specified version. +# Parameter: +# $1 - OS version to compare against (e.g., "20.04") +is_os_version_or_higher() { if ! is_ubuntu; then - # on Windows and macOS build agents are configured manually - return 0 + builder_warning "This function is only implemented for Ubuntu" + return 0 # Not Ubuntu, so we assume the version check is not applicable. fi - ba_linux_install_nvm + + local OS_VERSION=$1 + + # we use `dpkg --compare-versions` to compare the current Ubuntu version + # shellcheck disable=SC2312 + dpkg --compare-versions "$(lsb_release -r -s)" ge "${OS_VERSION}" } # Set the environment variables required to use node/nvm and set the # `KEYMAN_USE_NVM` variable so that the build can automatically install # the required node version. -set_variables_for_nvm() { +tc_set_variables_for_nvm() { if [[ -f "${HOME}/.nvm/nvm.sh" ]] && [[ -d "${HOME}/.keyman/node" ]]; then # nvm.sh uses some variables that might not be initialized, so we # disable the "unbound variable" check temporarily @@ -54,20 +62,12 @@ set_variables_for_nvm() { fi } -install_emscripten() { - if ! is_ubuntu; then - # on Windows and macOS build agents are configured manually - return 0 - fi - ba_linux_install_emscripten -} - -set_variables_for_emscripten() { +tc_set_variables_for_emscripten() { export EMSCRIPTEN_BASE="${EMSCRIPTEN_BASE:-${HOME}/emsdk/upstream/emscripten}" export KEYMAN_USE_EMSDK=1 } -upload_help() { +tc_upload_help() { local PRODUCT=$1 local PRODUCT_PATH=$2 builder_echo start "upload help" "Uploading new ${PRODUCT} help to help.keyman.com" @@ -137,3 +137,31 @@ tc_rsync_upload() { "$1" \ "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/$2" } + +tc_download_symbol_server_index() { + # Download symbol server index from symbol server + builder_echo start "download symbol server index" "Downloading symbol server index" + ( + # shellcheck disable=SC2154 + mkdir -p "${LOCAL_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}" + # shellcheck disable=SC2164 + cd "${LOCAL_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}" + + # shellcheck disable=SC2154 + tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/lastid.txt" "." + tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/history.txt" "." + tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/server.txt" "." + ) + builder_echo end "download symbol server index" success "Finished downloading symbol server index" +} + +tc_publish_new_symbols() { + # Publish new symbols to symbol server + builder_echo start "publish new symbols" "Publishing new symbols to symbol server" + ( + # shellcheck disable=SC2164 + cd "${LOCAL_SYMBOLS_PATH}" + tc_rsync_upload "." "${REMOTE_SYMBOLS_PATH}" + ) + builder_echo end "publish new symbols" success "Finished publishing new symbols to symbol server" +} diff --git a/resources/teamcity/includes/tc-linux.inc.sh b/resources/teamcity/includes/tc-linux.inc.sh index 8427ba765e..797c698f29 100644 --- a/resources/teamcity/includes/tc-linux.inc.sh +++ b/resources/teamcity/includes/tc-linux.inc.sh @@ -1,17 +1,6 @@ #!/usr/bin/env bash # Keyman is copyright (C) SIL Global. MIT License. -# Returns 0 if the OS version is greater than or equal to the specified version. -# Parameter: -# $1 - OS version to compare against (e.g., "20.04") -is_os_version_or_higher() { - local OS_VERSION=$1 - - # we use `dpkg --compare-versions` to compare the current Ubuntu version - # shellcheck disable=SC2312 - dpkg --compare-versions "$(lsb_release -r -s)" ge "${OS_VERSION}" -} - # Returns 0 if the specified package is installed. # Parameter: # $1 - Package name to check (e.g., "curl") @@ -59,7 +48,7 @@ ba_linux_install_nvm() { NVM_RELEASE=$(curl -s https://api.github.com/repos/nvm-sh/nvm/releases/latest | grep tag_name | cut -d : -f 2 | cut -d '"' -f 2) # shellcheck disable=SC2312 curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_RELEASE}/install.sh | bash - set_variables_for_nvm + tc_set_variables_for_nvm nvm install --lts --default --save nvm use --lts fi @@ -85,7 +74,7 @@ ba_linux_install_emscripten() { ./emsdk install "${KEYMAN_MIN_VERSION_EMSCRIPTEN}" ./emsdk activate "${KEYMAN_MIN_VERSION_EMSCRIPTEN}" fi - set_variables_for_emscripten + tc_set_variables_for_emscripten builder_echo end "install emscripten" success "Finished checking and installing emscripten" } diff --git a/resources/teamcity/includes/tc-windows.inc.sh b/resources/teamcity/includes/tc-windows.inc.sh deleted file mode 100644 index b8d840a61b..0000000000 --- a/resources/teamcity/includes/tc-windows.inc.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash -# Keyman is copyright (C) SIL Global. MIT License. - -download_symbol_server_index() { - # Download symbol server index from symbol server - builder_echo start "download symbol server index" "Downloading symbol server index" - ( - # shellcheck disable=SC2154 - mkdir -p "${LOCAL_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}" - # shellcheck disable=SC2164 - cd "${LOCAL_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}" - - # shellcheck disable=SC2154 - tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/lastid.txt" "." - tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/history.txt" "." - tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/server.txt" "." - ) - builder_echo end "download symbol server index" success "Finished downloading symbol server index" -} - -publish_new_symbols() { - # Publish new symbols to symbol server - builder_echo start "publish new symbols" "Publishing new symbols to symbol server" - ( - # shellcheck disable=SC2164 - cd "${LOCAL_SYMBOLS_PATH}" - tc_rsync_upload "." "${REMOTE_SYMBOLS_PATH}" - ) - builder_echo end "publish new symbols" success "Finished publishing new symbols to symbol server" -} diff --git a/resources/teamcity/ios/keyman-ios-release.sh b/resources/teamcity/ios/keyman-ios-release.sh index e5fe7346e0..d8f3848289 100755 --- a/resources/teamcity/ios/keyman-ios-release.sh +++ b/resources/teamcity/ios/keyman-ios-release.sh @@ -244,7 +244,7 @@ function do_build() { function do_publish() { _publish_to_downloads_keyman_com - upload_help "Keyman for iOS" ios + tc_upload_help "Keyman for iOS" ios if [[ "${KEYMAN_TIER}" == "stable" ]]; then _publish_to_appstore diff --git a/resources/teamcity/linux/keyman-linux-release.sh b/resources/teamcity/linux/keyman-linux-release.sh index b9a9c47efb..a127078458 100755 --- a/resources/teamcity/linux/keyman-linux-release.sh +++ b/resources/teamcity/linux/keyman-linux-release.sh @@ -10,10 +10,10 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" # shellcheck disable=SC2154 . "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" -. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-actions.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-download-info.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-linux.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/linux/linux-actions.inc.sh" ################################ Main script ################################ @@ -141,7 +141,7 @@ if builder_has_action all; then linux_install_dependencies_action _install_additional_dependencies - set_variables_for_nvm + tc_set_variables_for_nvm linux_build_action linux_unit_tests_action --no-integration @@ -150,7 +150,7 @@ else builder_run_action configure linux_install_dependencies_action builder_run_action configure _install_additional_dependencies - set_variables_for_nvm + tc_set_variables_for_nvm builder_run_action build linux_build_action builder_run_action test linux_unit_tests_action --no-integration diff --git a/resources/teamcity/linux/keyman-linux-test-integration.sh b/resources/teamcity/linux/keyman-linux-test-integration.sh index 0868cbb835..b68ade47be 100755 --- a/resources/teamcity/linux/keyman-linux-test-integration.sh +++ b/resources/teamcity/linux/keyman-linux-test-integration.sh @@ -11,9 +11,9 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" # shellcheck disable=SC2154 . "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" -. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-actions.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-linux.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/linux/linux-actions.inc.sh" ################################ Main script ################################ @@ -29,19 +29,26 @@ builder_parse "$@" cd "${KEYMAN_ROOT}/linux" +# Run the clean action in the `linux` directory.` +function clean_action() { + builder_heading "Cleaning up" + # shellcheck disable=SC2154 + "${KEYMAN_ROOT}/linux/build.sh" clean +} + if builder_has_action all; then - linux_clean_action + clean_action linux_install_dependencies_action linux_additional_test_dependencies_action - set_variables_for_nvm + tc_set_variables_for_nvm linux_build_action linux_unit_tests_action else - builder_run_action clean linux_clean_action + builder_run_action clean clean_action builder_run_action configure linux_install_dependencies_action builder_run_action configure linux_additional_test_dependencies_action - set_variables_for_nvm + tc_set_variables_for_nvm builder_run_action build linux_build_action builder_run_action test linux_unit_tests_action diff --git a/resources/teamcity/linux/keyman-linux-test.sh b/resources/teamcity/linux/keyman-linux-test.sh index 7c8387d153..98298253f8 100755 --- a/resources/teamcity/linux/keyman-linux-test.sh +++ b/resources/teamcity/linux/keyman-linux-test.sh @@ -11,9 +11,9 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" # shellcheck disable=SC2154 . "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" -. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-actions.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-linux.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/linux/linux-actions.inc.sh" ################################ Main script ################################ @@ -40,7 +40,7 @@ if builder_has_action all; then linux_install_dependencies_action linux_additional_test_dependencies_action - set_variables_for_nvm + tc_set_variables_for_nvm linux_build_action --coverage linux_unit_tests_action --coverage --report --no-integration @@ -49,7 +49,7 @@ else builder_run_action configure linux_install_dependencies_action builder_run_action configure linux_additional_test_dependencies_action - set_variables_for_nvm + tc_set_variables_for_nvm builder_run_action build linux_build_action --coverage builder_run_action test linux_unit_tests_action --coverage --report --no-integration diff --git a/resources/teamcity/includes/tc-actions.inc.sh b/resources/teamcity/linux/linux-actions.inc.sh similarity index 91% rename from resources/teamcity/includes/tc-actions.inc.sh rename to resources/teamcity/linux/linux-actions.inc.sh index 0834c32f43..f3ad2ec02d 100644 --- a/resources/teamcity/includes/tc-actions.inc.sh +++ b/resources/teamcity/linux/linux-actions.inc.sh @@ -1,16 +1,11 @@ -#!/usr/bin/env bash +# shellcheck shell=bash # Keyman is copyright (C) SIL Global. MIT License. -# Run the clean action in the `linux` directory.` -linux_clean_action() { - builder_heading "Cleaning up" - # shellcheck disable=SC2154 - "${KEYMAN_ROOT}/linux/build.sh" clean -} - # Install required dependencies for building Keyman on Linux. linux_install_dependencies_action() { builder_echo start "install dependencies" "Installing dependencies" + + # shellcheck disable=SC2154 . "${KEYMAN_ROOT}/linux/scripts/package-build.inc.sh" checkAndInstallRequirements builder_echo end "install dependencies" success "Finished installing dependencies" diff --git a/resources/teamcity/macos/keyman-macos-release.sh b/resources/teamcity/macos/keyman-macos-release.sh index 1fad300c60..fb73185019 100755 --- a/resources/teamcity/macos/keyman-macos-release.sh +++ b/resources/teamcity/macos/keyman-macos-release.sh @@ -72,7 +72,7 @@ function _build_publish() { function _do_publish() { _build_publish _publish_to_downloads_keyman_com - upload_help "Keyman for macOS" mac + tc_upload_help "Keyman for macOS" mac } ba_mac_unlock_keychain diff --git a/resources/teamcity/web/keyman-web-release.sh b/resources/teamcity/web/keyman-web-release.sh index a79dff946b..c7c290b5e4 100755 --- a/resources/teamcity/web/keyman-web-release.sh +++ b/resources/teamcity/web/keyman-web-release.sh @@ -89,7 +89,7 @@ function publish_web_action() { _push_release_to_skeymancom _zip_and_upload_artifacts - upload_help "Keyman for Web" web + tc_upload_help "Keyman for Web" web builder_echo end publish success "Finished publishing KeymanWeb release" } @@ -97,14 +97,14 @@ function publish_web_action() { if builder_has_action all; then web_install_dependencies_on_linux_action - set_variables_for_nvm + tc_set_variables_for_nvm web_build_action publish_web_action else builder_run_action configure web_install_dependencies_on_linux_action - set_variables_for_nvm + tc_set_variables_for_nvm builder_run_action build web_build_action builder_run_action publish publish_web_action diff --git a/resources/teamcity/web/keyman-web-test.sh b/resources/teamcity/web/keyman-web-test.sh index 0996ab7ec3..33fbe707d7 100755 --- a/resources/teamcity/web/keyman-web-test.sh +++ b/resources/teamcity/web/keyman-web-test.sh @@ -40,7 +40,7 @@ function check_build_size_action() { if builder_has_action all; then web_install_dependencies_on_linux_action - set_variables_for_nvm + tc_set_variables_for_nvm web_build_action web_test_action @@ -48,7 +48,7 @@ if builder_has_action all; then else builder_run_action configure web_install_dependencies_on_linux_action - set_variables_for_nvm + tc_set_variables_for_nvm builder_run_action build web_build_action builder_run_action test web_test_action diff --git a/resources/teamcity/windows/keyman-windows-release.sh b/resources/teamcity/windows/keyman-windows-release.sh index 7d1b79b4e0..44cdda1230 100755 --- a/resources/teamcity/windows/keyman-windows-release.sh +++ b/resources/teamcity/windows/keyman-windows-release.sh @@ -16,7 +16,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-download-info.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" -. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-windows.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/windows/windows-actions.inc.sh" ################################ Main script ################################ @@ -107,10 +106,10 @@ function windows_publish_action() { "${KEYMAN_ROOT}/windows/build.sh" publish windows_upload_symbols_to_sentry - download_symbol_server_index - publish_new_symbols + tc_download_symbol_server_index + tc_publish_new_symbols _publish_to_downloads_keyman_com - upload_help "Keyman for Windows" windows + tc_upload_help "Keyman for Windows" windows builder_echo end "publish windows" success "Finished publishing Keyman for Windows" } From 4624a22038cf481ebd9983e1089a6d7482d51602 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Thu, 3 Jul 2025 14:03:01 -0400 Subject: [PATCH 100/262] auto: increment master version to 19.0.77 --- HISTORY.md | 14 ++++++++++++++ VERSION.md | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index c76f95dbb9..bb915863e7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,19 @@ # Keyman Version History +## 19.0.76 alpha 2025-07-03 + +* maint(common): move TC configuration for common tests (#14205) +* refactor(common): improve downloads include script (#14253) +* maint(web): move rsync code to shared functions (#14254) +* maint(developer): replace Powershell scripts (#14255) +* maint(windows): move TC configuration for Keyman Windows to scripts (#14186) +* feat(windows): human text message for 413 error (#14055) +* refactor(web): move helper functions to beginning of function (#14252) +* maint(android): add TC build scripts for Keyman for Android (#14222) +* maint(linux): use `tc_rsync_upload` and `write_download_info` functions (#14258) +* maint(ios): add TC build scripts for iOS (#14229) +* maint(mac): add TC build scripts for macOS (#14239) + ## 19.0.75 alpha 2025-07-02 * feat(resources): support ignoring unknown options in builder (#14216) diff --git a/VERSION.md b/VERSION.md index ba0827f2cf..9cba072656 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.76 \ No newline at end of file +19.0.77 \ No newline at end of file From 366bd1c855e6ffdf21ef77b595763ec1b694927c Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 3 Jul 2025 20:21:17 +0200 Subject: [PATCH 101/262] fix(core): fix failing `common-test-core.sh` script Also pass `win` for Windows which will build both x86 and x64. Test-bot: skip --- resources/teamcity/common/common-test-core.sh | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/resources/teamcity/common/common-test-core.sh b/resources/teamcity/common/common-test-core.sh index 57bc8a1c2c..9ea781697a 100755 --- a/resources/teamcity/common/common-test-core.sh +++ b/resources/teamcity/common/common-test-core.sh @@ -25,19 +25,16 @@ builder_parse "$@" if is_ubuntu; then ARCH=arch elif is_windows; then - ARCH=x86 + ARCH=win elif is_macos; then ARCH=mac else builder_echo error "Unknown architecture" - return 1 + exit 1 fi function do_all() { "${KEYMAN_ROOT}/core/build.sh" configure,build,test:${ARCH} - if is_windows; then - "${KEYMAN_ROOT}/core/build.sh" configure,build,test:x64 - fi } builder_run_action all do_all From 4c8450a0d66502d66c159c35b77588cf245fd5e3 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 3 Jul 2025 20:29:11 +0200 Subject: [PATCH 102/262] fix(ios): fix ios release builds We did already cd into that directory a few lines above. Build-bot: skip Test-bot: skip --- resources/teamcity/ios/keyman-ios-release.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/teamcity/ios/keyman-ios-release.sh b/resources/teamcity/ios/keyman-ios-release.sh index e5fe7346e0..933a275424 100755 --- a/resources/teamcity/ios/keyman-ios-release.sh +++ b/resources/teamcity/ios/keyman-ios-release.sh @@ -203,7 +203,6 @@ function __do_upload_to_fastlane() { export PILOT_GROUPS="Alpha" fi - cd "upload/${KEYMAN_VERSION}" command -v fastlane # shellcheck disable=SC2154 fastlane pilot upload \ From 3e406326743f7194f3ed3362412f1239309d144e Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 3 Jul 2025 20:32:20 +0200 Subject: [PATCH 103/262] fix(mac): fix release build Skipping builds since the changed code only affects TC release builds. Build-bot: skip Test-bot: skip --- resources/teamcity/macos/keyman-macos-release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/teamcity/macos/keyman-macos-release.sh b/resources/teamcity/macos/keyman-macos-release.sh index 1fad300c60..b20005bdfb 100755 --- a/resources/teamcity/macos/keyman-macos-release.sh +++ b/resources/teamcity/macos/keyman-macos-release.sh @@ -69,7 +69,7 @@ function _build_publish() { builder_echo end success "publish" "Finished publishing Keyman for macOS" } -function _do_publish() { +function do_publish() { _build_publish _publish_to_downloads_keyman_com upload_help "Keyman for macOS" mac From cbcbae473e01e554746bd65d44d6252f5eef189f Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 3 Jul 2025 21:07:45 +0200 Subject: [PATCH 104/262] fix(common): fixes triggering builds if no builds to start If the PR only contains files that we ignore, we previously failed to finish the trigger build. This PR fixes the problem. The problem can be observed in #14271 which only changed a file in `resources/teamcity/common` which gets ignored in `find_platform_changes`. Test-bot: skip --- resources/build/run-required-test-builds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/build/run-required-test-builds.sh b/resources/build/run-required-test-builds.sh index 02ec814969..9227c613cc 100755 --- a/resources/build/run-required-test-builds.sh +++ b/resources/build/run-required-test-builds.sh @@ -259,7 +259,7 @@ fi # Start test builds as required # -if (( ${#build_platforms[@]} > 0)); then +if [[ ! -z ${build_platforms:-} ]] && (( ${#build_platforms[@]:-0} > 0)); then builder_echo heading "Start test builds" triggerTestBuilds build_platforms $PRNUM else From ca14da0e79aa6711b8ec7458f0cddd9a7b4a6b6d Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 4 Jul 2025 07:39:11 +1000 Subject: [PATCH 105/262] maint(common): match OSTYPE correctly on macOS Fixes: #14276 --- resources/teamcity/includes/tc-helpers.inc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/teamcity/includes/tc-helpers.inc.sh b/resources/teamcity/includes/tc-helpers.inc.sh index 5a02face40..05bac1b746 100644 --- a/resources/teamcity/includes/tc-helpers.inc.sh +++ b/resources/teamcity/includes/tc-helpers.inc.sh @@ -22,7 +22,7 @@ is_windows() { # Returns 0 if we're running on macOS. is_macos() { - if [[ "${OSTYPE:-}" == "darwin" ]]; then + if [[ "${OSTYPE:-}" == darwin* ]]; then return 0 else return 1 From 94efd2c6390f76be07e23c42a60e3eb84224d847 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 4 Jul 2025 07:58:32 +1000 Subject: [PATCH 106/262] maint(common): trigger common builds for resources/teamcity/common changes Also removes old paths from triggers, and updates comments on matched patterns. Fixes: #14278 Test-bot: skip --- resources/build/trigger-definitions.inc.sh | 28 ++++++++++++++-------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/resources/build/trigger-definitions.inc.sh b/resources/build/trigger-definitions.inc.sh index db879b95e8..8748e63edf 100644 --- a/resources/build/trigger-definitions.inc.sh +++ b/resources/build/trigger-definitions.inc.sh @@ -38,15 +38,23 @@ available_platforms_regex=`echo "${available_platforms[@]}" | sed 's/ /|/g'` # We also allow 'common' and 'core' platforms for Build-bot: commands readonly available_platforms_regex="$available_platforms_regex|common|core" -# the base folder for each pattern does not need to be included, nor oem folders -# e.g. android='common/models|common/predictive-text' -# will expand into android='^(android|(oem/[^/]+/android)|common/models|common/predictive-text)' +# the base folder for each pattern does not need to be included, nor oem +# folders, nor resources/teamcity folders +# +# e.g. watch_android='web|comon/web' will expand to cover: +# android +# oem/*/android +# web +# common/web +# resources/teamcity/android +# resources/teamcity/includes -watch_android='web|common/models|common/predictive-text|common/web' -watch_ios='web|common/models|common/predictive-text|common/web' + +watch_android='web|common/web' +watch_ios='web|common/web' watch_linux='core|common/test/keyboards/baseline' watch_mac='core' -watch_web='common/models|common/predictive-text|common/web|core' +watch_web='common/web|core' watch_windows='common|core|web' watch_developer='common|core|web' @@ -55,10 +63,10 @@ watch_developer='common|core|web' watch_common_web='common/web' # Currently, we run web tests on all three of our build platforms, to ensure # that they work in all our environments. So we need to include common/web in -# the matches for these -watch_common_windows='common/windows|common/web' -watch_common_mac='common/mac|common/web' -watch_common_linux='common/linux|common/web' +# the matches for these, as well as resources/teamcity/common +watch_common_windows='common/windows|common/web|resources/teamcity/common' +watch_common_mac='common/mac|common/web|resources/teamcity/common' +watch_common_linux='common/linux|common/web|resources/teamcity/common' # # Available build configurations and VCS identifiers; identifiers are somewhat inconsistent due From 2668dfc76df94acdebd89045d12872ddd31d49ce Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 4 Jul 2025 13:01:22 +1000 Subject: [PATCH 107/262] docs(resources): clarify Build-bot usage Fixes: #14266 --- docs/build-bot.md | 99 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 6 deletions(-) diff --git a/docs/build-bot.md b/docs/build-bot.md index e879154a96..ea4f28c2fa 100644 --- a/docs/build-bot.md +++ b/docs/build-bot.md @@ -29,6 +29,39 @@ Or you may want to ensure that an artifact is built for Windows: Build-bot: release windows ``` +## The Build-bot command + +The build bot is controlled through the `Build-bot` command, which can be put +into commit trailers or the PR body (not PR comments). The format of the command +is: + +``` +Build-bot: [Platforms] +``` + +* `BuildLevel` can be `skip`, `build`, or `release`. See [Build Level] for more + details. +* `Platforms` can be omitted, in which case the command applies to all platforms + (equivalent to specifying `all`). If specified, it must be a comma-separated + list, with or without spaces, of one or more of the following platform + identifiers: + + * `all`: apply to all platforms listed below + * `android` + * `developer` + * `ios` + * `linux` + * `mac` + * `web` + * `windows` + * `common`: build common components, on all three build platforms (note: + `release` is equivalent to `build` buildLevel for `common`) + * `core`: build Keyman Core, on all three build platforms, and also WASM + (note: `release` is equivalent to `build` buildLevel for `core`) + +The list of platforms can be found in the `available_platforms` variable in +trigger-definitions.inc.sh. + ## Build Level The build level specifies what we want to be run for a test build on a PR. @@ -42,8 +75,8 @@ other release distribution endpoints. The build level is controlled by the Build-bot commit trailer and PR body Build-bot/Test-bot trailers. The default build level will be 'build'. -For target branch builds, the build level will always be 'build', and Build-bot: -commit trailers are ignored. +For target branch (master, beta, stable-x.y) builds, the build level will always +be 'build', and Build-bot: commit trailers are ignored. ### 'skip' build level @@ -82,7 +115,8 @@ For a 'release' build level: * we won't upload artifacts to any release endpoint such as Debian, packages.sil.org, etc, or to the release areas for Play Store or App Store -(uploading to *.keyman.com and to release endpoints happens in the release TC build config/GHA) +(uploading to *.keyman.com and to release endpoints happens in the release TC +build config/GHA) ## Controlling the build bot with trailers @@ -113,10 +147,11 @@ The Build-bot has limited interactions with the Keyman test bot (aka keymanapp-test-bot): if a 'User Testing' section is found in the PR body, the default build level will be upgraded from 'build' to 'release'. -Build bot trailers found in the commits or in the PR body are applied after the -test bot command. +Build bot trailers found in either the commits or in the PR body are applied +after the test bot command. -WARNING: The build bot does not check PR comments for Test-bot commands. +WARNING: The build bot does not check PR comments for Test-bot commands or a +'User Testing' section. ## Example Build-bot interactions @@ -136,6 +171,58 @@ Note that other platforms are still 'skip' but not included in the build set. Finally, the PR author pushes another commit, with `Build-bot: release windows`. The build set is now: `(android:skip ios:skip windows:release)`. +# FAQ +* How do I specify commands for multiple platforms, e.g. building on Windows and Linux? + +``` +Build-bot: build windows,linux +``` + +* If I modify a file that would cause a build on all platforms, does `Build-bot: + build android` then cause a build only on Android and skip all others? Or is + it only additive? + +The commands are additive. To skip all others, you would do: + +``` +Build-bot: skip +Build-bot: build android +``` + +* What happens in the following scenario: I have a PR that I only want to build + on Android and I add `Build-bot: build android`. Later on I merge in the + changes from `master` and add another commit. If I still only want to build on + Android, do I have to add `Build-bot: build android` on my new commit again? + Or what happens with merges that touch other files which would cause builds + for other platforms? + +First, you would need to specify `skip` for all other platforms in order to only +build on Android: + +``` +Build-bot: skip +Build-bot: build android +``` + +Then, the build bot scans all the commits in the PR, and additively builds the +final build set from the `Build-bot` commands from all the commits (up to 2000 +in one PR). Thus, earlier bot commands commits will continue to be honoured. +This makes it easier to apply merge commits, for example, or small fixups, +without needing to think about what needs to be built again. + +* How can I re-trigger builds after changing my build bot commands? + +In [TeamCity - Trigger Test Builds](https://build.palaso.org/buildConfiguration/Keyman_Test), +select a custom Run (small button attached to right of Run button), and in the +"Changes" tab, "Build branch" field, enter the PR number to re-run the builds. +Any test builds currently running against that PR will be cancelled and +restarted. + +Note: if you reduce the build set (with `skip`), and re-run builds in this way +without pushing a new commit, you will end up with stale status checks on the +latest commit in your PR for the builds that are now skipped on the new run, so +in this scenario it is safest to push a new commit with the updated build bot +commands. [Build Level]: #Build_Level \ No newline at end of file From d1e1058591d0659d0534c309e0606e7c7334e00a Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Thu, 3 Jul 2025 23:14:14 -0400 Subject: [PATCH 108/262] auto: increment master version to 19.0.78 --- HISTORY.md | 11 +++++++++++ VERSION.md | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index bb915863e7..31d0547ff8 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,16 @@ # Keyman Version History +## 19.0.77 alpha 2025-07-04 + +* maint(mac): rename `_do_publish` function to `do_publish` to fix release build (#14273) +* maint(ios): remove extra `cd` command from ios release builds (#14272) +* maint(android): remove `--ci` parameter from android builds (#14264) +* refactor(resources): replace shebang for TC include files (#14265) +* fix(core): fix failing `common-test-core.sh` script (#14271) +* maint(common): match OSTYPE correctly on macOS (#14277) +* fix(common): fix triggering builds if no builds to start (#14274) +* maint(common): trigger common builds for resources/teamcity/common changes (#14279) + ## 19.0.76 alpha 2025-07-03 * maint(common): move TC configuration for common tests (#14205) diff --git a/VERSION.md b/VERSION.md index 9cba072656..3b8dc801c8 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.77 \ No newline at end of file +19.0.78 \ No newline at end of file From d07c8167edf34cfed759486ecb14a229c6e88173 Mon Sep 17 00:00:00 2001 From: MengHeng <90595388+Meng-Heng@users.noreply.github.com> Date: Fri, 4 Jul 2025 14:48:32 +0700 Subject: [PATCH 109/262] Apply suggestions from code review Co-authored-by: Marc Durdin --- developer/docs/help/context/keyboard-editor.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/developer/docs/help/context/keyboard-editor.md b/developer/docs/help/context/keyboard-editor.md index fc072b552c..2fd02e88ad 100644 --- a/developer/docs/help/context/keyboard-editor.md +++ b/developer/docs/help/context/keyboard-editor.md @@ -269,6 +269,20 @@ Template... be lost. Therefore, selecting an appropriate template early in the development process is suggested. + - template-basic: has only a phone layout (4 rows of keys), with three layers: default (K_A through K_Z + plus some punctuation), shift (Shift K_A through Shift K_Z, plus some punctuation), numeric (K_0 through + K_9 unshifted and shifted, plus other non-alphanumerice keys). + + - template-latin: has both phone and tablet layouts (which are identical except for some key spacing, + each with 4 rows of keys), with four layers: default, shift, numeric, symbol. The first three layers + are similar to those of template-basic, with the addition of longpress keys for accented characters. + The symbol layer has many symbols not usually seen on a physical keyboard. + + - template-traditional: has both phone (4 rows of keys) and tablet (5 rows of keys) layouts. The phone + layout is similar to the template-basic layout. The tablet layout has eight layers: default, shift, ctrl, + shift-ctrl, alt, shift-alt, ctrl-alt, shift-ctrl-alt. The default layer has the keys from the default layer + of the desktop layout, the shift layer has the keys from the shift layer of the desktop layout, and so on. + Import from On Screen : If you have an existing On-Screen Keyboard for your keyboard layout, importing the design from the On-Screen Keyboard can reduce the From 1ac8c2ded75428bca3ba27370d66d686b7910bbe Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 4 Jul 2025 03:38:34 -0500 Subject: [PATCH 110/262] docs(developer): Fix links in gestures guide Follows #14204 and fixes the broken links found in keymanapp/help.keyman.com#2149 --- .../guides/develop/touch-keyboard-tutorial/gestures.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md b/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md index c66039cee3..b9c4697573 100644 --- a/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md +++ b/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md @@ -4,11 +4,11 @@ title: Using Gestures in Keyman Developer Keyman Developer offers **Longpress** keys, **Flicks**, and **Multitaps** for touch keyboard layout. To allow gestures onto your keyboard, go to the Touch layout tab and define the keys as follows: -Keyman Developer touch layout gestures +Keyman Developer touch layout gestures ## Tips -***Flicks** and **long presses** can both be used in a single keyboard. In this scenario, it is recommended to use flicks in only one direction—south (down). Long presses work well alongside this pattern. See, for example, [sil_euro_latin](../../../../keyboard/sil_euro_latin/3.0.3/sil_euro_latin#toc-mobile-keyboard-layout). +***Flicks** and **long presses** can both be used in a single keyboard. In this scenario, it is recommended to use flicks in only one direction—south (down). Long presses work well alongside this pattern. See, for example, [sil_euro_latin](/keyboard/sil_euro_latin/3.0.3/sil_euro_latin#toc-mobile-keyboard-layout). * Another helpful tip seen in the sil_euro_latin keyboard is using south-flicks on the base and shift layers corresponding to the numeric/symbol layer, this aids in both memorization and discovery. * **Flicks** in multiple directions are possible, but currently Keyman does not support hints for multiple directions, so they are not very discoverable. * **Long presses** are more discoverable, but **flicks** are faster. We suggest using south flicks along with long presses, and including the flick output in the long press for maximum discoverability and optimum ergonomics. @@ -22,5 +22,5 @@ Keyman Developer offers **Longpress** keys, **Flicks**, and **Multitaps** for to ## See also -* The [Winchus keyboard](../../../../keyboard/winchus/1.4/winchus#guia-rapida-celular) -* The [GFF Amharic keyboard](../../../../keyboard/gff_amharic/3.2.1/gff_amharic#toc-using-this-keyboard) \ No newline at end of file +* The [Winchus keyboard](/keyboard/winchus/1.4/winchus#guia-rapida-celular) +* The [GFF Amharic keyboard](/keyboard/gff_amharic/3.2.1/gff_amharic#toc-using-this-keyboard) \ No newline at end of file From 1333d78eb47374cc723593b243a8a51e74ca25fc Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 4 Jul 2025 21:46:32 +0200 Subject: [PATCH 111/262] Apply suggestions from code review --- .../help/guides/develop/touch-keyboard-tutorial/gestures.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md b/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md index b9c4697573..b09d5919cf 100644 --- a/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md +++ b/developer/docs/help/guides/develop/touch-keyboard-tutorial/gestures.md @@ -8,7 +8,7 @@ Keyman Developer offers **Longpress** keys, **Flicks**, and **Multitaps** for to ## Tips -***Flicks** and **long presses** can both be used in a single keyboard. In this scenario, it is recommended to use flicks in only one direction—south (down). Long presses work well alongside this pattern. See, for example, [sil_euro_latin](/keyboard/sil_euro_latin/3.0.3/sil_euro_latin#toc-mobile-keyboard-layout). +***Flicks** and **long presses** can both be used in a single keyboard. In this scenario, it is recommended to use flicks in only one direction—south (down). Long presses work well alongside this pattern. See, for example, [sil_euro_latin](https://help.keyman.com/keyboard/sil_euro_latin/3.0.3/sil_euro_latin#toc-mobile-keyboard-layout). * Another helpful tip seen in the sil_euro_latin keyboard is using south-flicks on the base and shift layers corresponding to the numeric/symbol layer, this aids in both memorization and discovery. * **Flicks** in multiple directions are possible, but currently Keyman does not support hints for multiple directions, so they are not very discoverable. * **Long presses** are more discoverable, but **flicks** are faster. We suggest using south flicks along with long presses, and including the flick output in the long press for maximum discoverability and optimum ergonomics. @@ -22,5 +22,5 @@ Keyman Developer offers **Longpress** keys, **Flicks**, and **Multitaps** for to ## See also -* The [Winchus keyboard](/keyboard/winchus/1.4/winchus#guia-rapida-celular) -* The [GFF Amharic keyboard](/keyboard/gff_amharic/3.2.1/gff_amharic#toc-using-this-keyboard) \ No newline at end of file +* The [Winchus keyboard](https://help.keyman.com/keyboard/winchus/1.4/winchus#guia-rapida-celular) +* The [GFF Amharic keyboard](https://help.keyman.com/keyboard/gff_amharic/3.2.1/gff_amharic#toc-using-this-keyboard) \ No newline at end of file From 598e33c2853adf8db1b8d4b92655b40ac0511d0d Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Fri, 4 Jul 2025 16:02:50 -0400 Subject: [PATCH 112/262] auto: increment master version to 19.0.79 --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 31d0547ff8..d31f440304 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 19.0.78 alpha 2025-07-04 + +* docs(developer): Fix links in gestures guide (#14297) + ## 19.0.77 alpha 2025-07-04 * maint(mac): rename `_do_publish` function to `do_publish` to fix release build (#14273) diff --git a/VERSION.md b/VERSION.md index 3b8dc801c8..1b64c3a8c4 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.78 \ No newline at end of file +19.0.79 \ No newline at end of file From 821371cfd9cfe012cc17617c1fe8a30f045a4e7d Mon Sep 17 00:00:00 2001 From: Daniel Yacob Date: Mon, 7 Jul 2025 08:52:58 -0400 Subject: [PATCH 113/262] Explanation of the Description field added in Details section. --- developer/docs/help/context/package-editor.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/developer/docs/help/context/package-editor.md b/developer/docs/help/context/package-editor.md index 38026732bc..85335a2c1b 100644 --- a/developer/docs/help/context/package-editor.md +++ b/developer/docs/help/context/package-editor.md @@ -129,6 +129,12 @@ are optional. A web site is encouraged and should be filled in the web site field, including the initial "http://" or "https://". +The Description is the keyboard summary that users will see on the keyboard’s +overview page at the keyman.com web site. +[GitHub style Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) +can be used in the description text. However, HTML tags are not supported. + + A package can optionally include a 140x250 JPEG or PNG image file to be shown during installation. This image file must be added to the Files list in step 1, then selected from the list here. @@ -191,4 +197,4 @@ keyboard to the [Keyman Cloud Keyboards Repository](/developer/keyboards/). ## Package Installers As of Keyman Developer 17, bundled executable package installers for Keyman for -Windows can be created using [kmc](kmc), but cannot be created within the IDE. \ No newline at end of file +Windows can be created using [kmc](kmc), but cannot be created within the IDE. From 970d93c7f51cc6dbab4f81e1e9e992b736cec91a Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Mon, 7 Jul 2025 07:58:03 -0500 Subject: [PATCH 114/262] fix(ios): restore keyboard scale map for iPad Air 2 Fixes: KEYMAN-IOS-JC --- .../KMEI/KeymanEngine/Classes/Keyboard/KeyboardScaleMap.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeyboardScaleMap.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeyboardScaleMap.swift index d91006b4cb..08fee03028 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeyboardScaleMap.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/KeyboardScaleMap.swift @@ -153,6 +153,7 @@ class KeyboardScaleMap { scalings[KeyboardScaleMap.hashKey(for: Device.iPad8)] = Scaling(portrait: KeyboardSize(w: 810, h: 265, b: 55), landscape: KeyboardSize(w: 1080, h: 353, b: 55)) scalings[KeyboardScaleMap.hashKey(for: Device.iPad9)] = Scaling(portrait: KeyboardSize(w: 810, h: 265, b: 55), landscape: KeyboardSize(w: 1080, h: 353, b: 55)) scalings[KeyboardScaleMap.hashKey(for: Device.iPad10)] = Scaling(portrait: KeyboardSize(w: 820, h: 262-5, b: 55), landscape: KeyboardSize(w: 1180, h: 347-5, b: 55)) // has negative 'system inset' (-5), which causes a mismatch in final height balanced by the subtraction + scalings[KeyboardScaleMap.hashKey(for: Device.iPadAir2)] = Scaling(portrait: KeyboardSize(w: 768, h: 258, b: 55), landscape: KeyboardSize(w: 1024, h: 343, b: 55)) // Banner always shows; merges copy ctrls with pred-banner. scalings[KeyboardScaleMap.hashKey(for: Device.iPadAir3)] = Scaling(portrait: KeyboardSize(w: 834, h: 265, b: 55), landscape: KeyboardSize(w: 1112, h: 353, b: 55)) scalings[KeyboardScaleMap.hashKey(for: Device.iPadAir4)] = Scaling(portrait: KeyboardSize(w: 820, h: 262-5, b: 55), landscape: KeyboardSize(w: 1180, h: 347-5, b: 55)) scalings[KeyboardScaleMap.hashKey(for: Device.iPadAir5)] = Scaling(portrait: KeyboardSize(w: 820, h: 262-5, b: 55), landscape: KeyboardSize(w: 1180, h: 347-5, b: 55)) From 0a5342150d1b2f29c2b726fb75683fb9cc7408ce Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 7 Jul 2025 17:34:08 +0200 Subject: [PATCH 115/262] maint(android): use `--release` for release builds This will fix the Android release builds. Previously we did a debug build but then tried to copy files from the release build folder. The original build config passed `--release` when doing a release build, which lost when moving to build scripts. Fixes: #14302 Build-bot: skip Build-bot: release android Test-bot: skip --- resources/teamcity/android/android-actions.inc.sh | 3 ++- resources/teamcity/android/keyman-android-release.sh | 4 ++-- resources/teamcity/android/keyman-android-test.sh | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/resources/teamcity/android/android-actions.inc.sh b/resources/teamcity/android/android-actions.inc.sh index 7fa0345096..5c56b81abe 100755 --- a/resources/teamcity/android/android-actions.inc.sh +++ b/resources/teamcity/android/android-actions.inc.sh @@ -11,8 +11,9 @@ android_clean_action() { android_build_action() { builder_echo start "build" "Building Keyman for Android" local TARGETS="$1" + local ARGS="$2" # REVIEW: is it deliberate that we `configure` all targets but only `build,test` `$TARGETS`? - "${KEYMAN_ROOT}/android/build.sh" configure build,test:"${TARGETS}" --debug + "${KEYMAN_ROOT}/android/build.sh" configure build,test:"${TARGETS}" ${ARGS} builder_echo end "build" success "Finished building Keyman for Android" } diff --git a/resources/teamcity/android/keyman-android-release.sh b/resources/teamcity/android/keyman-android-release.sh index 31dd0d9c4e..7aa258ee49 100755 --- a/resources/teamcity/android/keyman-android-release.sh +++ b/resources/teamcity/android/keyman-android-release.sh @@ -120,10 +120,10 @@ fi if builder_has_action all; then android_clean_action - android_build_action "${TARGETS}" + android_build_action "${TARGETS}" --release do_publish else builder_run_action clean android_clean_action - builder_run_action build android_build_action "${TARGETS}" + builder_run_action build android_build_action "${TARGETS}" --release builder_run_action publish do_publish fi diff --git a/resources/teamcity/android/keyman-android-test.sh b/resources/teamcity/android/keyman-android-test.sh index 8a531a2bfe..ca856e1ff4 100755 --- a/resources/teamcity/android/keyman-android-test.sh +++ b/resources/teamcity/android/keyman-android-test.sh @@ -38,8 +38,8 @@ fi if builder_has_action all; then android_clean_action - android_build_action "${TARGETS}" + android_build_action "${TARGETS}" --debug else builder_run_action clean android_clean_action - builder_run_action build android_build_action "${TARGETS}" + builder_run_action build android_build_action "${TARGETS}" --debug fi From 24246b80ff296be19841bcb1cdf13604e699541d Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 7 Jul 2025 17:44:59 +0200 Subject: [PATCH 116/262] maint(android): use absolute paths This is a small refactoring to use full, absolute paths when copying files and changing directories. This makes it clearer to see where those directories/files are located. Build-bot: skip Build-bot: build android Test-bot: skip --- resources/teamcity/android/keyman-android-release.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/teamcity/android/keyman-android-release.sh b/resources/teamcity/android/keyman-android-release.sh index 7aa258ee49..ce11e8726c 100755 --- a/resources/teamcity/android/keyman-android-release.sh +++ b/resources/teamcity/android/keyman-android-release.sh @@ -49,7 +49,7 @@ function _publish_to_downloads_keyman_com() { local COMPRESS_CMD # shellcheck disable=SC2154 - UPLOAD_PATH="upload/${KEYMAN_VERSION}" + 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" @@ -61,22 +61,22 @@ function _publish_to_downloads_keyman_com() { mkdir -p "${UPLOAD_PATH}" ( - cd KMAPro/kMAPro/libs + cd "${KEYMAN_ROOT}/android/KMAPro/kMAPro/libs" "${COMPRESS_CMD}" a -bd -bb0 "../../../${UPLOAD_PATH}/${KEYMAN_ENGINE_ANDROID_ZIP}" keyman-engine.aar ../../../Samples '-xr!build.sh' ) - cp "KMAPro/kMAPro/build/outputs/apk/release/${KEYMAN_APK}" "${UPLOAD_PATH}" + cp "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/release/${KEYMAN_APK}" "${UPLOAD_PATH}" 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 - if [[ -d "../oem/firstvoices/android/app/build/outputs/apk/release" ]]; then - cp "../oem/firstvoices/android/app/build/outputs/apk/release/${FIRSTVOICES_APK}" "${UPLOAD_PATH}" + if [[ -d "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/release" ]]; then + cp "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/release/${FIRSTVOICES_APK}" "${UPLOAD_PATH}" write_download_info "${UPLOAD_PATH}" "${FIRSTVOICES_APK}" "FirstVoices Keyboards" apk android fi ( - cd upload + cd "${KEYMAN_ROOT}/android/upload" # shellcheck disable=SC2154 tc_rsync_upload "${KEYMAN_VERSION}" "android/${KEYMAN_TIER}" ) From 4d373c6cfe52147138e11af2be2033daa7fa924d Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 7 Jul 2025 18:41:16 +0200 Subject: [PATCH 117/262] maint(resources): address code review comments Co-authored-by: Marc Durdin --- .../developer/keyman-developer-release.sh | 5 ++- resources/teamcity/includes/tc-helpers.inc.sh | 33 +--------------- resources/teamcity/includes/tc-linux.inc.sh | 18 +++++++++ resources/teamcity/includes/tc-mac.inc.sh | 2 + resources/teamcity/includes/tc-windows.inc.sh | 38 +++++++++++++++++++ resources/teamcity/linux/linux-actions.inc.sh | 4 +- resources/teamcity/web/web-actions.inc.sh | 2 +- .../windows/keyman-windows-release.sh | 5 ++- 8 files changed, 69 insertions(+), 38 deletions(-) create mode 100644 resources/teamcity/includes/tc-windows.inc.sh diff --git a/resources/teamcity/developer/keyman-developer-release.sh b/resources/teamcity/developer/keyman-developer-release.sh index 59d9f763c5..963f74fdcb 100755 --- a/resources/teamcity/developer/keyman-developer-release.sh +++ b/resources/teamcity/developer/keyman-developer-release.sh @@ -15,6 +15,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" # shellcheck disable=SC2154 . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-download-info.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-windows.inc.sh" ################################ Main script ################################ @@ -130,8 +131,8 @@ function publish_action() { export RSYNC_ROOT _publish_sentry - tc_download_symbol_server_index - tc_publish_new_symbols + ba_win_download_symbol_server_index + ba_win_publish_new_symbols _publish_to_downloads_keyman_com tc_upload_help "api documentation" developer } diff --git a/resources/teamcity/includes/tc-helpers.inc.sh b/resources/teamcity/includes/tc-helpers.inc.sh index 94a233ccba..065df7fe9e 100644 --- a/resources/teamcity/includes/tc-helpers.inc.sh +++ b/resources/teamcity/includes/tc-helpers.inc.sh @@ -32,10 +32,9 @@ is_macos() { # Returns 0 if the OS version is greater than or equal to the specified version. # Parameter: # $1 - OS version to compare against (e.g., "20.04") -is_os_version_or_higher() { +ba_linux_is_os_version_or_higher() { if ! is_ubuntu; then - builder_warning "This function is only implemented for Ubuntu" - return 0 # Not Ubuntu, so we assume the version check is not applicable. + builder_die "ba_linux_is_os_version_or_higher() is only implemented for Ubuntu" fi local OS_VERSION=$1 @@ -137,31 +136,3 @@ tc_rsync_upload() { "$1" \ "${RSYNC_USER}@${RSYNC_HOST}:${RSYNC_ROOT}/$2" } - -tc_download_symbol_server_index() { - # Download symbol server index from symbol server - builder_echo start "download symbol server index" "Downloading symbol server index" - ( - # shellcheck disable=SC2154 - mkdir -p "${LOCAL_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}" - # shellcheck disable=SC2164 - cd "${LOCAL_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}" - - # shellcheck disable=SC2154 - tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/lastid.txt" "." - tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/history.txt" "." - tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/server.txt" "." - ) - builder_echo end "download symbol server index" success "Finished downloading symbol server index" -} - -tc_publish_new_symbols() { - # Publish new symbols to symbol server - builder_echo start "publish new symbols" "Publishing new symbols to symbol server" - ( - # shellcheck disable=SC2164 - cd "${LOCAL_SYMBOLS_PATH}" - tc_rsync_upload "." "${REMOTE_SYMBOLS_PATH}" - ) - builder_echo end "publish new symbols" success "Finished publishing new symbols to symbol server" -} diff --git a/resources/teamcity/includes/tc-linux.inc.sh b/resources/teamcity/includes/tc-linux.inc.sh index 797c698f29..7e219f7e28 100644 --- a/resources/teamcity/includes/tc-linux.inc.sh +++ b/resources/teamcity/includes/tc-linux.inc.sh @@ -1,5 +1,8 @@ #!/usr/bin/env bash # Keyman is copyright (C) SIL Global. MIT License. +# +# Shared functions for any builds that run on Linux agents, +# e.g. linux, android, web, ... # Returns 0 if the specified package is installed. # Parameter: @@ -112,3 +115,18 @@ ba_linux_stop_xvfb() { rm -f "${PID_FILE}" fi } + +# Returns 0 if the OS version is greater than or equal to the specified version. +# Parameter: +# $1 - OS version to compare against (e.g., "20.04") +ba_linux_is_os_version_or_higher() { + if ! is_ubuntu; then + builder_die "ba_linux_is_os_version_or_higher() is only implemented for Ubuntu" + fi + + local OS_VERSION=$1 + + # we use `dpkg --compare-versions` to compare the current Ubuntu version + # shellcheck disable=SC2312 + dpkg --compare-versions "$(lsb_release -r -s)" ge "${OS_VERSION}" +} diff --git a/resources/teamcity/includes/tc-mac.inc.sh b/resources/teamcity/includes/tc-mac.inc.sh index 911fec97ff..a5de278343 100644 --- a/resources/teamcity/includes/tc-mac.inc.sh +++ b/resources/teamcity/includes/tc-mac.inc.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash # Keyman is copyright (C) SIL Global. MIT License. +# +# Shared functions for any builds that run on Mac agents ba_mac_unlock_keychain() { if [[ -z "${MAC_BUILDAGENT_PASSWORD}" ]]; then diff --git a/resources/teamcity/includes/tc-windows.inc.sh b/resources/teamcity/includes/tc-windows.inc.sh new file mode 100644 index 0000000000..4c0411eda6 --- /dev/null +++ b/resources/teamcity/includes/tc-windows.inc.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. +# +# Shared functions for any builds that run on Windows agents + +ba_win_download_symbol_server_index() { + if ! is_windows; then + builder_die "ba_win_download_symbol_server_index should only be run on Windows agents" + fi + + builder_echo start "download symbol server index" "Downloading symbol server index" + ( + # shellcheck disable=SC2154 + mkdir -p "${LOCAL_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}" + # shellcheck disable=SC2164 + cd "${LOCAL_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}" + + # shellcheck disable=SC2154 + tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/lastid.txt" "." + tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/history.txt" "." + tc_rsync_download "${REMOTE_SYMBOLS_PATH}/${SYMBOLS_SUBDIR}/server.txt" "." + ) + builder_echo end "download symbol server index" success "Finished downloading symbol server index" +} + +ba_win_publish_new_symbols() { + if ! is_windows; then + builder_die "ba_win_publish_new_symbols should only be run on Windows agents" + fi + + builder_echo start "publish new symbols" "Publishing new symbols to symbol server" + ( + # shellcheck disable=SC2164 + cd "${LOCAL_SYMBOLS_PATH}" + tc_rsync_upload "." "${REMOTE_SYMBOLS_PATH}" + ) + builder_echo end "publish new symbols" success "Finished publishing new symbols to symbol server" +} diff --git a/resources/teamcity/linux/linux-actions.inc.sh b/resources/teamcity/linux/linux-actions.inc.sh index f3ad2ec02d..0b96f4c07c 100644 --- a/resources/teamcity/linux/linux-actions.inc.sh +++ b/resources/teamcity/linux/linux-actions.inc.sh @@ -16,14 +16,14 @@ linux_additional_test_dependencies_action() { builder_echo start additional_dependencies "Installing additional dependencies" local TOINSTALL="lcov libdatetime-perl gcovr python3-venv jq" - if is_os_version_or_higher 24.04; then + if ba_linux_is_os_version_or_higher 24.04; then TOINSTALL="${TOINSTALL} libgirepository-2.0-dev python3-coverage" fi # shellcheck disable=SC2086 ba_linux_check_and_install_packages ${TOINSTALL} - if ! is_os_version_or_higher 24.04; then + if ! ba_linux_is_os_version_or_higher 24.04; then builder_heading "Installing python3-coverage from pip" pip3 install --user coverage fi diff --git a/resources/teamcity/web/web-actions.inc.sh b/resources/teamcity/web/web-actions.inc.sh index 60665e01f2..d12b846e96 100644 --- a/resources/teamcity/web/web-actions.inc.sh +++ b/resources/teamcity/web/web-actions.inc.sh @@ -18,7 +18,7 @@ web_install_dependencies_on_linux_action() { } _install_playwright_dependencies() { - if ! is_ubuntu || ! is_os_version_or_higher 24.04; then + if ! is_ubuntu || ! ba_linux_is_os_version_or_higher 24.04; then return 0 fi diff --git a/resources/teamcity/windows/keyman-windows-release.sh b/resources/teamcity/windows/keyman-windows-release.sh index 44cdda1230..ec3fbe671b 100755 --- a/resources/teamcity/windows/keyman-windows-release.sh +++ b/resources/teamcity/windows/keyman-windows-release.sh @@ -16,6 +16,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-download-info.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" +. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-windows.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/windows/windows-actions.inc.sh" ################################ Main script ################################ @@ -106,8 +107,8 @@ function windows_publish_action() { "${KEYMAN_ROOT}/windows/build.sh" publish windows_upload_symbols_to_sentry - tc_download_symbol_server_index - tc_publish_new_symbols + ba_win_download_symbol_server_index + ba_win_publish_new_symbols _publish_to_downloads_keyman_com tc_upload_help "Keyman for Windows" windows builder_echo end "publish windows" success "Finished publishing Keyman for Windows" From 38f43d84cfbd34661c859a5e6cae2f780dacc9d2 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 3 Jul 2025 20:04:53 +0200 Subject: [PATCH 118/262] refactor(common): some reformattings in `shellHelperFunctions.sh` Also always include `jq.inc.sh`. The include file already has everything we did here. Test-bot: skip --- linux/scripts/dist.sh | 2 +- resources/shellHelperFunctions.sh | 94 +++++++++++++++---------------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/linux/scripts/dist.sh b/linux/scripts/dist.sh index 9a55cd630b..a0d6cd7f21 100755 --- a/linux/scripts/dist.sh +++ b/linux/scripts/dist.sh @@ -93,7 +93,7 @@ dpkg-source \ --tar-ignore=keyman/resources/scopes \ --tar-ignore=keyman/resources/teamcity \ --tar-ignore=keyman/resources/build/*.lua \ - --tar-ignore=keyman/resources/build/jq* \ + --tar-ignore=keyman/resources/build/jq-* \ --tar-ignore=keyman/results \ --tar-ignore=keyman/tmp \ --tar-ignore=keyman/web \ diff --git a/resources/shellHelperFunctions.sh b/resources/shellHelperFunctions.sh index 802106401b..c7163f1c92 100755 --- a/resources/shellHelperFunctions.sh +++ b/resources/shellHelperFunctions.sh @@ -1,15 +1,8 @@ #!/usr/bin/env bash +# Keyman is copyright (C) SIL Global. MIT License. -# We only import JQ if not already defined; jq is used by -# _select_node_version_with_nvm() - -if [[ -z "${JQ+x}" ]]; then - if [[ "$BUILDER_OS" == win ]]; then - . "$KEYMAN_ROOT/resources/build/jq.inc.sh" - else - JQ=jq - fi -fi +# shellcheck disable=SC2154 +. "${KEYMAN_ROOT}/resources/build/jq.inc.sh" # Allows for a quick macOS check for those scripts requiring a macOS environment. verify_on_mac() { @@ -25,70 +18,74 @@ projects=("android" "ios" "linux" "lmlayer" "mac" "web" "windows") # Used to validate a specified 'project' parameter. verify_project() { match=false - for proj in ${projects[@]} + for proj in "${projects[@]}" do - if [ $proj = $1 ]; then + if [[ "${proj}" = "$1" ]]; then match=true fi done - if [ $match = false ]; then + if [[ ${match} = false ]]; then builder_die "Invalid project specified!" fi } displayInfo() { - if [ "$QUIET" != true ]; then - while [[ $# -gt 0 ]] ; do - echo $1 - shift # past argument - done - fi + # TODO: can we replace this function with builder_debug? + if [[ "${QUIET}" != true ]]; then + while [[ $# -gt 0 ]] ; do + echo "$1" + shift # past argument + done + fi } assertFileExists() { - if ! [ -f $1 ]; then - builder_die "Build failed: missing $1" - fi + if ! [[ -f $1 ]]; then + builder_die "Build failed: missing $1" + fi } assertDirExists() { - if ! [ -d $1 ]; then - builder_die "Build failed: missing $1" - fi + if ! [[ -d $1 ]]; then + builder_die "Build failed: missing $1" + fi } assertValidVersionNbr() { - if [[ "$1" == "" || ! "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - builder_die "Specified version not valid: '$1'. Version should be in the form Major.Minor.BuildCounter" - fi + # REVIEW: this function doesn't seem to be used anywhere in the codebase. + if [[ "$1" == "" || ! "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + builder_die "Specified version not valid: '$1'. Version should be in the form Major.Minor.BuildCounter" + fi } assertValidPRVersionNbr() { - if [[ "$1" == "" || ! "$1" =~ ^[0-9]+\.[0-9]+\.pull\.[0-9]+$ ]]; then - builder_die "Specified version not valid: '$1'. Version should be in the form Major.Minor.pull.BuildCounter" - fi + # REVIEW: this function doesn't seem to be used anywhere in the codebase. + if [[ "$1" == "" || ! "$1" =~ ^[0-9]+\.[0-9]+\.pull\.[0-9]+$ ]]; then + builder_die "Specified version not valid: '$1'. Version should be in the form Major.Minor.pull.BuildCounter" + fi } dl_info_display_usage() { - echo "Used to create a metadata file needed on the download site" - echo "for it to connect to the download.keyman.com API functions." - echo - echo "usage: write-download_info " - echo - echo " name Specifies the user-friendly name of the product represented by the file." - echo " filepath Specifies the path and file in need of a .download_info metadata file." - echo " version Specifies the build version number, which should be in the" - echo " form Major.Minor.BuildCounter" - echo " tier Specifies tier (typically one of: alpha, beta, stable)." - echo " platform Specifies the target platforms for the file." - echo " (Should be one of: android, ios, mac, web, windows)" - echo - echo "The resulting .downloadinfo file will be automatically placed in the same directory" - echo "as the originally-specified file." - exit 1 + # REVIEW: this function doesn't seem to be used anywhere in the codebase. + echo "Used to create a metadata file needed on the download site" + echo "for it to connect to the download.keyman.com API functions." + echo + echo "usage: write-download_info " + echo + echo " name Specifies the user-friendly name of the product represented by the file." + echo " filepath Specifies the path and file in need of a .download_info metadata file." + echo " version Specifies the build version number, which should be in the" + echo " form Major.Minor.BuildCounter" + echo " tier Specifies tier (typically one of: alpha, beta, stable)." + echo " platform Specifies the target platforms for the file." + echo " (Should be one of: android, ios, mac, web, windows)" + echo + echo "The resulting .downloadinfo file will be automatically placed in the same directory" + echo "as the originally-specified file." + exit 1 } write_download_info() { @@ -168,6 +165,7 @@ write_download_info() { # set_version sets the file version on mac/ios projects set_version ( ) { + # REVIEW: this function doesn't seem to be used anywhere in the codebase. PRODUCT_PATH=$1 if [ $KEYMAN_VERSION ]; then @@ -189,6 +187,8 @@ set_version ( ) { # set_npm_version # set_npm_version () { + # REVIEW: this function doesn't seem to be used anywhere in the codebase. + # We use --no-git-tag-version because our CI system controls version numbering and # already tags releases. We also want to have the version of this match the # release of Keyman Developer -- these two versions should be in sync. Because this From a25202530ebabc3b28d028fc76c993ff5dde7ebe Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Thu, 3 Jul 2025 20:11:57 +0200 Subject: [PATCH 119/262] maint(common): consolidate `write_download_info` functions Fixes: #14259 Build-bot: skip Test-bot: skip --- android/build.sh | 9 +- ios/tools/prepRelease.sh | 10 +- resources/shellHelperFunctions.sh | 113 ++++++++---------- .../teamcity/includes/tc-download-info.inc.sh | 54 --------- 4 files changed, 57 insertions(+), 129 deletions(-) delete mode 100644 resources/teamcity/includes/tc-download-info.inc.sh diff --git a/android/build.sh b/android/build.sh index caeddb5b51..41a4df0de9 100755 --- a/android/build.sh +++ b/android/build.sh @@ -109,12 +109,11 @@ if builder_start_action archive; then # Write download info files # - cd "${UPLOAD_PATH}" - write_download_info "Keyman Engine for Android" "${KEYMAN_ENGINE_ANDROID_ZIP}" "${KEYMAN_VERSION}" "${KEYMAN_TIER}" "android" - write_download_info "Keyman for Android" "${KEYMAN_APK}" "${KEYMAN_VERSION}" "${KEYMAN_TIER}" "android" + 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 - if [ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]; then - write_download_info "FirstVoices Keyboards" "${FIRSTVOICES_APK}" "${KEYMAN_VERSION}" "${KEYMAN_TIER}" "android" + if [[ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]]; then + write_download_info "${UPLOAD_PATH}" "${FIRSTVOICES_APK}" "FirstVoices Keyboards" apk android fi builder_finish_action success archive diff --git a/ios/tools/prepRelease.sh b/ios/tools/prepRelease.sh index cd82520662..c9e47edd20 100755 --- a/ios/tools/prepRelease.sh +++ b/ios/tools/prepRelease.sh @@ -129,11 +129,9 @@ fi # Write download info files # -cd "${UPLOAD_DIR}" +write_download_info "${UPLOAD_DIR}" "${KMEI_DST_NAME}" "Keyman Engine for iOS" zip ios +write_download_info "${UPLOAD_DIR}" "${KEYMANAPP_IPA_DST}" "Keyman for iPhone and iPad" ipa ios -write_download_info "Keyman Engine for iOS" "${KMEI_DST_NAME}" "${BUILD_NUMBER}" "${KEYMAN_TIER}" "ios" -write_download_info "Keyman for iPhone and iPad" "${KEYMANAPP_IPA_DST}" "${BUILD_NUMBER}" "${KEYMAN_TIER}" "ios" - -if [ ${RELEASE_OEM_FIRSTVOICES} = true ]; then - write_download_info "FirstVoices Keyboards" "${FIRSTVOICESAPP_IPA_DST}" "${BUILD_NUMBER}" "${KEYMAN_TIER}" "ios" +if [[ ${RELEASE_OEM_FIRSTVOICES} = true ]]; then + write_download_info "${UPLOAD_DIR}" "${FIRSTVOICESAPP_IPA_DST}" "FirstVoices Keyboards" ipa ios fi diff --git a/resources/shellHelperFunctions.sh b/resources/shellHelperFunctions.sh index c7163f1c92..6b30918cb7 100755 --- a/resources/shellHelperFunctions.sh +++ b/resources/shellHelperFunctions.sh @@ -16,7 +16,7 @@ verify_on_mac() { projects=("android" "ios" "linux" "lmlayer" "mac" "web" "windows") # Used to validate a specified 'project' parameter. -verify_project() { +_verify_project() { match=false for proj in "${projects[@]}" do @@ -88,79 +88,64 @@ dl_info_display_usage() { exit 1 } +# +# Write ${UPLOAD_DIR}/${ARTIFACT_FILENAME}.download_info file for the target +# artifact +# +# Parameters: +# 1: UPLOAD_DIR Directory where artifact can be found +# 2: ARTIFACT_FILENAME Filename (without path) of artifact +# 3: ARTIFACT_NAME Descriptive name of artifact +# 4: ARTIFACT_TYPE File extension of artifact, without initial period (e.g. tar.gz) +# 5: PLATFORM Target platform for artifact +# write_download_info() { - #Process file & path information. - PRODUCT_NAME="$1" - BASE_PATH="$2" - KM_VERSION="$3" - KM_TIER="$4" - KM_PLATFORM="$5" + local UPLOAD_DIR="$1" + local ARTIFACT_FILENAME="$2" + local ARTFIACT_NAME="$3" + local ARTIFACT_TYPE="$4" + local PLATFORM="$5" - verify_project "$KM_PLATFORM" + local DATE HASH SIZE DOWNLOAD_INFO STAT_FLAGS - BASE_DIR=$(dirname "${BASE_PATH}") - BASE_FILE=$(basename "${BASE_PATH}"); + _verify_project "${PLATFORM}" - if ([ -h "${BASE_DIR}" ]) then - while([ -h "${BASE_DIR}" ]) do BASE_PATH=`readlink "${BASE_DIR}"`; - done - fi - - assertFileExists "$2" - - pushd . > /dev/null - cd `dirname ${BASE_DIR}` > /dev/null - BASE_PATH=`pwd`; - popd > /dev/null - - DEST_DIR="$BASE_DIR" - - #Process version parameter. - assertValidVersionNbr "$3" - KM_BLD_COUNTER="$((${KM_VERSION##*.}))" - - if [ "$KM_VERSION" = "" ]; then - builder_die "Required -version parameter not specified!" - fi - - if [ "$KM_TIER" = "" ]; then - builder_die "Required -tier parameter not specified!" - fi - - DOWNLOAD_INFO_FILEPATH="${BASE_PATH}/${BASE_FILE}.download_info" - if [[ ! -f "${BASE_PATH}/${BASE_FILE}" ]]; then - builder_die "Cannot compute file size or MD5 for non-existent DMG file: ${BASE_PATH}/${BASE_FILE}" - fi - - FILE_EXTENSION="${BASE_FILE##*.}" - - # stat flags to get filesize in bytes - if [ "$BUILDER_OS" == "mac" ] && [[ $(which stat) == /usr/bin/stat ]]; then + # shellcheck disable=SC2312 + if [[ "${BUILDER_OS}" == "mac" ]] && [[ $(command -v stat) == /usr/bin/stat ]]; then # /usr/bin/stat on mac is BSD STAT_FLAGS="-f%z" else # GNU (coreutils) - STAT_FLAGS="-c%s" - fi - FILE_SIZE=$(stat ${STAT_FLAGS} "${BASE_PATH}/${BASE_FILE}") - MD5_HASH=$(md5sum "${BASE_PATH}/${BASE_FILE}" | cut -d" " -f 1 -) # hash is the first element returned - - if [[ -f "$DOWNLOAD_INFO_FILEPATH" ]]; then - builder_warn "Overwriting $DOWNLOAD_INFO_FILEPATH" + STAT_FLAGS="--print=%s" fi - echo { > "$DOWNLOAD_INFO_FILEPATH" - echo " \"name\": \"${PRODUCT_NAME}\"," >> "$DOWNLOAD_INFO_FILEPATH" - echo " \"version\": \"${KM_VERSION}\"," >> "$DOWNLOAD_INFO_FILEPATH" - echo " \"date\": \"$(date "+%Y-%m-%d")\"," >> "$DOWNLOAD_INFO_FILEPATH" - echo " \"platform\": \"${KM_PLATFORM}\"," >> "$DOWNLOAD_INFO_FILEPATH" - echo " \"stability\": \"${KM_TIER}\"," >> "$DOWNLOAD_INFO_FILEPATH" - echo " \"file\": \"${BASE_FILE}\"," >> "$DOWNLOAD_INFO_FILEPATH" - echo " \"md5\": \"${MD5_HASH}\"," >> "$DOWNLOAD_INFO_FILEPATH" - echo " \"type\": \"${FILE_EXTENSION}\"," >> "$DOWNLOAD_INFO_FILEPATH" - echo " \"build\": \"${KM_BLD_COUNTER}\"," >> "$DOWNLOAD_INFO_FILEPATH" - echo " \"size\": \"${FILE_SIZE}\"" >> "$DOWNLOAD_INFO_FILEPATH" - echo } >> "$DOWNLOAD_INFO_FILEPATH" + # Construct .download_info + DATE=$(date +%F) + # shellcheck disable=SC2312 + HASH=$(md5sum "${UPLOAD_DIR}/${ARTIFACT_FILENAME}" | cut -d ' ' -f 1) + # shellcheck disable=SC2248 + SIZE=$(stat ${STAT_FLAGS} "${UPLOAD_DIR}/${ARTIFACT_FILENAME}") + + # shellcheck disable=SC2016,SC2154 + DOWNLOAD_INFO=$( + "${JQ}" -n \ + --arg NAME "${ARTFIACT_NAME}" \ + --arg BUILD_NUMBER "${KEYMAN_VERSION}" \ + --arg DATE "${DATE}" \ + --arg PLATFORM "${PLATFORM}" \ + --arg KEYMAN_TIER "${KEYMAN_TIER}" \ + --arg FILENAME "${ARTIFACT_FILENAME}" \ + --arg ARTIFACT_TYPE "${ARTIFACT_TYPE}" \ + --arg HASH "${HASH}" \ + --arg BUILD_COUNTER "${KEYMAN_VERSION_PATCH}" \ + --arg SIZE "${SIZE}" \ + '{ + name: $NAME, version: $BUILD_NUMBER, date: $DATE, platform: $PLATFORM, + stability: $KEYMAN_TIER, file: $FILENAME, md5: $HASH, type: $ARTIFACT_TYPE, + build: $BUILD_COUNTER, size: $SIZE + }' + ) + echo "${DOWNLOAD_INFO}" | "${JQ}" . >> "${UPLOAD_DIR}/${ARTIFACT_FILENAME}.download_info" } # set_version sets the file version on mac/ios projects diff --git a/resources/teamcity/includes/tc-download-info.inc.sh b/resources/teamcity/includes/tc-download-info.inc.sh deleted file mode 100644 index c1e596a394..0000000000 --- a/resources/teamcity/includes/tc-download-info.inc.sh +++ /dev/null @@ -1,54 +0,0 @@ -# shellcheck shell=bash -# Keyman is copyright (C) SIL Global. MIT License. - -# shellcheck disable=SC2154 -. "${KEYMAN_ROOT}/resources/build/jq.inc.sh" - -# -# Write ${UPLOAD_DIR}/${ARTIFACT_FILENAME}.download_info file for the target -# artifact -# -# Parameters: -# 1: UPLOAD_DIR Directory where artifact can be found -# 2: ARTIFACT_FILENAME Filename (without path) of artifact -# 3: ARTIFACT_NAME Descriptive name of artifact -# 4: ARTIFACT_TYPE File extension of artifact, without initial period (e.g. tar.gz) -# 5: PLATFORM Target platform for artifact -# -write_download_info() { - # TODO: DRY out with Linux download_info writer, others - local UPLOAD_DIR="$1" - local ARTIFACT_FILENAME="$2" - local ARTFIACT_NAME="$3" - local ARTIFACT_TYPE="$4" - local PLATFORM="$5" - - local DATE HASH SIZE DOWNLOAD_INFO - - # Construct .download_info - DATE=$(date +%F) - # shellcheck disable=SC2312 - HASH=$(md5sum "${UPLOAD_DIR}/${ARTIFACT_FILENAME}" | cut -d ' ' -f 1) - SIZE=$(stat --print="%s" "${UPLOAD_DIR}/${ARTIFACT_FILENAME}") - - # shellcheck disable=SC2016 - DOWNLOAD_INFO=$( - "${JQ}" -n \ - --arg NAME "${ARTFIACT_NAME}" \ - --arg BUILD_NUMBER "${KEYMAN_VERSION}" \ - --arg DATE "${DATE}" \ - --arg PLATFORM "${PLATFORM}" \ - --arg KEYMAN_TIER "${KEYMAN_TIER}" \ - --arg FILENAME "${ARTIFACT_FILENAME}" \ - --arg ARTIFACT_TYPE "${ARTIFACT_TYPE}" \ - --arg HASH "${HASH}" \ - --arg BUILD_COUNTER "${KEYMAN_VERSION_PATCH}" \ - --arg SIZE "${SIZE}" \ - '{ - name: $NAME, version: $BUILD_NUMBER, date: $DATE, platform: $PLATFORM, - stability: $KEYMAN_TIER, file: $FILENAME, md5: $HASH, type: $ARTIFACT_TYPE, - build: $BUILD_COUNTER, size: $SIZE - }' - ) - echo "${DOWNLOAD_INFO}" | "${JQ}" . >> "${UPLOAD_DIR}/${ARTIFACT_FILENAME}.download_info" -} From bc7499a3e1f1d22dc8aa79bb4804e569bdcb1d80 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 7 Jul 2025 19:14:40 +0200 Subject: [PATCH 120/262] maint(developer): consolidate creating KMC_ZIP file Previously we created the KMC_ZIP file twice, and one of them was outdated. This change moves creating the zip file to `developer/src/inst/build.sh` so that the TC build script only does the upload. Fixes: #14263 Test-bot: skip --- developer/src/inst/build.sh | 21 ++++++++--------- .../developer/keyman-developer-release.sh | 23 ++++++------------- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/developer/src/inst/build.sh b/developer/src/inst/build.sh index a8eb8b849f..133352525a 100755 --- a/developer/src/inst/build.sh +++ b/developer/src/inst/build.sh @@ -242,25 +242,24 @@ function make-installer() { # } -# TODO: rename this to keyman-developer-cli-$Version.zip -KMC_ZIP="$DEVELOPER_ROOT/release/$KEYMAN_VERSION/kmcomp-$KEYMAN_VERSION.zip" - function make-kmc-install-zip() { builder_heading make-kmc-install-zip copy-schemas - cd "$DEVELOPER_ROOT/bin" + cd "${DEVELOPER_ROOT}/bin" - wzzip -bd -bb0 "$KMC_ZIP" \ + # TODO: rename this to keyman-developer-cli-$Version.zip + local KMCOMP_ZIP="${DEVELOPER_ROOT}/release/${KEYMAN_VERSION}/kmcomp-${KEYMAN_VERSION}.zip" + + # shellcheck disable=SC2154 + local COMPRESS_CMD="${SEVENZ_HOME}/7z" + + "${COMPRESS_CMD}" a -bd -bb0 "${KMCOMP_ZIP}" \ kmconvert.exe \ - sentry.dll sentry.x64.dll \ - kmdecomp.exe \ keyboard_info.schema.json \ - kmp.schema.json \ - keyman-touch-layout.spec.json keyman-touch-layout.clean.spec.json \ xml/layoutbuilder/*.keyman-touch-layout \ - projects/* \ - server/* + projects/ \ + server/ cd "$THIS_SCRIPT_PATH" } diff --git a/resources/teamcity/developer/keyman-developer-release.sh b/resources/teamcity/developer/keyman-developer-release.sh index ca42536a4b..e397399aec 100755 --- a/resources/teamcity/developer/keyman-developer-release.sh +++ b/resources/teamcity/developer/keyman-developer-release.sh @@ -95,9 +95,9 @@ function _publish_to_downloads_keyman_com() { ( cd "${KEYMAN_ROOT}/developer" - local UPLOAD_PATH KEYBOARDS_PATH KMCOMP_ZIP DEVELOPER_EXE DEBUG_ZIP COMPRESS_CMD + local UPLOAD_PATH KEYBOARDS_PATH KMCOMP_ZIP DEVELOPER_EXE DEBUG_ZIP # shellcheck disable=SC2154 - UPLOAD_PATH="upload/${KEYMAN_VERSION}" + UPLOAD_PATH="${KEYMAN_ROOT}/developer/upload/${KEYMAN_VERSION}" KEYBOARDS_PATH="${UPLOAD_PATH}/keyboards" KMCOMP_ZIP="kmcomp-${KEYMAN_VERSION}.zip" DEVELOPER_EXE="keymandeveloper-${KEYMAN_VERSION}.exe" @@ -106,17 +106,8 @@ function _publish_to_downloads_keyman_com() { rm -rf "${UPLOAD_PATH}" mkdir -p "${UPLOAD_PATH}" - ( - cd bin - cp "${KEYMAN_ROOT}/common/schemas/keyboard_info/keyboard_info.schema.json" . - - # shellcheck disable=SC2154 - COMPRESS_CMD="${SEVENZ_HOME}/7z" - - "${COMPRESS_CMD}" a -bd -bb0 "../${UPLOAD_PATH}/${KMCOMP_ZIP}" kmconvert.exe keyboard_info.schema.json xml/layoutbuilder/*.keyman-touch-layout projects/ server/ - ) - - cp "release/${KEYMAN_VERSION}/${DEVELOPER_EXE}" "${UPLOAD_PATH}/" + cp "${KEYMAN_ROOT}/developer/release/${KEYMAN_VERSION}/${DEVELOPER_EXE}" "${UPLOAD_PATH}/" + cp "${KEYMAN_ROOT}/developer/release/${KEYMAN_VERSION}/${KMCOMP_ZIP}" "${UPLOAD_PATH}/" write_download_info "${UPLOAD_PATH}" "${DEVELOPER_EXE}" "Keyman Developer" exe win write_download_info "${UPLOAD_PATH}" "${KMCOMP_ZIP}" "Keyman Developer Command-Line Compiler" zip win @@ -124,12 +115,12 @@ function _publish_to_downloads_keyman_com() { mkdir -p "${KEYBOARDS_PATH}" cp -r "${KEYMAN_ROOT}/common/test/keyboards"/*/build/*.kmp "${KEYBOARDS_PATH}/" - if [[ -f "release/${KEYMAN_VERSION}/${DEBUG_ZIP}" ]]; then - cp "release/${KEYMAN_VERSION}/${DEBUG_ZIP}" "${UPLOAD_PATH}/" + if [[ -f "${KEYMAN_ROOT}/developer/release/${KEYMAN_VERSION}/${DEBUG_ZIP}" ]]; then + cp "${KEYMAN_ROOT}/developer/release/${KEYMAN_VERSION}/${DEBUG_ZIP}" "${UPLOAD_PATH}/" write_download_info "${UPLOAD_PATH}" "${DEBUG_ZIP}" "Keyman Developer debug files" zip win fi - cd upload + cd "${KEYMAN_ROOT}/developer/upload" # shellcheck disable=SC2154 tc_rsync_upload "${KEYMAN_VERSION}" "developer/${KEYMAN_TIER}" ) From af6b53974d700d81ded2ab0f9ddf6124afa0fb8e Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 7 Jul 2025 14:04:40 -0400 Subject: [PATCH 121/262] auto: increment master version to 19.0.80 --- HISTORY.md | 6 ++++++ VERSION.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index d31f440304..7371477f2e 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,11 @@ # Keyman Version History +## 19.0.79 alpha 2025-07-07 + +* maint(android): use `--release` for release builds (#14307) +* refactor(common): some reformattings in `shellHelperFunctions.sh` (#14269) +* maint(common): consolidate `write_download_info` functions (#14270) + ## 19.0.78 alpha 2025-07-04 * docs(developer): Fix links in gestures guide (#14297) diff --git a/VERSION.md b/VERSION.md index 1b64c3a8c4..cb7930d26b 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.79 \ No newline at end of file +19.0.80 \ No newline at end of file From d1124504af842f86c9cafeac7692d33daab12813 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Mon, 7 Jul 2025 21:01:50 +0200 Subject: [PATCH 122/262] maint(common): remove include of deleted `tc-download-info.inc.sh` This fixes the release builds. Follow-up-of: #14270 Build-bot: skip Test-bot: skip --- resources/teamcity/android/keyman-android-release.sh | 1 - resources/teamcity/developer/keyman-developer-release.sh | 1 - resources/teamcity/linux/keyman-linux-release.sh | 1 - resources/teamcity/web/keyman-web-release.sh | 1 - resources/teamcity/windows/keyman-windows-release.sh | 1 - 5 files changed, 5 deletions(-) diff --git a/resources/teamcity/android/keyman-android-release.sh b/resources/teamcity/android/keyman-android-release.sh index 7aa258ee49..32db7d1112 100755 --- a/resources/teamcity/android/keyman-android-release.sh +++ b/resources/teamcity/android/keyman-android-release.sh @@ -14,7 +14,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" # shellcheck disable=SC2154 . "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" -. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-download-info.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/android/android-actions.inc.sh" diff --git a/resources/teamcity/developer/keyman-developer-release.sh b/resources/teamcity/developer/keyman-developer-release.sh index ca42536a4b..3977fe931f 100755 --- a/resources/teamcity/developer/keyman-developer-release.sh +++ b/resources/teamcity/developer/keyman-developer-release.sh @@ -13,7 +13,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE # shellcheck disable=SC2154 -. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-download-info.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-windows.inc.sh" diff --git a/resources/teamcity/linux/keyman-linux-release.sh b/resources/teamcity/linux/keyman-linux-release.sh index b9a9c47efb..9d94e8034d 100755 --- a/resources/teamcity/linux/keyman-linux-release.sh +++ b/resources/teamcity/linux/keyman-linux-release.sh @@ -11,7 +11,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" # shellcheck disable=SC2154 . "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-actions.inc.sh" -. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-download-info.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-linux.inc.sh" diff --git a/resources/teamcity/web/keyman-web-release.sh b/resources/teamcity/web/keyman-web-release.sh index a79dff946b..1c5cf0d88f 100755 --- a/resources/teamcity/web/keyman-web-release.sh +++ b/resources/teamcity/web/keyman-web-release.sh @@ -17,7 +17,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${KEYMAN_ROOT}/resources/teamcity/web/web-actions.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-linux.inc.sh" -. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-download-info.inc.sh" ################################ Main script ################################ diff --git a/resources/teamcity/windows/keyman-windows-release.sh b/resources/teamcity/windows/keyman-windows-release.sh index 7d1b79b4e0..8e972f7b57 100755 --- a/resources/teamcity/windows/keyman-windows-release.sh +++ b/resources/teamcity/windows/keyman-windows-release.sh @@ -14,7 +14,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" # shellcheck disable=SC2154 . "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" -. "${KEYMAN_ROOT}/resources/teamcity/includes/tc-download-info.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-windows.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/windows/windows-actions.inc.sh" From 7a5184a893e61df85e66de64ae6a0211c25ebd50 Mon Sep 17 00:00:00 2001 From: Meng-Heng Date: Tue, 8 Jul 2025 16:05:55 +0700 Subject: [PATCH 123/262] docs: rephrase Marc suggestion --- developer/docs/help/context/keyboard-editor.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/developer/docs/help/context/keyboard-editor.md b/developer/docs/help/context/keyboard-editor.md index fc072b552c..5d3eed901f 100644 --- a/developer/docs/help/context/keyboard-editor.md +++ b/developer/docs/help/context/keyboard-editor.md @@ -226,7 +226,8 @@ Display 102nd Key (as on European keyboards) *See the difference between the two layouts in Keyman Developer.* > [!NOTE] - > To ensure compatibility with both ANSI and ISO physical keyboards, you should tick the "Display 102nd Key (as on European keyboards)" checkbox in Keyman Developer. + > The "Display 102nd Key (as on European keyboards)" checkbox can be left unchecked. + > To ensure compatibility, the extra key shouldn't be assigned since it won't be accessible on an ANSI physical keyboard. Auto-fill underlying layout : When the Fill from layout button is clicked, if this option is From dd30a13345867be7ad8eaa31a06574689253ca52 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Tue, 8 Jul 2025 13:35:15 +0200 Subject: [PATCH 124/262] maint(resources): fix triggering of builds Fixes: #14313 Follow-up-of: #14274 Test-bot: skip --- resources/build/run-required-test-builds.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/build/run-required-test-builds.sh b/resources/build/run-required-test-builds.sh index 9227c613cc..0d4aa0704e 100755 --- a/resources/build/run-required-test-builds.sh +++ b/resources/build/run-required-test-builds.sh @@ -259,7 +259,7 @@ fi # Start test builds as required # -if [[ ! -z ${build_platforms:-} ]] && (( ${#build_platforms[@]:-0} > 0)); then +if [[ ! -z "${build_platforms[*]:-}" ]] && (( ${#build_platforms[@]} > 0)); then builder_echo heading "Start test builds" triggerTestBuilds build_platforms $PRNUM else From 0adb67e2422354bae982b7a2e00ca9bc0f38ba70 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Tue, 8 Jul 2025 14:01:23 -0400 Subject: [PATCH 125/262] auto: increment master version to 19.0.81 --- HISTORY.md | 5 +++++ VERSION.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 7371477f2e..7f31cca515 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # Keyman Version History +## 19.0.80 alpha 2025-07-08 + +* maint(common): remove include of deleted `tc-download-info.inc.sh` (#14311) +* maint(resources): fix triggering of builds (#14314) + ## 19.0.79 alpha 2025-07-07 * maint(android): use `--release` for release builds (#14307) diff --git a/VERSION.md b/VERSION.md index cb7930d26b..a4f68bcdb8 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.80 \ No newline at end of file +19.0.81 \ No newline at end of file From 6b61ea49a7b935a40e35ff4a169b7dab31618747 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 9 Jul 2025 11:04:06 +0200 Subject: [PATCH 126/262] maint(linux): on GHA packaging remove Oracular, add Questing Remove EOL Ubuntu 24.10 Oracular, add Ubuntu 25.10 Questing. Test-bot: skip --- .github/workflows/deb-packaging.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deb-packaging.yml b/.github/workflows/deb-packaging.yml index aaeb1c6306..a06d7ed3cd 100644 --- a/.github/workflows/deb-packaging.yml +++ b/.github/workflows/deb-packaging.yml @@ -127,7 +127,7 @@ jobs: strategy: fail-fast: true matrix: - dist: [jammy, noble, oracular] + dist: [jammy, noble, plucky] steps: - name: Checkout @@ -153,7 +153,7 @@ jobs: strategy: fail-fast: true matrix: - dist: [plucky] + dist: [questing] steps: - name: Checkout From 169490e3fa746715c3a35eb9c999b996441bd254 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 9 Jul 2025 11:21:00 +0200 Subject: [PATCH 127/262] maint(linux): remove Oracular from launchpad builds Ubuntu 24.10 Oracular is EOL, so we remove it from our launchpad builds. Test-bot: skip --- linux/scripts/launchpad.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linux/scripts/launchpad.sh b/linux/scripts/launchpad.sh index 3d139c1032..2e50922309 100755 --- a/linux/scripts/launchpad.sh +++ b/linux/scripts/launchpad.sh @@ -33,7 +33,7 @@ else fi echo "ppa: ${ppa}" -distributions="${DIST:-jammy noble oracular plucky questing}" +distributions="${DIST:-jammy noble plucky questing}" packageversion="${PACKAGEVERSION:-1~sil1}" BASEDIR=$(pwd) From 3c7304e8bda464010b1c8fb319848b080ecf1981 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 9 Jul 2025 11:49:05 +0200 Subject: [PATCH 128/262] maint(android): remove `--ci` parameter from Android release builds PR #14264 removed the `--ci` parameter from Android builds, but forgot to remove it for release builds. This PR fixes this. Follow-up-of: #14264 Build-bot: skip Test-bot: skip --- resources/teamcity/android/keyman-android-release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/teamcity/android/keyman-android-release.sh b/resources/teamcity/android/keyman-android-release.sh index 32db7d1112..729471ed27 100755 --- a/resources/teamcity/android/keyman-android-release.sh +++ b/resources/teamcity/android/keyman-android-release.sh @@ -86,7 +86,7 @@ function _publish_to_downloads_keyman_com() { function _publish_to_playstore() { builder_echo start "publish to Google Play Store" "Publishing release to Google Play Store" - "${KEYMAN_ROOT}/android/build.sh" "publish:${PUBTARGETS}" --ci + "${KEYMAN_ROOT}/android/build.sh" "publish:${PUBTARGETS}" builder_echo end "publish to Google Play Store" success "Finished publishing release to Google Play Store" } From 479a0da7399919af8dc6c13f4bfd4387f3fdc890 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 9 Jul 2025 12:00:12 +0200 Subject: [PATCH 129/262] =?UTF-8?q?maint(windows):=20fix=20platform=20name?= =?UTF-8?q?=20for=20Windows=20=F0=9F=A9=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .download_info files use the platform `win` instead of `windows`. This PR changes this, and also renames the `_verify_project` function to `_verify_platform` to better match what it's checking. Fixes Windows release builds. Test-bot: skip --- resources/shellHelperFunctions.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/shellHelperFunctions.sh b/resources/shellHelperFunctions.sh index 6b30918cb7..8f17a4c1e0 100755 --- a/resources/shellHelperFunctions.sh +++ b/resources/shellHelperFunctions.sh @@ -12,13 +12,13 @@ verify_on_mac() { fi } -# The list of valid projects that our build scripts ought expect. -projects=("android" "ios" "linux" "lmlayer" "mac" "web" "windows") +# The list of valid platforms that our build scripts ought expect. +platforms=("android" "ios" "linux" "lmlayer" "mac" "web" "win") # Used to validate a specified 'project' parameter. -_verify_project() { +_verify_platform() { match=false - for proj in "${projects[@]}" + for proj in "${platforms[@]}" do if [[ "${proj}" = "$1" ]]; then match=true @@ -108,7 +108,7 @@ write_download_info() { local DATE HASH SIZE DOWNLOAD_INFO STAT_FLAGS - _verify_project "${PLATFORM}" + _verify_platform "${PLATFORM}" # shellcheck disable=SC2312 if [[ "${BUILDER_OS}" == "mac" ]] && [[ $(command -v stat) == /usr/bin/stat ]]; then From 428b611effd59cec122e405484c72e63aae45ce6 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 9 Jul 2025 12:05:35 +0200 Subject: [PATCH 130/262] =?UTF-8?q?maint(developer):=20add=20missing=20inc?= =?UTF-8?q?lude=20file=20=F0=9F=A9=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `keyman-developer-release.sh` calls the `write_download_info` function but neglected to source the `shellHelperFunctions.sh` file which defines that function. Thus this change fixes the release build. Build-bot: skip Test-bot: skip --- resources/teamcity/developer/keyman-developer-release.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/teamcity/developer/keyman-developer-release.sh b/resources/teamcity/developer/keyman-developer-release.sh index 3977fe931f..15120ced55 100755 --- a/resources/teamcity/developer/keyman-developer-release.sh +++ b/resources/teamcity/developer/keyman-developer-release.sh @@ -13,6 +13,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE # shellcheck disable=SC2154 +. "${KEYMAN_ROOT}/resources/shellHelperFunctions.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-helpers.inc.sh" . "${KEYMAN_ROOT}/resources/teamcity/includes/tc-windows.inc.sh" From 8f0206329bbe4aca101ed1900e05473869cc4fb1 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 9 Jul 2025 16:09:02 +0200 Subject: [PATCH 131/262] Update resources/shellHelperFunctions.sh Co-authored-by: Darcy Wong --- resources/shellHelperFunctions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/shellHelperFunctions.sh b/resources/shellHelperFunctions.sh index 8f17a4c1e0..e7a46fc173 100755 --- a/resources/shellHelperFunctions.sh +++ b/resources/shellHelperFunctions.sh @@ -15,7 +15,7 @@ verify_on_mac() { # The list of valid platforms that our build scripts ought expect. platforms=("android" "ios" "linux" "lmlayer" "mac" "web" "win") -# Used to validate a specified 'project' parameter. +# Used to validate a specified 'platform' parameter. _verify_platform() { match=false for proj in "${platforms[@]}" From 98159ae7469580cee14aeda18f87513db9fcf424 Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 9 Jul 2025 16:30:18 +0200 Subject: [PATCH 132/262] maint(windows): replace hardcoded `symbols` paths with parameters Only touches release build files, so skipping PR test builds. Fixes: #14202 Build-bot: skip Test-bot: skip --- .../developer/keyman-developer-release.sh | 18 ++++++++---------- .../teamcity/windows/keyman-windows-release.sh | 18 ++++++++---------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/resources/teamcity/developer/keyman-developer-release.sh b/resources/teamcity/developer/keyman-developer-release.sh index 3977fe931f..31f345a48d 100755 --- a/resources/teamcity/developer/keyman-developer-release.sh +++ b/resources/teamcity/developer/keyman-developer-release.sh @@ -23,22 +23,20 @@ builder_describe \ "all run all actions" \ "build build Keyman Developer and test keyboards" \ "publish publish release of Keyman Developer" \ - "--rsync-path=RSYNC_PATH rsync path on remote server" \ - "--rsync-user=RSYNC_USER rsync user on remote server" \ - "--rsync-host=RSYNC_HOST rsync host on remote server" \ - "--rsync-root=RSYNC_ROOT rsync root on remote server" \ - "--help.keyman.com=HELP_KEYMAN_COM path to help.keyman.com repository" + "--rsync-path=RSYNC_PATH rsync path on remote server" \ + "--rsync-user=RSYNC_USER rsync user on remote server" \ + "--rsync-host=RSYNC_HOST rsync host on remote server" \ + "--rsync-root=RSYNC_ROOT rsync root on remote server" \ + "--help.keyman.com=HELP_KEYMAN_COM path to help.keyman.com repository" \ + "--symbols-local-path=LOCAL_SYMBOLS_PATH local path to symbols directory" \ + "--symbols-remote-path=REMOTE_SYMBOLS_PATH remote path to symbols directory" \ + "--symbols-subdir=SYMBOLS_SUBDIR subdirectory containing symbols" builder_parse "$@" # shellcheck disable=SC2154 cd "${KEYMAN_ROOT}/developer/src" -# TODO: move to parameters (#14202) -LOCAL_SYMBOLS_PATH="${KEYMAN_ROOT}/../symbols" -REMOTE_SYMBOLS_PATH="windows/symbols" -SYMBOLS_SUBDIR="000admin" - function _build_developer() { builder_echo start "build developer" "Building Keyman Developer" diff --git a/resources/teamcity/windows/keyman-windows-release.sh b/resources/teamcity/windows/keyman-windows-release.sh index 8e972f7b57..030f0b6ae6 100755 --- a/resources/teamcity/windows/keyman-windows-release.sh +++ b/resources/teamcity/windows/keyman-windows-release.sh @@ -26,11 +26,14 @@ builder_describe \ "build build Keyman for Windows Windows" \ "test run Keyman for Windows tests" \ "publish publish release" \ - "--rsync-path=RSYNC_PATH rsync path on remote server" \ - "--rsync-user=RSYNC_USER rsync user on remote server" \ - "--rsync-host=RSYNC_HOST rsync host on remote server" \ - "--rsync-root=RSYNC_ROOT rsync root on remote server" \ - "--help.keyman.com=HELP_KEYMAN_COM path to help.keyman.com repository" + "--rsync-path=RSYNC_PATH rsync path on remote server" \ + "--rsync-user=RSYNC_USER rsync user on remote server" \ + "--rsync-host=RSYNC_HOST rsync host on remote server" \ + "--rsync-root=RSYNC_ROOT rsync root on remote server" \ + "--help.keyman.com=HELP_KEYMAN_COM path to help.keyman.com repository" \ + "--symbols-local-path=LOCAL_SYMBOLS_PATH local path to symbols directory" \ + "--symbols-remote-path=REMOTE_SYMBOLS_PATH remote path to symbols directory" \ + "--symbols-subdir=SYMBOLS_SUBDIR subdirectory containing symbols" builder_parse "$@" @@ -41,11 +44,6 @@ if ! is_windows; then exit 1 fi -# TODO: move to parameters (#14202) -LOCAL_SYMBOLS_PATH="${KEYMAN_ROOT}/../symbols" -REMOTE_SYMBOLS_PATH="windows/symbols" -SYMBOLS_SUBDIR="000admin" - function _publish_to_downloads_keyman_com() { # Publish to downloads.keyman.com builder_echo start "publish to downloads.keyman.com" "Publishing release to downloads.keyman.com" From 3b136fcec97bc7d3ab8317026b97a39366e8a0ee Mon Sep 17 00:00:00 2001 From: Eberhard Beilharz Date: Wed, 9 Jul 2025 18:33:09 +0200 Subject: [PATCH 133/262] maint(common): consolidate functions to determine OS - add `builder_is_windows`, `builder_is_macos` and `builder_is_linux` functions - add/move `BUILDER_OS` env variable to `resources/builder.inc.sh` - use the new functions and env variable Fixes: #14109 Test-bot: skip --- common/linux/build.sh | 2 +- common/mac/build.sh | 2 +- .../test/resources/test_kill_browserstack.sh | 2 +- common/windows/build.sh | 2 +- core/commands.inc.sh | 4 +- developer/src/build.sh | 2 +- developer/src/kmcmplib/commands.inc.sh | 4 +- docs/builder.md | 21 ++++++ mac/Keyman4MacIM/make-km-dmg.sh | 2 +- mac/Keyman4MacIM/write-download_info.sh | 2 +- resources/build/build-utils-ci.inc.sh | 2 +- resources/build/build-utils.sh | 23 +------ resources/build/jq.inc.sh | 23 +++---- resources/build/zip.inc.sh | 18 +++--- resources/builder.inc.sh | 64 ++++++++++++++----- resources/docker-images/docker-build.inc.sh | 4 +- resources/locate_emscripten.inc.sh | 2 +- resources/shellHelperFunctions.sh | 25 ++++---- .../android/keyman-android-release.sh | 2 +- resources/teamcity/common/common-test-core.sh | 6 +- .../developer/developer-actions.inc.sh | 2 +- .../developer/keyman-developer-release.sh | 2 +- .../keyman-developer-test-linux-macos.sh | 2 +- .../keyman-developer-test-windows.sh | 2 +- resources/teamcity/includes/tc-helpers.inc.sh | 38 ++--------- resources/teamcity/includes/tc-linux.inc.sh | 2 +- resources/teamcity/web/keyman-web-release.sh | 2 +- resources/teamcity/web/web-actions.inc.sh | 8 +-- .../windows/keyman-windows-release.sh | 2 +- .../teamcity/windows/keyman-windows-test.sh | 2 +- 30 files changed, 137 insertions(+), 137 deletions(-) diff --git a/common/linux/build.sh b/common/linux/build.sh index 756826cc2d..b52102313e 100755 --- a/common/linux/build.sh +++ b/common/linux/build.sh @@ -18,7 +18,7 @@ builder_describe "Keyman common Linux modules" \ builder_parse "$@" -if [[ $BUILDER_OS != linux ]]; then +if ! builder_is_linux; then builder_echo grey "Platform is not linux; skipping common/linux" exit 0 fi diff --git a/common/mac/build.sh b/common/mac/build.sh index 392966d84c..44a7064c83 100755 --- a/common/mac/build.sh +++ b/common/mac/build.sh @@ -18,7 +18,7 @@ builder_describe "Keyman common mac modules" \ builder_parse "$@" -if [[ $BUILDER_OS != mac ]]; then +if ! builder_is_macos; then builder_echo grey "Platform is not macOS; skipping common/mac" exit 0 fi diff --git a/common/test/resources/test_kill_browserstack.sh b/common/test/resources/test_kill_browserstack.sh index 82e40f85cd..f6ea16a36b 100755 --- a/common/test/resources/test_kill_browserstack.sh +++ b/common/test/resources/test_kill_browserstack.sh @@ -12,7 +12,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE -if [[ $BUILDER_OS == win ]]; then +if builder_is_windows; then # BrowserStackLocal may not exist, so always pass taskkill //f //im BrowserStackLocal.exe || true fi diff --git a/common/windows/build.sh b/common/windows/build.sh index 06f5dd0201..663a7f41c7 100755 --- a/common/windows/build.sh +++ b/common/windows/build.sh @@ -18,7 +18,7 @@ builder_describe "Keyman common Windows modules" \ builder_parse "$@" -if [[ $BUILDER_OS != win ]]; then +if ! builder_is_windows; then builder_echo grey "Platform is not Windows; skipping common/windows" exit 0 fi diff --git a/core/commands.inc.sh b/core/commands.inc.sh index 761be3db4d..73ad033a8e 100644 --- a/core/commands.inc.sh +++ b/core/commands.inc.sh @@ -91,7 +91,7 @@ do_test() { if [[ $target =~ ^(x86|x64)$ ]]; then cmd //C build.bat $target $BUILDER_CONFIGURATION test $testparams else - if [[ $target == wasm ]] && [[ $BUILDER_OS == mac ]]; then + if [[ $target == wasm ]] && builder_is_macos; then # 11794 -- parallel tests failing on some mac build agents; temporary # mitigation until we diagnose root cause meson test -j 1 -C "$MESON_PATH" $testparams @@ -136,7 +136,7 @@ do_uninstall() { # ---------------------------------------------------------------------------- build_meson_cross_file_for_wasm() { - if [ $BUILDER_OS == win ]; then + if builder_is_windows; then local R=$(cygpath -w $(echo $EMSCRIPTEN_BASE) | sed 's_\\_\\\\_g') else local R=$(echo $EMSCRIPTEN_BASE | sed 's_/_\\/_g') diff --git a/developer/src/build.sh b/developer/src/build.sh index d01c6caeb8..d81762aa2c 100755 --- a/developer/src/build.sh +++ b/developer/src/build.sh @@ -66,7 +66,7 @@ builder_parse "$@" #------------------------------------------------------------------------------------------------------------------- -if [[ $BUILDER_OS == win ]]; then +if builder_is_windows; then source "$KEYMAN_ROOT/resources/build/win/environment.inc.sh" fi diff --git a/developer/src/kmcmplib/commands.inc.sh b/developer/src/kmcmplib/commands.inc.sh index 280343853c..e2a5679ed6 100644 --- a/developer/src/kmcmplib/commands.inc.sh +++ b/developer/src/kmcmplib/commands.inc.sh @@ -126,7 +126,7 @@ do_command() { # ---------------------------------------------------------------------------- build_meson_cross_file_for_wasm() { - if [ $BUILDER_OS == win ]; then + if builder_is_windows; then local R=$(cygpath -w $(echo $EMSCRIPTEN_BASE) | sed 's_\\_\\\\_g') else local R=$(echo $EMSCRIPTEN_BASE | sed 's_/_\\/_g') @@ -140,4 +140,4 @@ should_do_full_test() { fi return 1 -} \ No newline at end of file +} diff --git a/docs/builder.md b/docs/builder.md index 360a15a550..18272accd0 100644 --- a/docs/builder.md +++ b/docs/builder.md @@ -123,6 +123,13 @@ package.json (`npm run