diff --git a/HISTORY.md b/HISTORY.md index f42af6d60b..45405b8d55 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,65 @@ # Keyman Version History +## 19.0.214 alpha 2026-03-12 + +* chore(linux): Update debian changelog (#15718) +* maint(resources): use x86_64 emscripten on arm/windows builds (#15715) + +## 19.0.213 alpha 2026-03-10 + +* maint(linux): set git credential for linux help on the correct repo (#15707) + +## 19.0.212 alpha 2026-03-09 + +* chore(android): Cleanup unused Extracted Text (#15671) +* chore(deps): bump fast-xml-parser from 5.3.4 to 5.3.8 (#15649) +* fix(developer): resolve input project path for `kmc copy` (#15700) +* fix(developer): await copier result before reporting success or failure (#15701) +* fix(developer): correct INVALID_HANDLE_VALUE check in UnlockProject (#15704) +* fix(developer): use trimmed version of paths when verifying in New Project (#15705) + +## 19.0.211 alpha 2026-03-06 + +* chore(deps-dev): bump basic-ftp from 5.0.5 to 5.2.0 (#15631) +* chore(deps-dev): bump rollup from 4.22.4 to 4.59.0 (#15632) +* chore(deps): bump minimatch and @microsoft/api-extractor (#15647) +* chore(deps): bump minimatch from 10.1.1 to 10.2.4 in /developer/src/server/src/win32/trayicon/addon-src (#15648) +* chore(deps): bump tar from 7.5.7 to 7.5.10 in /developer/src/server/src/win32/trayicon/addon-src (#15682) +* chore(deps): bump multer from 2.0.2 to 2.1.1 (#15681) +* chore(web): remove implicit any type when setting processingConfig.itemIdentifier (#15689) +* maint(common): rollback from Typescript 5.8.2 to 5.4.x (#15690) +* docs: tweak build bot docs and update imsample gitignore (#15691) +* maint(developer): make `TestCompilerCallbacks` usage more consistent and simplify (#15665) +* maint(developer): cleanup additional TestCompilerCallbacks references (#15684) +* fix(developer): prevent new projects from overwriting existing projects (#15669) +* fix(developer): retrieve debug memo anchor without side effects (#15672) +* fix(developer): ngrok upgrade (#15626) +* chore(deps-dev): bump minimatch from 9.0.5 to 9.0.9 in /resources/build/pr-build-status (#15650) + +## 19.0.210 alpha 2026-03-05 + +* fix(developer): apply keyboard description in Windows keyboard import (#15667) +* fix(developer): improve UI feedback on upgrade (#15670) + +## 19.0.209 alpha 2026-03-04 + +* maint(ios): update fv Cartfile for Xcode 26 (#15655) +* feat(linux): implement diagnostic report (#15576) +* refactor(core): cleanup (#15656) + +## 19.0.208 alpha 2026-03-03 + +* docs(core): add keyhandling doc (#15629) +* fix(developer): handle CRLF as CR internally in LDML debugger (#15616) +* fix(developer): handle .kps file that has no `` element (#15622) +* fix(developer): use correct return values and nullish checks in package-validation (#15653) + +## 19.0.207 alpha 2026-03-02 + +* chore(ios): Cleanup `RELEASE_OEM_FIRSTVOICES` var in build.sh scripts (#15605) +* fix(developer): package compiler handle missing `info` section in .kps file (#15620) +* fix(developer): handle window already destroyed when closing editor (#15618) + ## 19.0.206 alpha 2026-02-25 * maint(ios): upgrade to Xcode 26.2 (#15498) @@ -1223,6 +1283,30 @@ * refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168) * chore: increment to alpha 19.0 (#13187) +## 18.0.248 stable 2026-03-12 + +* fix(ios): Revert Sentry to 8.38.0 to fix FirstVoices crash on startup with 18.0.247 (#15726) + +## 18.0.247 stable 2026-03-09 + +* chore(linux): Update debian changelog (#15556) +* chore(linux): update copyright year and standards version (#15558) +* fix(mac): retry for mac disk detach (#15577) +* maint(mac): upgrade to Xcode 26.2 (#15575) +* maint(ios): upgrade to Xcode 26.2 (#15614) +* fix(developer): handle CRLF as CR internally in LDML debugger (#15660) +* fix(developer): handle .kps file that has no `` element (#15661) +* fix(developer): use correct return values and nullish checks in package-validation (#15662) +* fix(developer): handle window already destroyed when closing editor (#15663) +* fix(developer): package compiler handle missing `info` section in .kps file (#15664) +* fix(developer): apply keyboard description in Windows keyboard import (#15668) +* fix(developer): improve UI feedback on upgrade (#15686) +* fix(developer): prevent new projects from overwriting existing projects (#15694) +* fix(developer): retrieve debug memo anchor without side effects (#15695) +* fix(developer): ngrok upgrade (#15633) +* fix(developer): await copier result before reporting success or failure (#15702) +* fix(developer): resolve input project path for `kmc copy` (#15703) + ## 18.0.246 stable 2026-02-04 * cherrypick(common): Update FirstVoices versions in keyboards.csv (#15273) diff --git a/VERSION.md b/VERSION.md index 3027f050d0..9daf8a196e 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.207 \ No newline at end of file +19.0.215 \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index 97f8ab7d38..16330bfcda 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -43,7 +43,6 @@ import io.sentry.Sentry; public class SystemKeyboard extends InputMethodService implements OnKeyboardEventListener { private static View inputView = null; - private static ExtractedText exText = null; private KMHardwareKeyboardInterpreter interpreter = null; private int inputType = InputType.TYPE_NULL; private int lastOrientation = Configuration.ORIENTATION_UNDEFINED; @@ -204,10 +203,9 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven return value, so we test for that as well (#11479) */ if (icText != null && icText.text != null) { - boolean didUpdateText = KMManager.updateText(KeyboardType.KEYBOARD_TYPE_SYSTEM, icText.text.toString()); - boolean didUpdateSelection = KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_SYSTEM); - if (!didUpdateText || !didUpdateSelection) - exText = icText; + // Update the text selection but ignore the returned statuses + KMManager.updateText(KeyboardType.KEYBOARD_TYPE_SYSTEM, icText.text.toString()); + KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_SYSTEM); } } @@ -278,10 +276,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven @Override public void onKeyboardLoaded(KeyboardType keyboardType) { - if (keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { - if (exText != null) - exText = null; - } + // Do nothing } @Override diff --git a/common/web/types/package.json b/common/web/types/package.json index 21bb83d7aa..dc22aa5605 100644 --- a/common/web/types/package.json +++ b/common/web/types/package.json @@ -36,9 +36,7 @@ "restructure": "3.0.1" }, "devDependencies": { - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", - "ajv": "^8.17.1", + "ajv": "^8.12.0", "ajv-cli": "^5.0.0", "ajv-formats": "^2.1.1", "c8": "^7.12.0", diff --git a/common/windows/delphi/chromium/Keyman.UI.UframeCEFHost.pas b/common/windows/delphi/chromium/Keyman.UI.UframeCEFHost.pas index bffcd952d2..abc1b0f169 100644 --- a/common/windows/delphi/chromium/Keyman.UI.UframeCEFHost.pas +++ b/common/windows/delphi/chromium/Keyman.UI.UframeCEFHost.pas @@ -303,7 +303,7 @@ end; function TframeCEFHost.HasFocus: Boolean; begin AssertVclThread; - Result := cefwp.HandleAllocated and IsChild(cefwp.Handle, GetFocus); + Result := Assigned(cefwp) and cefwp.HandleAllocated and IsChild(cefwp.Handle, GetFocus); end; procedure TframeCEFHost.Handle_CEF_SHOW(var message: TMessage); @@ -373,7 +373,7 @@ end; procedure TframeCEFHost.SetFocus; begin AssertVclThread; - if not FIsClosing and cefwp.CanFocus and Assigned(cef) then + if not FIsClosing and Assigned(cefwp) and Assigned(cef) and cefwp.CanFocus then begin GetParentForm(Self).ActiveControl := Self; cef.SetFocus(True); @@ -574,7 +574,8 @@ end; procedure TframeCEFHost.Handle_CEF_DESTROY(var Message: TMessage); begin AssertVclThread; - cefwp.DestroyChildWindow; + if Assigned(cefwp) then + cefwp.DestroyChildWindow; FreeAndNil(cefwp); end; diff --git a/core/docs/api/index.md b/core/docs/api/index.md index 94bb517654..427e6f1ded 100644 --- a/core/docs/api/index.md +++ b/core/docs/api/index.md @@ -19,6 +19,7 @@ This is an internal API intended for use only within Keyman Engine. * [Options](options) * [Processor](processor) * [State and Actions](state) +* [Key Handling](keyhandling) * [JSON introspection Schema](json-schema) * [Building Keyman Core](building) @@ -122,4 +123,4 @@ Caps Lock. [km_core_event_flags]: processor#km_core_event_flags "km_core_event_flags enum" [km_core_process_event]: processor#km_core_process_event "km_core_process_event function" [km_core_event]: processor#km_core_event "km_core_event function" -[km_core_event_code]: processor#km_core_event_code "km_core_event_code enum" \ No newline at end of file +[km_core_event_code]: processor#km_core_event_code "km_core_event_code enum" diff --git a/core/docs/api/json-schema.md b/core/docs/api/json-schema.md index 756d5c4969..4fca89e677 100644 --- a/core/docs/api/json-schema.md +++ b/core/docs/api/json-schema.md @@ -4,10 +4,10 @@ title: JSON Introspection Schema The [`km_core_state_to_json()`](state#km_core_state_to_json) call generates a JSON document describing the internal state of the keyboard -processor, this is the schema describing that document. +processor. This is the schema describing that document. **WARNING**: The structure and format of the JSON document is independently -versioned is not considered part of C API. It is intended solely for use in +versioned and is not considered part of the C API. It is intended solely for use in diagnostics or by development and debugging tools which may need to be aware of keyboard processor engine implementation details. @@ -74,4 +74,4 @@ keyboard processor engine implementation details. "context": { "$ref": "#/definitions/context" } } } -``` \ No newline at end of file +``` diff --git a/core/docs/api/keyhandling.md b/core/docs/api/keyhandling.md new file mode 100644 index 0000000000..6dcc3177b0 --- /dev/null +++ b/core/docs/api/keyhandling.md @@ -0,0 +1,42 @@ +# Key Handling + +For each key press the processor will called twice, for the key down event +as well as for the key up event. Depending on the type of key pressed the +processor might handle the key itself or pass it on to the application. +The value of `emit_keystroke` in `km_core_actions` struct tells if the +processor handled the key (`emit_keystroke=0`) or not (`emit_keystroke=1`). +It is important that the same value gets set for both key down and key up +events, otherwise the application might miss some events which then looks +to the user like keys are stuck. + +Usually the processor won't handle any frame keys and let the application +deal with it. This allows shortcut keys like Ctrl+C +to work. The only exception is the Backspace key which is +handled internally if enough context is available. Regular keys will be +handled by the processor. + +Note that there is a difference between CLDR/LDML and KMN keyboards if +there are no rules/transforms defined for a key: KMN keyboards will +output the cap value of the key (e.g. pressing a will output +`a` if no rule is defined), whereas CLDR/LDML keyboards will suppress +any output for that key. + +The following table lists the state of `km_core_actions.emit_keystroke` +on return of `km_core_process_event` when the following type of key is +pressed: + +|Type | KeyDown | KeyUp | +|-----------------------------|---------------|----------| +|Key with rule | FALSE | FALSE | +|Key w/o rule | FALSE | FALSE | +|Framekeys: | | | +|Enter | TRUE | TRUE | +|Backspace¹ | FALSE | FALSE | +|Backspace² | TRUE | TRUE | +|Ctrl+Key | TRUE | TRUE | +|Modifier key Shift| TRUE | TRUE | +|Modifier key Ctrl | TRUE | TRUE | +|Modifier key LAlt | TRUE | TRUE | + +1: context available
+2: without or with empty context diff --git a/core/docs/api/options.md b/core/docs/api/options.md index f51528eafe..c5539d7ae1 100644 --- a/core/docs/api/options.md +++ b/core/docs/api/options.md @@ -3,7 +3,7 @@ title: Options - Keyman Core API --- A state’s default options are set from the keyboard at creation time and the -environment. The Platform layer is then is expected to apply any persisted +environment. The Platform layer is then expected to apply any persisted options it is maintaining. Options are passed into and out of API functions as simple C arrays of [km_core_option_item] terminated with a `KM_CORE_OPTIONS_END` sentinel value. A state's options are exposed and manipulatable via the @@ -281,4 +281,4 @@ km_core_state_options_to_json(km_core_state const *state, [km_core_event_flags]: processor#km_core_event_flags "km_core_event_flags enum" [km_core_process_event]: processor#km_core_process_event "km_core_process_event function" [km_core_event]: processor#km_core_event "km_core_event function" -[km_core_event_code]: processor#km_core_event_code "km_core_event_code enum" \ No newline at end of file +[km_core_event_code]: processor#km_core_event_code "km_core_event_code enum" diff --git a/core/docs/api/processor.md b/core/docs/api/processor.md index c72d743bb9..e8a8f7c0f4 100644 --- a/core/docs/api/processor.md +++ b/core/docs/api/processor.md @@ -15,8 +15,8 @@ enum km_core_event_flags { KM_CORE_EVENT_FLAG_DEFAULT = 0, KM_CORE_EVENT_FLAG_TOUCH = 1, }; - ``` + ## Values `KM_CORE_EVENT_FLAG_DEFAULT` @@ -195,4 +195,4 @@ enum km_core_event_code { [km_core_event_flags]: processor#km_core_event_flags "km_core_event_flags enum" [km_core_process_event]: processor#km_core_process_event "km_core_process_event function" [km_core_event]: processor#km_core_event "km_core_event function" -[km_core_event_code]: processor#km_core_event_code "km_core_event_code enum" \ No newline at end of file +[km_core_event_code]: processor#km_core_event_code "km_core_event_code enum" diff --git a/core/docs/api/state.md b/core/docs/api/state.md index 0292d9f8a5..32d3dc9b32 100644 --- a/core/docs/api/state.md +++ b/core/docs/api/state.md @@ -15,7 +15,8 @@ owned by the state object. ## Description -Describes the +Describes a change to the hardware caps lock indicator state requested by +Keyman Core to the Platform layer. ## Specification ```c @@ -50,6 +51,7 @@ This API replaces the Action items APIs, which are now deprecated and will be removed in the future. ## Specification + ```c typedef struct { unsigned int code_points_to_delete; @@ -60,8 +62,8 @@ typedef struct { km_core_caps_state new_caps_lock_state; const km_core_usv* deleted_context; } km_core_actions; - ``` + ## Members `code_points_to_delete` @@ -78,15 +80,17 @@ typedef struct { `emit_keystroke` : Emit the (unmodified) input keystroke to the application, 0 = no, 1 = yes. + On most platforms this signals whether the processor handled the event + (0) or not (1). See also [key handling](keyhandling). `new_caps_lock_state` : -1=unchanged, 0=off, 1=on `deleted_context` : Reference copy of actual UTF32 codepoints deleted from end of context - (closest to caret) exactly code_points_to_delete in length (plus null + (closest to caret) exactly `code_points_to_delete` in length (plus null terminator). Used to determine encoding conversion differences when - deleting; only set when using [km_core_state_get_actions], otherwise nullptr. + deleting; only set when using [km_core_state_get_actions], otherwise `nullptr`. ------------------------------------------------------------------------------- @@ -282,4 +286,4 @@ An opaque pointer to a state object. [km_core_event_flags]: processor#km_core_event_flags "km_core_event_flags enum" [km_core_process_event]: processor#km_core_process_event "km_core_process_event function" [km_core_event]: processor#km_core_event "km_core_event function" -[km_core_event_code]: processor#km_core_event_code "km_core_event_code enum" \ No newline at end of file +[km_core_event_code]: processor#km_core_event_code "km_core_event_code enum" diff --git a/core/include/keyman/keyman_core_api.h b/core/include/keyman/keyman_core_api.h index 7863689296..c26cbdf9ff 100644 --- a/core/include/keyman/keyman_core_api.h +++ b/core/include/keyman/keyman_core_api.h @@ -576,15 +576,17 @@ typedef struct { `emit_keystroke` : Emit the (unmodified) input keystroke to the application, 0 = no, 1 = yes. + On most platforms this signals whether the processor handled the event + (0) or not (1). See also [key handling](keyhandling). `new_caps_lock_state` : -1=unchanged, 0=off, 1=on `deleted_context` : Reference copy of actual UTF32 codepoints deleted from end of context - (closest to caret) exactly code_points_to_delete in length (plus null + (closest to caret) exactly `code_points_to_delete` in length (plus null terminator). Used to determine encoding conversion differences when - deleting; only set when using [km_core_state_get_actions], otherwise nullptr. + deleting; only set when using [km_core_state_get_actions], otherwise `nullptr`. ------------------------------------------------------------------------------- @@ -744,7 +746,7 @@ title: Options - Keyman Core API --- A state’s default options are set from the keyboard at creation time and the -environment. The Platform layer is then is expected to apply any persisted +environment. The Platform layer is then expected to apply any persisted options it is maintaining. Options are passed into and out of API functions as simple C arrays of [km_core_option_item] terminated with a `KM_CORE_OPTIONS_END` sentinel value. A state's options are exposed and manipulatable via the diff --git a/core/src/ldml/ldml_processor.cpp b/core/src/ldml/ldml_processor.cpp index 17e4d634eb..9ef36b62fc 100644 --- a/core/src/ldml/ldml_processor.cpp +++ b/core/src/ldml/ldml_processor.cpp @@ -243,7 +243,7 @@ void ldml_event_state::emit_backspace() { // else loop again assert(end.type != KM_CORE_CT_END); // inappropriate here. state->context().pop_back(); - } + } /* We couldn't find a character at end of context (context is empty), so we'll pass the backspace keystroke on to the app to process; the @@ -532,10 +532,10 @@ ldml_event_state::ldml_event_state( uint8_t i, uint16_t e) { this->state = s; - this->vk = v; - this->modifier_state = m; - this->is_key_down = i; - this->event_flags = e; + this->_vk = v; + this->_modifier_state = m; + this->_is_key_down = i; + this->_event_flags = e; actions.persist_options = new km_core_option_item[1]; actions.persist_options[0] = NULL_OPTIONS[0]; diff --git a/core/src/ldml/ldml_processor.hpp b/core/src/ldml/ldml_processor.hpp index 6bf8a8fa31..b673a9275c 100644 --- a/core/src/ldml/ldml_processor.hpp +++ b/core/src/ldml/ldml_processor.hpp @@ -178,10 +178,10 @@ public: size_t context_to_string(std::u32string &str, bool include_markers = true); private: - km_core_virtual_key vk; - uint16_t modifier_state; - uint8_t is_key_down; - uint16_t event_flags; + km_core_virtual_key _vk; + uint16_t _modifier_state; + uint8_t _is_key_down; + uint16_t _event_flags; km_core_state *state; // our in-flight action struct. @@ -194,12 +194,12 @@ public: // implementation km_core_virtual_key ldml_event_state::get_vk() const { - return vk; + return _vk; } uint16_t ldml_event_state::get_modifier_state() const { - return modifier_state; + return _modifier_state; } } // namespace core diff --git a/core/tests/unit/km_core_keyboard_api.tests.cpp b/core/tests/unit/km_core_keyboard_api.tests.cpp index 06e604c023..262390d7c6 100644 --- a/core/tests/unit/km_core_keyboard_api.tests.cpp +++ b/core/tests/unit/km_core_keyboard_api.tests.cpp @@ -18,7 +18,7 @@ protected: km_core_keyboard_dispose(this->keyboard); this->keyboard = nullptr; } - } + } }; TEST_F(KmCoreKeyboardApiTests, LoadFromBlob) { diff --git a/core/tests/unit/kmx/meson.build b/core/tests/unit/kmx/meson.build index 22158b6a83..71e2aed1b5 100644 --- a/core/tests/unit/kmx/meson.build +++ b/core/tests/unit/kmx/meson.build @@ -239,7 +239,7 @@ kbd_log = custom_target(test_kbd + '.kmx'.underscorify(), input: kbd_src, command: kmc_cmd + ['build', '--debug', '--no-compiler-version', '@INPUT@', '--out-file', kbd_obj] ) - test('imx_list', imx_e, depends: kbd_log, args: [kbd_obj] ) +test('imx_list', imx_e, depends: kbd_log, args: [kbd_obj] ) external_e = executable('ext_event_tests', ['kmx_external_event.tests.cpp', common_test_files], cpp_args: defns + warns, @@ -258,4 +258,4 @@ kbd_log = custom_target(test_kbd + '.kmx'.underscorify(), input: kbd_src, command: kmc_cmd + ['build', '--debug', '--no-compiler-version', '@INPUT@', '--out-file', kbd_obj] ) - test('ext_event', external_e, depends: kbd_log, args: [kbd_obj] ) +test('ext_event', external_e, depends: kbd_log, args: [kbd_obj] ) diff --git a/developer/docs/help/context/options.md b/developer/docs/help/context/options.md index ac1825382c..63bc65e62f 100644 --- a/developer/docs/help/context/options.md +++ b/developer/docs/help/context/options.md @@ -125,42 +125,73 @@ Update database ![Options dialog - Server tab](../images/ui/frmOptions_Server.png) -There are two configurations in the Keyman Developer Server section: -1. Configure Server... -2. List local URLs for Server +### Configure Server dialog -Configure Server... +After clicking the **Configure Server...** button, a new window will pop-up: -: After clicking the button, a new window will pop-up +![Keyman Developer Server Options dialog](../images/ui/frmKeyman_Developer_Server_Options.png) - ![Keyman Developer Server Options dialog](../images/ui/frmKeyman_Developer_Server_Options.png) +You can customize the Keyman Developer Server for testing your keyboard online +and on other devices. Features of the Keyman Developer Server include: - You can customize the Keyman Developer server for testing the usability of your keyboard. Features of the Keyman Developer server include: +* Seamless integration with the IDE (Start, Stop, Live reload, Recompile...) +* Sharing keyboards for testing online and on other devices +* Allowing testing across devices and networks - * Seamless integration with the IDE (Start, Stop, Live reload, Recompile...) - * Port configuration - * Allow testing cross devices - * Wider range support for NGROK... +Keyman Developer Server integrates with a free tool called +[ngrok](https://ngrok.com) that provides a public URL to your instance of Keyman +Developer Server. This URL is dynamic and can change over time, and is only +available while Keyman Developer Server is running. This URL can be accessed +from any mobile device that is online, whether or not it is on your local +network. This means you can also quickly share keyboards, packages, and models +with other users, with a minimum of difficulty. - [Find out more](https://github.com/keymanapp/keyman/pull/6073) +* [Learn more about ngrok](server#ngrok) - Understanding the three tick boxes will enhance the usability of Keyman Developer: - - **Leave Server running after closing IDE**: allows keyboard testing on local URLs after closing Keyman Developer. - - **Use ngrok to provide public url for web debugger**: configure NGROK as instructed, once it looks something like this: - ![Keyman Developer NGROK](../images/ui/frmNGROK_Config.png) +**Options available in this dialog:** - Please restart the machine, and the URL (*link ending in .ngrok-free.app*) will appear in the specified locations: - ![Keyman Developer NGROK URL 1](../images/ui/frmNGROK_Server_1.png) - ![Keyman Developer NGROK URL 2](../images/ui/frmNGROK_Server_2.png) +- **Leave Server running after closing IDE**: allows keyboard testing on local + URLs even if Keyman Developer is not running. - Now, freely share it across multiple devices using the link or QR code. - ![Keyboard Test on Mobile](../images/ui/NGROK_Phone.png) +- **Use ngrok to provide public url for web debugger**: enable ngrok + integration; you may need to download additional components; see below: - - **Show server console window on start**: once ticked, Keyman Developer's server console will appear as an independent window as a node application with the default message: - `Starting Keyman Developer Server 17.0.290-beta, listening on port 8008.` +- **Download or update ngrok**: If the text next to this button shows 'Not + installed', press the button to download the required component -List local URLs for Server -: Below Configure Server, untick the box, and it will quit displaying any local URLs for keyboard testing. - ![Keyman Developer Server Options dialog](../images/ui/frmList_Local_URLs.png) +- **Install required redistributable**: ngrok requires an additional Microsoft + VC++ redistributable component; if the text next to this button shows 'Not + installed', press the button and follow the prompts to install the + redistributable component. -Explore more: [Keyman Developer Server](server#toc-configuring-keyman-developer-server) +After enabling ngrok, a URL with ending a domain such as `.ngrok-free.app` will +appear in the Tools/Server menu and the Web and Mobile debugger pages in the +Keyboard Editor and the Package Editor: + +![Tools/Options/Server menu showing ngrok](../images/ui/tools_options_server_ngrok.png) + +![Web and Mobile test showing ngrok](../images/ui/web_and_mobile_test_ngrok.png) + +Once this is active, you can access Keyman Developer Server across multiple +devices and networks using the link or QR code. + +- **Show server console window on start**: once ticked, Keyman Developer's + server console will appear as an independent window as a node application, + which may be helpful if you encounter errors as they will appear in the + console. + + A default message will appear when the server console starts, similar to: + `Starting Keyman Developer Server -beta, listening on port 8008.` + +### List local URLs for Server + +When selected, Keyman Developer will show you more local URLs in the +Tools/Server menu for accessing Keyman Developer Server from your browser. Some +of these URLs will work only on your local computer, and will not work on other +devices. Deselect this option in order to show only the most relevant access +URLs. (On some networks, it may be helpful to see all the available URLs.) + + +## Related links + +* [Keyman Developer Server](server#toc-configuring-keyman-developer-server) diff --git a/developer/docs/help/context/server.md b/developer/docs/help/context/server.md index bccf637af1..20b3581ae1 100644 --- a/developer/docs/help/context/server.md +++ b/developer/docs/help/context/server.md @@ -296,7 +296,7 @@ context as well. The devices listed are representative of the various types of platforms that Keyman supports, to allow testing on each of the available form factors. -## Making your Keyman Developer Server instance globally available with ngrok +## Making your Keyman Developer Server instance globally available with ngrok {#ngrok} One of the fiddliest parts of testing keyboards and models with mobile devices is transferring the files to your phone or tablet. Keyman Developer Server will @@ -324,28 +324,30 @@ configure ngrok manually. click **Configure Server...**. You should be presented with a window similar to this: - ![](images/config.png) + ![](../images/ui/frmKeyman_Developer_Server_Options.png) 2. Tick the box **Use ngrok to provide public url for Server** -3. If ngrok is available, a version number will be shown to the right of the +3. If ngrok is available, the text 'Installed' will be shown to the right of the **Download or update ngrok** button. Otherwise, click this button to download the latest version. -4. You'll need a free ngrok account, so click the **Create free ngrok +4. **Install required redistributable**: ngrok requires an additional Microsoft + VC++ redistributable component; if the text next to this button shows 'Not + installed', press the button and follow the prompts to install the + redistributable component. +5. You'll need a free ngrok account, so click the **Create free ngrok account...** button to open the ngrok website, and follow the steps there to finish the account creation process. -5. After creating the account, you should be presented with an authentication +6. After creating the account, you should be presented with an authentication token. You can also access this by clicking the **Get token** link in the Keyman Developer Server Options dialog box. Paste this token into the **Authentication token** edit box. -6. Select the most appropriate region for your location, to minimize the latency - (delay) when using the ngrok service. 7. Click **OK** to save changes, and start the ngrok service within Keyman Developer Server. Once ngrok is configured, you should see an additional **Server** menu item in Keyman Developer's **Tools** menu, with three items: -**Open `https://some-url.ngrok.io` in browser** +**Open `https://some-url.ngrok-free.app` in browser** : Opens the public URL to your current Keyman Developer Server instance in the default browser on your computer. @@ -357,15 +359,7 @@ Keyman Developer's **Tools** menu, with three items: **Configure...** -: Opens the Keyman Developer Server Options dialog box. - -# Configuring Keyman Developer Server - -Keyman Developer Server has a number of configuration options. On Windows, using -Keyman Developer is the easiest way to manage these options. Open **Tools | -Options**, select **Server** tab, and click **Configure Server...**. - -![](images/config.png) +: Opens the [Keyman Developer Server Options](options#server-tab) dialog box. ## Configuration Files diff --git a/developer/docs/help/images/ui/NGROK_Phone.png b/developer/docs/help/images/ui/NGROK_Phone.png deleted file mode 100644 index b33ef1e316..0000000000 Binary files a/developer/docs/help/images/ui/NGROK_Phone.png and /dev/null differ diff --git a/developer/docs/help/images/ui/frmKeyman_Developer_Server_Options.png b/developer/docs/help/images/ui/frmKeyman_Developer_Server_Options.png index 6f0c831308..1f49a4c637 100644 Binary files a/developer/docs/help/images/ui/frmKeyman_Developer_Server_Options.png and b/developer/docs/help/images/ui/frmKeyman_Developer_Server_Options.png differ diff --git a/developer/docs/help/images/ui/frmNGROK_Config.png b/developer/docs/help/images/ui/frmNGROK_Config.png deleted file mode 100644 index ffbbecef23..0000000000 Binary files a/developer/docs/help/images/ui/frmNGROK_Config.png and /dev/null differ diff --git a/developer/docs/help/images/ui/frmNGROK_Server_1.png b/developer/docs/help/images/ui/tools_options_server_ngrok.png similarity index 100% rename from developer/docs/help/images/ui/frmNGROK_Server_1.png rename to developer/docs/help/images/ui/tools_options_server_ngrok.png diff --git a/developer/docs/help/images/ui/frmNGROK_Server_2.png b/developer/docs/help/images/ui/web_and_mobile_test_ngrok.png similarity index 100% rename from developer/docs/help/images/ui/frmNGROK_Server_2.png rename to developer/docs/help/images/ui/web_and_mobile_test_ngrok.png diff --git a/developer/src/common/delphi/components/KeymanDeveloperDebuggerMemo.pas b/developer/src/common/delphi/components/KeymanDeveloperDebuggerMemo.pas index 54e2c87ee8..dd46b57d7a 100644 --- a/developer/src/common/delphi/components/KeymanDeveloperDebuggerMemo.pas +++ b/developer/src/common/delphi/components/KeymanDeveloperDebuggerMemo.pas @@ -21,6 +21,7 @@ interface uses System.Classes, + Winapi.ActiveX, Winapi.Messages, Winapi.RichEdit, Winapi.Windows, @@ -35,7 +36,7 @@ type TMemoSelection = record Start, Finish: Integer; - Anchor: Integer; + Caret: Integer; // In a non-empty selection, Anchor is at the other end of the selection function ToString: string; end; @@ -43,7 +44,6 @@ type private FOnMessage: TKeymanDeveloperDebuggerMessageEvent; FAllowUnicodeInput: Boolean; - FSelectionChanging: Boolean; FIsDebugging: Boolean; procedure SetAllowUnicode(const Value: Boolean); function GetSelection: TMemoSelection; @@ -60,7 +60,6 @@ type property AllowUnicode: Boolean read FAllowUnicodeInput write SetAllowUnicode default True; property OnMessage: TKeymanDeveloperDebuggerMessageEvent read FOnMessage write FOnMessage; property Selection: TMemoSelection read GetSelection write SetSelection; - property SelectionChanging: Boolean read FSelectionChanging; property IsDebugging: Boolean read FIsDebugging write FIsDebugging; end; @@ -69,6 +68,7 @@ procedure Register; implementation uses + tom_TLB, System.SysUtils; { TKeymanDeveloperDebuggerMemo } @@ -107,26 +107,29 @@ begin end; function TKeymanDeveloperDebuggerMemo.GetSelection: TMemoSelection; +var + Unk: IUnknown; + TextDoc: ITextDocument; + TextSel: ITextSelection; begin - Result.Anchor := 0; - - // EM_GETSEL doesn't tell us the anchor position, but we can figure - // it out with this kludge. I am not aware of side effects from this - // at this time. SendMessage(Handle, EM_GETSEL, NativeUInt(@Result.Start), NativeUInt(@Result.Finish)); - if Result.Start <> Result.Finish then + Result.Caret := Result.Finish; + + if Result.Finish <> Result.Start then begin - // We only need to play the selection test game if there is a non-zero - // selection length - FSelectionChanging := True; - Lines.BeginUpdate; - try - SendMessage(Handle, EM_SETSEL, -1, 0); - SendMessage(Handle, EM_GETSEL, NativeUInt(@Result.Anchor), 0); - SetSelection(Result); - finally - Lines.EndUpdate; - FSelectionChanging := False; + if SendMessage(Handle, EM_GETOLEINTERFACE, 0, LPARAM(@Unk)) <> 0 then + begin + if Unk.QueryInterface(IID_ITextDocument, TextDoc) = S_OK then + begin + TextSel := TextDoc.Selection; + if TextSel <> nil then + begin + if (TextSel.Flags and tomSelStartActive) <> 0 then + begin + Result.Caret := Result.Start; + end; + end; + end; end; end; end; @@ -153,7 +156,7 @@ end; procedure TKeymanDeveloperDebuggerMemo.SetSelection( const Value: TMemoSelection); begin - if Value.Anchor = Value.Start + if Value.Caret = Value.Start then SendMessage(Handle, EM_SETSEL, Value.Finish, Value.Start) else SendMessage(Handle, EM_SETSEL, Value.Start, Value.Finish); end; @@ -174,10 +177,10 @@ begin end; { TMemoSelection } - + function TMemoSelection.ToString: string; -begin - Result := Format('%d %d %d', [Start, Finish, Anchor]); -end; - +begin + Result := Format('TMemoSelection(start=%d finish=%d caret=%d)', [Start, Finish, Caret]); +end; + end. diff --git a/developer/src/common/delphi/components/developer_components.dpk b/developer/src/common/delphi/components/developer_components.dpk index 4a0b7f6386..3f27243187 100644 --- a/developer/src/common/delphi/components/developer_components.dpk +++ b/developer/src/common/delphi/components/developer_components.dpk @@ -44,6 +44,7 @@ contains TextFileFormat in '..\general\TextFileFormat.pas', LeftTabbedPageControl in 'LeftTabbedPageControl.pas', MenuImgList in 'MenuImgList.pas', - StringGridEditControlled in 'StringGridEditControlled.pas'; + StringGridEditControlled in 'StringGridEditControlled.pas', + tom_TLB in 'tom_TLB.pas'; end. diff --git a/developer/src/common/delphi/components/developer_components.dproj b/developer/src/common/delphi/components/developer_components.dproj index dabd46bf8a..38aa37350e 100644 --- a/developer/src/common/delphi/components/developer_components.dproj +++ b/developer/src/common/delphi/components/developer_components.dproj @@ -139,6 +139,7 @@ + Cfg_2 Base diff --git a/developer/src/common/delphi/components/tom_TLB.pas b/developer/src/common/delphi/components/tom_TLB.pas new file mode 100644 index 0000000000..7055de4009 --- /dev/null +++ b/developer/src/common/delphi/components/tom_TLB.pas @@ -0,0 +1,942 @@ +unit tom_TLB; + +// ************************************************************************ // +// WARNING +// ------- +// The types declared in this file were generated from data read from a +// Type Library. If this type library is explicitly or indirectly (via +// another type library referring to this type library) re-imported, or the +// 'Refresh' command of the Type Library Editor activated while editing the +// Type Library, the contents of this file will be regenerated and all +// manual modifications will be lost. +// ************************************************************************ // + +// $Rev: 52393 $ +// File generated on 4/03/2026 10:48:24 AM from Type Library described below. + +// ************************************************************************ // +// Type Lib: C:\Windows\SysWOW64\RICHED20.dll (1) +// LIBID: {8CC497C9-A1DF-11CE-8098-00AA0047BE5D} +// LCID: 0 +// Helpfile: +// HelpString: +// DepndLst: +// (1) v2.0 stdole, (C:\Windows\SysWOW64\stdole2.tlb) +// SYS_KIND: SYS_WIN32 +// Errors: +// Hint: Symbol 'Type' renamed to 'type_' +// Hint: Parameter 'Unit' of ITextSelection.MoveLeft changed to 'Unit_' +// Hint: Parameter 'Unit' of ITextSelection.MoveRight changed to 'Unit_' +// Hint: Parameter 'Unit' of ITextSelection.MoveUp changed to 'Unit_' +// Hint: Parameter 'Unit' of ITextSelection.MoveDown changed to 'Unit_' +// Hint: Parameter 'Unit' of ITextSelection.HomeKey changed to 'Unit_' +// Hint: Parameter 'Unit' of ITextSelection.EndKey changed to 'Unit_' +// Hint: Member 'End' of 'ITextRange' changed to 'End_' +// Hint: Parameter 'Unit' of ITextRange.Expand changed to 'Unit_' +// Hint: Parameter 'Unit' of ITextRange.GetIndex changed to 'Unit_' +// Hint: Parameter 'Unit' of ITextRange.SetIndex changed to 'Unit_' +// Hint: Parameter 'Unit' of ITextRange.StartOf changed to 'Unit_' +// Hint: Parameter 'Unit' of ITextRange.EndOf changed to 'Unit_' +// Hint: Parameter 'Unit' of ITextRange.Move changed to 'Unit_' +// Hint: Parameter 'Unit' of ITextRange.MoveStart changed to 'Unit_' +// Hint: Parameter 'Unit' of ITextRange.MoveEnd changed to 'Unit_' +// Hint: Parameter 'Unit' of ITextRange.Delete changed to 'Unit_' +// Hint: Parameter 'Type' of ITextRange.ChangeCase changed to 'Type_' +// Hint: Parameter 'Type' of ITextRange.GetPoint changed to 'Type_' +// Hint: Parameter 'Type' of ITextRange.SetPoint changed to 'Type_' +// Hint: Member 'Protected' of 'ITextFont' changed to 'Protected_' +// Hint: Parameter 'Type' of ITextDocument2.GetClientRect changed to 'Type_' +// ************************************************************************ // +{$TYPEDADDRESS OFF} // Unit must be compiled without type-checked pointers. +{$WARN SYMBOL_PLATFORM OFF} +{$WRITEABLECONST ON} +{$VARPROPSETTER ON} +{$ALIGN 4} + +interface + +uses Winapi.Windows, System.Classes, System.Variants, System.Win.StdVCL, Vcl.Graphics, Winapi.ActiveX; + + +// *********************************************************************// +// GUIDS declared in the TypeLibrary. Following prefixes are used: +// Type Libraries : LIBID_xxxx +// CoClasses : CLASS_xxxx +// DISPInterfaces : DIID_xxxx +// Non-DISP interfaces: IID_xxxx +// *********************************************************************// +const + // TypeLibrary Major and minor versions + tomMajorVersion = 1; + tomMinorVersion = 0; + + LIBID_tom: TGUID = '{8CC497C9-A1DF-11CE-8098-00AA0047BE5D}'; + + IID_ITextDocument: TGUID = '{8CC497C0-A1DF-11CE-8098-00AA0047BE5D}'; + IID_ITextRange: TGUID = '{8CC497C2-A1DF-11CE-8098-00AA0047BE5D}'; + IID_ITextSelection: TGUID = '{8CC497C1-A1DF-11CE-8098-00AA0047BE5D}'; + IID_ITextFont: TGUID = '{8CC497C3-A1DF-11CE-8098-00AA0047BE5D}'; + IID_ITextPara: TGUID = '{8CC497C4-A1DF-11CE-8098-00AA0047BE5D}'; + IID_ITextStoryRanges: TGUID = '{8CC497C5-A1DF-11CE-8098-00AA0047BE5D}'; + IID_ITextDocument2: TGUID = '{01C25500-4268-11D1-883A-3C8B00C10000}'; + IID_ITextMsgFilter: TGUID = '{A3787420-4267-11D1-883A-3C8B00C10000}'; + +// *********************************************************************// +// Declaration of Enumerations defined in Type Library +// *********************************************************************// +// Constants for enum __MIDL___MIDL_itf_tom_0000_0000_0001 +type + __MIDL___MIDL_itf_tom_0000_0000_0001 = TOleEnum; +const + tomFalse = $00000000; + tomTrue = $FFFFFFFF; + tomUndefined = $FF676981; + tomToggle = $FF676982; + tomAutoColor = $FF676983; + tomDefault = $FF676984; + tomSuspend = $FF676985; + tomResume = $FF676986; + tomApplyNow = $00000000; + tomApplyLater = $00000001; + tomTrackParms = $00000002; + tomCacheParms = $00000003; + tomBackward = $C0000001; + tomForward = $3FFFFFFF; + tomMove = $00000000; + tomExtend = $00000001; + tomNoSelection = $00000000; + tomSelectionIP = $00000001; + tomSelectionNormal = $00000002; + tomSelectionFrame = $00000003; + tomSelectionColumn = $00000004; + tomSelectionRow = $00000005; + tomSelectionBlock = $00000006; + tomSelectionInlineShape = $00000007; + tomSelectionShape = $00000008; + tomSelStartActive = $00000001; + tomSelAtEOL = $00000002; + tomSelOvertype = $00000004; + tomSelActive = $00000008; + tomSelReplace = $00000010; + tomEnd = $00000000; + tomStart = $00000020; + tomCollapseEnd = $00000000; + tomCollapseStart = $00000001; + tomClientCoord = $00000100; + tomNone = $00000000; + tomSingle = $00000001; + tomWords = $00000002; + tomDouble = $00000003; + tomDotted = $00000004; + tomDash = $00000005; + tomDashDot = $00000006; + tomDashDotDot = $00000007; + tomWave = $00000008; + tomThick = $00000009; + tomHair = $0000000A; + tomLineSpaceSingle = $00000000; + tomLineSpace1pt5 = $00000001; + tomLineSpaceDouble = $00000002; + tomLineSpaceAtLeast = $00000003; + tomLineSpaceExactly = $00000004; + tomLineSpaceMultiple = $00000005; + tomAlignLeft = $00000000; + tomAlignCenter = $00000001; + tomAlignRight = $00000002; + tomAlignJustify = $00000003; + tomAlignDecimal = $00000003; + tomAlignBar = $00000004; + tomAlignInterWord = $00000003; + tomAlignInterLetter = $00000004; + tomAlignScaled = $00000005; + tomAlignGlyphs = $00000006; + tomAlignSnapGrid = $00000007; + tomSpaces = $00000000; + tomDots = $00000001; + tomDashes = $00000002; + tomLines = $00000003; + tomThickLines = $00000004; + tomEquals = $00000005; + tomTabBack = $FFFFFFFD; + tomTabNext = $FFFFFFFE; + tomTabHere = $FFFFFFFF; + tomListNone = $00000000; + tomListBullet = $00000001; + tomListNumberAsArabic = $00000002; + tomListNumberAsLCLetter = $00000003; + tomListNumberAsUCLetter = $00000004; + tomListNumberAsLCRoman = $00000005; + tomListNumberAsUCRoman = $00000006; + tomListNumberAsSequence = $00000007; + tomListParentheses = $00010000; + tomListPeriod = $00020000; + tomListPlain = $00030000; + tomCharacter = $00000001; + tomWord = $00000002; + tomSentence = $00000003; + tomParagraph = $00000004; + tomLine = $00000005; + tomStory = $00000006; + tomScreen = $00000007; + tomSection = $00000008; + tomColumn = $00000009; + tomRow = $0000000A; + tomWindow = $0000000B; + tomCell = $0000000C; + tomCharFormat = $0000000D; + tomParaFormat = $0000000E; + tomTable = $0000000F; + tomObject = $00000010; + tomMatchWord = $00000002; + tomMatchCase = $00000004; + tomMatchPattern = $00000008; + tomUnknownStory = $00000000; + tomMainTextStory = $00000001; + tomFootnotesStory = $00000002; + tomEndnotesStory = $00000003; + tomCommentsStory = $00000004; + tomTextFrameStory = $00000005; + tomEvenPagesHeaderStory = $00000006; + tomPrimaryHeaderStory = $00000007; + tomEvenPagesFooterStory = $00000008; + tomPrimaryFooterStory = $00000009; + tomFirstPageHeaderStory = $0000000A; + tomFirstPageFooterStory = $0000000B; + tomNoAnimation = $00000000; + tomLasVegasLights = $00000001; + tomBlinkingBackground = $00000002; + tomSparkleText = $00000003; + tomMarchingBlackAnts = $00000004; + tomMarchingRedAnts = $00000005; + tomShimmer = $00000006; + tomWipeDown = $00000007; + tomWipeRight = $00000008; + tomAnimationMax = $00000008; + tomLowerCase = $00000000; + tomUpperCase = $00000001; + tomTitleCase = $00000002; + tomSentenceCase = $00000004; + tomToggleCase = $00000005; + tomReadOnly = $00000100; + tomShareDenyRead = $00000200; + tomShareDenyWrite = $00000400; + tomPasteFile = $00001000; + tomCreateNew = $00000010; + tomCreateAlways = $00000020; + tomOpenExisting = $00000030; + tomOpenAlways = $00000040; + tomTruncateExisting = $00000050; + tomRTF = $00000001; + tomText = $00000002; + tomHTML = $00000003; + tomWordDocument = $00000004; + tomBold = $80000001; + tomItalic = $80000002; + tomUnderline = $80000004; + tomStrikeout = $80000008; + tomProtected = $80000010; + tomLink = $80000020; + tomSmallCaps = $80000040; + tomAllCaps = $80000080; + tomHidden = $80000100; + tomOutline = $80000200; + tomShadow = $80000400; + tomEmboss = $80000800; + tomImprint = $80001000; + tomDisabled = $80002000; + tomRevised = $80004000; + tomNormalCaret = $00000000; + tomKoreanBlockCaret = $00000001; + tomIncludeInset = $00000001; + tomIgnoreCurrentFont = $00000000; + tomMatchFontCharset = $00000001; + tomMatchFontSignature = $00000002; + tomCharset = $80000000; + tomRE10Mode = $00000001; + tomNoIME = $00080000; + tomSelfIME = $00040000; + +type + +// *********************************************************************// +// Forward declaration of types defined in TypeLibrary +// *********************************************************************// + ITextDocument = interface; + ITextDocumentDisp = dispinterface; + ITextRange = interface; + ITextRangeDisp = dispinterface; + ITextSelection = interface; + ITextSelectionDisp = dispinterface; + ITextFont = interface; + ITextFontDisp = dispinterface; + ITextPara = interface; + ITextParaDisp = dispinterface; + ITextStoryRanges = interface; + ITextStoryRangesDisp = dispinterface; + ITextDocument2 = interface; + ITextDocument2Disp = dispinterface; + ITextMsgFilter = interface; + +// *********************************************************************// +// Declaration of structures, unions and aliases. +// *********************************************************************// + wireHWND = ^_RemotableHandle; + POleVariant1 = ^OleVariant; {*} + PInteger1 = ^Integer; {*} + + tomConstants = __MIDL___MIDL_itf_tom_0000_0000_0001; + + __MIDL_IWinTypes_0009 = record + case Integer of + 0: (hInproc: Integer); + 1: (hRemote: Integer); + end; + + _RemotableHandle = record + fContext: Integer; + u: __MIDL_IWinTypes_0009; + end; + + UINT_PTR = LongWord; + LONG_PTR = Integer; + +// *********************************************************************// +// Interface: ITextDocument +// Flags: (4544) Dual NonExtensible OleAutomation Dispatchable +// GUID: {8CC497C0-A1DF-11CE-8098-00AA0047BE5D} +// *********************************************************************// + ITextDocument = interface(IDispatch) + ['{8CC497C0-A1DF-11CE-8098-00AA0047BE5D}'] + function Get_Name: WideString; safecall; + function Get_Selection: ITextSelection; safecall; + function Get_StoryCount: Integer; safecall; + function Get_StoryRanges: ITextStoryRanges; safecall; + function Get_Saved: Integer; safecall; + procedure Set_Saved(pValue: Integer); safecall; + function Get_DefaultTabStop: Single; safecall; + procedure Set_DefaultTabStop(pValue: Single); safecall; + procedure New; safecall; + procedure Open(const pVar: OleVariant; Flags: Integer; CodePage: Integer); safecall; + procedure Save(const pVar: OleVariant; Flags: Integer; CodePage: Integer); safecall; + function Freeze: Integer; safecall; + function Unfreeze: Integer; safecall; + procedure BeginEditCollection; safecall; + procedure EndEditCollection; safecall; + function Undo(Count: Integer): Integer; safecall; + function Redo(Count: Integer): Integer; safecall; + function Range(cp1: Integer; cp2: Integer): ITextRange; safecall; + function RangeFromPoint(x: Integer; y: Integer): ITextRange; safecall; + property Name: WideString read Get_Name; + property Selection: ITextSelection read Get_Selection; + property StoryCount: Integer read Get_StoryCount; + property StoryRanges: ITextStoryRanges read Get_StoryRanges; + property Saved: Integer read Get_Saved write Set_Saved; + property DefaultTabStop: Single read Get_DefaultTabStop write Set_DefaultTabStop; + end; + +// *********************************************************************// +// DispIntf: ITextDocumentDisp +// Flags: (4544) Dual NonExtensible OleAutomation Dispatchable +// GUID: {8CC497C0-A1DF-11CE-8098-00AA0047BE5D} +// *********************************************************************// + ITextDocumentDisp = dispinterface + ['{8CC497C0-A1DF-11CE-8098-00AA0047BE5D}'] + property Name: WideString readonly dispid 0; + property Selection: ITextSelection readonly dispid 1; + property StoryCount: Integer readonly dispid 2; + property StoryRanges: ITextStoryRanges readonly dispid 3; + property Saved: Integer dispid 4; + property DefaultTabStop: Single dispid 5; + procedure New; dispid 6; + procedure Open(const pVar: OleVariant; Flags: Integer; CodePage: Integer); dispid 7; + procedure Save(const pVar: OleVariant; Flags: Integer; CodePage: Integer); dispid 8; + function Freeze: Integer; dispid 9; + function Unfreeze: Integer; dispid 10; + procedure BeginEditCollection; dispid 11; + procedure EndEditCollection; dispid 12; + function Undo(Count: Integer): Integer; dispid 13; + function Redo(Count: Integer): Integer; dispid 14; + function Range(cp1: Integer; cp2: Integer): ITextRange; dispid 15; + function RangeFromPoint(x: Integer; y: Integer): ITextRange; dispid 16; + end; + +// *********************************************************************// +// Interface: ITextRange +// Flags: (4544) Dual NonExtensible OleAutomation Dispatchable +// GUID: {8CC497C2-A1DF-11CE-8098-00AA0047BE5D} +// *********************************************************************// + ITextRange = interface(IDispatch) + ['{8CC497C2-A1DF-11CE-8098-00AA0047BE5D}'] + function Get_Text: WideString; safecall; + procedure Set_Text(const pbstr: WideString); safecall; + function Get_Char: Integer; safecall; + procedure Set_Char(pch: Integer); safecall; + function Get_Duplicate: ITextRange; safecall; + function Get_FormattedText: ITextRange; safecall; + procedure Set_FormattedText(const ppRange: ITextRange); safecall; + function Get_Start: Integer; safecall; + procedure Set_Start(pcpFirst: Integer); safecall; + function Get_End_: Integer; safecall; + procedure Set_End_(pcpLim: Integer); safecall; + function Get_Font: ITextFont; safecall; + procedure Set_Font(const pFont: ITextFont); safecall; + function Get_Para: ITextPara; safecall; + procedure Set_Para(const pPara: ITextPara); safecall; + function Get_StoryLength: Integer; safecall; + function Get_StoryType: Integer; safecall; + procedure Collapse(bStart: Integer); safecall; + function Expand(Unit_: Integer): Integer; safecall; + function GetIndex(Unit_: Integer): Integer; safecall; + procedure SetIndex(Unit_: Integer; Index: Integer; Extend: Integer); safecall; + procedure SetRange(cpActive: Integer; cpOther: Integer); safecall; + function InRange(const pRange: ITextRange): Integer; safecall; + function InStory(const pRange: ITextRange): Integer; safecall; + function IsEqual(const pRange: ITextRange): Integer; safecall; + procedure Select; safecall; + function StartOf(Unit_: Integer; Extend: Integer): Integer; safecall; + function EndOf(Unit_: Integer; Extend: Integer): Integer; safecall; + function Move(Unit_: Integer; Count: Integer): Integer; safecall; + function MoveStart(Unit_: Integer; Count: Integer): Integer; safecall; + function MoveEnd(Unit_: Integer; Count: Integer): Integer; safecall; + function MoveWhile(const Cset: OleVariant; Count: Integer): Integer; safecall; + function MoveStartWhile(const Cset: OleVariant; Count: Integer): Integer; safecall; + function MoveEndWhile(const Cset: OleVariant; Count: Integer): Integer; safecall; + function MoveUntil(const Cset: OleVariant; Count: Integer): Integer; safecall; + function MoveStartUntil(const Cset: OleVariant; Count: Integer): Integer; safecall; + function MoveEndUntil(const Cset: OleVariant; Count: Integer): Integer; safecall; + function FindText(const bstr: WideString; cch: Integer; Flags: Integer): Integer; safecall; + function FindTextStart(const bstr: WideString; cch: Integer; Flags: Integer): Integer; safecall; + function FindTextEnd(const bstr: WideString; cch: Integer; Flags: Integer): Integer; safecall; + function Delete(Unit_: Integer; Count: Integer): Integer; safecall; + procedure Cut(out pVar: OleVariant); safecall; + procedure Copy(out pVar: OleVariant); safecall; + procedure Paste(const pVar: OleVariant; Format: Integer); safecall; + function CanPaste(const pVar: OleVariant; Format: Integer): Integer; safecall; + function CanEdit: Integer; safecall; + procedure ChangeCase(Type_: Integer); safecall; + procedure GetPoint(Type_: Integer; out px: Integer; out py: Integer); safecall; + procedure SetPoint(x: Integer; y: Integer; Type_: Integer; Extend: Integer); safecall; + procedure ScrollIntoView(Value: Integer); safecall; + function GetEmbeddedObject: IUnknown; safecall; + property Text: WideString read Get_Text write Set_Text; + property Char: Integer read Get_Char write Set_Char; + property Duplicate: ITextRange read Get_Duplicate; + property FormattedText: ITextRange read Get_FormattedText write Set_FormattedText; + property Start: Integer read Get_Start write Set_Start; + property End_: Integer read Get_End_ write Set_End_; + property Font: ITextFont read Get_Font write Set_Font; + property Para: ITextPara read Get_Para write Set_Para; + property StoryLength: Integer read Get_StoryLength; + property StoryType: Integer read Get_StoryType; + end; + +// *********************************************************************// +// DispIntf: ITextRangeDisp +// Flags: (4544) Dual NonExtensible OleAutomation Dispatchable +// GUID: {8CC497C2-A1DF-11CE-8098-00AA0047BE5D} +// *********************************************************************// + ITextRangeDisp = dispinterface + ['{8CC497C2-A1DF-11CE-8098-00AA0047BE5D}'] + property Text: WideString dispid 0; + property Char: Integer dispid 513; + property Duplicate: ITextRange readonly dispid 514; + property FormattedText: ITextRange dispid 515; + property Start: Integer dispid 516; + property End_: Integer dispid 517; + property Font: ITextFont dispid 518; + property Para: ITextPara dispid 519; + property StoryLength: Integer readonly dispid 520; + property StoryType: Integer readonly dispid 521; + procedure Collapse(bStart: Integer); dispid 528; + function Expand(Unit_: Integer): Integer; dispid 529; + function GetIndex(Unit_: Integer): Integer; dispid 530; + procedure SetIndex(Unit_: Integer; Index: Integer; Extend: Integer); dispid 531; + procedure SetRange(cpActive: Integer; cpOther: Integer); dispid 532; + function InRange(const pRange: ITextRange): Integer; dispid 533; + function InStory(const pRange: ITextRange): Integer; dispid 534; + function IsEqual(const pRange: ITextRange): Integer; dispid 535; + procedure Select; dispid 536; + function StartOf(Unit_: Integer; Extend: Integer): Integer; dispid 537; + function EndOf(Unit_: Integer; Extend: Integer): Integer; dispid 544; + function Move(Unit_: Integer; Count: Integer): Integer; dispid 545; + function MoveStart(Unit_: Integer; Count: Integer): Integer; dispid 546; + function MoveEnd(Unit_: Integer; Count: Integer): Integer; dispid 547; + function MoveWhile(const Cset: OleVariant; Count: Integer): Integer; dispid 548; + function MoveStartWhile(const Cset: OleVariant; Count: Integer): Integer; dispid 549; + function MoveEndWhile(const Cset: OleVariant; Count: Integer): Integer; dispid 550; + function MoveUntil(const Cset: OleVariant; Count: Integer): Integer; dispid 551; + function MoveStartUntil(const Cset: OleVariant; Count: Integer): Integer; dispid 552; + function MoveEndUntil(const Cset: OleVariant; Count: Integer): Integer; dispid 553; + function FindText(const bstr: WideString; cch: Integer; Flags: Integer): Integer; dispid 560; + function FindTextStart(const bstr: WideString; cch: Integer; Flags: Integer): Integer; dispid 561; + function FindTextEnd(const bstr: WideString; cch: Integer; Flags: Integer): Integer; dispid 562; + function Delete(Unit_: Integer; Count: Integer): Integer; dispid 563; + procedure Cut(out pVar: OleVariant); dispid 564; + procedure Copy(out pVar: OleVariant); dispid 565; + procedure Paste(const pVar: OleVariant; Format: Integer); dispid 566; + function CanPaste(const pVar: OleVariant; Format: Integer): Integer; dispid 567; + function CanEdit: Integer; dispid 568; + procedure ChangeCase(Type_: Integer); dispid 569; + procedure GetPoint(Type_: Integer; out px: Integer; out py: Integer); dispid 576; + procedure SetPoint(x: Integer; y: Integer; Type_: Integer; Extend: Integer); dispid 577; + procedure ScrollIntoView(Value: Integer); dispid 578; + function GetEmbeddedObject: IUnknown; dispid 579; + end; + +// *********************************************************************// +// Interface: ITextSelection +// Flags: (4544) Dual NonExtensible OleAutomation Dispatchable +// GUID: {8CC497C1-A1DF-11CE-8098-00AA0047BE5D} +// *********************************************************************// + ITextSelection = interface(ITextRange) + ['{8CC497C1-A1DF-11CE-8098-00AA0047BE5D}'] + function Get_Flags: Integer; safecall; + procedure Set_Flags(pFlags: Integer); safecall; + function Get_type_: Integer; safecall; + function MoveLeft(Unit_: Integer; Count: Integer; Extend: Integer): Integer; safecall; + function MoveRight(Unit_: Integer; Count: Integer; Extend: Integer): Integer; safecall; + function MoveUp(Unit_: Integer; Count: Integer; Extend: Integer): Integer; safecall; + function MoveDown(Unit_: Integer; Count: Integer; Extend: Integer): Integer; safecall; + function HomeKey(Unit_: Integer; Extend: Integer): Integer; safecall; + function EndKey(Unit_: Integer; Extend: Integer): Integer; safecall; + procedure TypeText(const bstr: WideString); safecall; + property Flags: Integer read Get_Flags write Set_Flags; + property type_: Integer read Get_type_; + end; + +// *********************************************************************// +// DispIntf: ITextSelectionDisp +// Flags: (4544) Dual NonExtensible OleAutomation Dispatchable +// GUID: {8CC497C1-A1DF-11CE-8098-00AA0047BE5D} +// *********************************************************************// + ITextSelectionDisp = dispinterface + ['{8CC497C1-A1DF-11CE-8098-00AA0047BE5D}'] + property Flags: Integer dispid 257; + property type_: Integer readonly dispid 258; + function MoveLeft(Unit_: Integer; Count: Integer; Extend: Integer): Integer; dispid 259; + function MoveRight(Unit_: Integer; Count: Integer; Extend: Integer): Integer; dispid 260; + function MoveUp(Unit_: Integer; Count: Integer; Extend: Integer): Integer; dispid 261; + function MoveDown(Unit_: Integer; Count: Integer; Extend: Integer): Integer; dispid 262; + function HomeKey(Unit_: Integer; Extend: Integer): Integer; dispid 263; + function EndKey(Unit_: Integer; Extend: Integer): Integer; dispid 264; + procedure TypeText(const bstr: WideString); dispid 265; + property Text: WideString dispid 0; + property Char: Integer dispid 513; + property Duplicate: ITextRange readonly dispid 514; + property FormattedText: ITextRange dispid 515; + property Start: Integer dispid 516; + property End_: Integer dispid 517; + property Font: ITextFont dispid 518; + property Para: ITextPara dispid 519; + property StoryLength: Integer readonly dispid 520; + property StoryType: Integer readonly dispid 521; + procedure Collapse(bStart: Integer); dispid 528; + function Expand(Unit_: Integer): Integer; dispid 529; + function GetIndex(Unit_: Integer): Integer; dispid 530; + procedure SetIndex(Unit_: Integer; Index: Integer; Extend: Integer); dispid 531; + procedure SetRange(cpActive: Integer; cpOther: Integer); dispid 532; + function InRange(const pRange: ITextRange): Integer; dispid 533; + function InStory(const pRange: ITextRange): Integer; dispid 534; + function IsEqual(const pRange: ITextRange): Integer; dispid 535; + procedure Select; dispid 536; + function StartOf(Unit_: Integer; Extend: Integer): Integer; dispid 537; + function EndOf(Unit_: Integer; Extend: Integer): Integer; dispid 544; + function Move(Unit_: Integer; Count: Integer): Integer; dispid 545; + function MoveStart(Unit_: Integer; Count: Integer): Integer; dispid 546; + function MoveEnd(Unit_: Integer; Count: Integer): Integer; dispid 547; + function MoveWhile(const Cset: OleVariant; Count: Integer): Integer; dispid 548; + function MoveStartWhile(const Cset: OleVariant; Count: Integer): Integer; dispid 549; + function MoveEndWhile(const Cset: OleVariant; Count: Integer): Integer; dispid 550; + function MoveUntil(const Cset: OleVariant; Count: Integer): Integer; dispid 551; + function MoveStartUntil(const Cset: OleVariant; Count: Integer): Integer; dispid 552; + function MoveEndUntil(const Cset: OleVariant; Count: Integer): Integer; dispid 553; + function FindText(const bstr: WideString; cch: Integer; Flags: Integer): Integer; dispid 560; + function FindTextStart(const bstr: WideString; cch: Integer; Flags: Integer): Integer; dispid 561; + function FindTextEnd(const bstr: WideString; cch: Integer; Flags: Integer): Integer; dispid 562; + function Delete(Unit_: Integer; Count: Integer): Integer; dispid 563; + procedure Cut(out pVar: OleVariant); dispid 564; + procedure Copy(out pVar: OleVariant); dispid 565; + procedure Paste(const pVar: OleVariant; Format: Integer); dispid 566; + function CanPaste(const pVar: OleVariant; Format: Integer): Integer; dispid 567; + function CanEdit: Integer; dispid 568; + procedure ChangeCase(Type_: Integer); dispid 569; + procedure GetPoint(Type_: Integer; out px: Integer; out py: Integer); dispid 576; + procedure SetPoint(x: Integer; y: Integer; Type_: Integer; Extend: Integer); dispid 577; + procedure ScrollIntoView(Value: Integer); dispid 578; + function GetEmbeddedObject: IUnknown; dispid 579; + end; + +// *********************************************************************// +// Interface: ITextFont +// Flags: (4544) Dual NonExtensible OleAutomation Dispatchable +// GUID: {8CC497C3-A1DF-11CE-8098-00AA0047BE5D} +// *********************************************************************// + ITextFont = interface(IDispatch) + ['{8CC497C3-A1DF-11CE-8098-00AA0047BE5D}'] + function Get_Duplicate: ITextFont; safecall; + procedure Set_Duplicate(const ppFont: ITextFont); safecall; + function CanChange: Integer; safecall; + function IsEqual(const pFont: ITextFont): Integer; safecall; + procedure Reset(Value: Integer); safecall; + function Get_Style: Integer; safecall; + procedure Set_Style(pValue: Integer); safecall; + function Get_AllCaps: Integer; safecall; + procedure Set_AllCaps(pValue: Integer); safecall; + function Get_Animation: Integer; safecall; + procedure Set_Animation(pValue: Integer); safecall; + function Get_BackColor: Integer; safecall; + procedure Set_BackColor(pValue: Integer); safecall; + function Get_Bold: Integer; safecall; + procedure Set_Bold(pValue: Integer); safecall; + function Get_Emboss: Integer; safecall; + procedure Set_Emboss(pValue: Integer); safecall; + function Get_ForeColor: Integer; safecall; + procedure Set_ForeColor(pValue: Integer); safecall; + function Get_Hidden: Integer; safecall; + procedure Set_Hidden(pValue: Integer); safecall; + function Get_Engrave: Integer; safecall; + procedure Set_Engrave(pValue: Integer); safecall; + function Get_Italic: Integer; safecall; + procedure Set_Italic(pValue: Integer); safecall; + function Get_Kerning: Single; safecall; + procedure Set_Kerning(pValue: Single); safecall; + function Get_LanguageID: Integer; safecall; + procedure Set_LanguageID(pValue: Integer); safecall; + function Get_Name: WideString; safecall; + procedure Set_Name(const pbstr: WideString); safecall; + function Get_Outline: Integer; safecall; + procedure Set_Outline(pValue: Integer); safecall; + function Get_Position: Single; safecall; + procedure Set_Position(pValue: Single); safecall; + function Get_Protected_: Integer; safecall; + procedure Set_Protected_(pValue: Integer); safecall; + function Get_Shadow: Integer; safecall; + procedure Set_Shadow(pValue: Integer); safecall; + function Get_Size: Single; safecall; + procedure Set_Size(pValue: Single); safecall; + function Get_SmallCaps: Integer; safecall; + procedure Set_SmallCaps(pValue: Integer); safecall; + function Get_Spacing: Single; safecall; + procedure Set_Spacing(pValue: Single); safecall; + function Get_StrikeThrough: Integer; safecall; + procedure Set_StrikeThrough(pValue: Integer); safecall; + function Get_Subscript: Integer; safecall; + procedure Set_Subscript(pValue: Integer); safecall; + function Get_Superscript: Integer; safecall; + procedure Set_Superscript(pValue: Integer); safecall; + function Get_Underline: Integer; safecall; + procedure Set_Underline(pValue: Integer); safecall; + function Get_Weight: Integer; safecall; + procedure Set_Weight(pValue: Integer); safecall; + property Duplicate: ITextFont read Get_Duplicate write Set_Duplicate; + property Style: Integer read Get_Style write Set_Style; + property AllCaps: Integer read Get_AllCaps write Set_AllCaps; + property Animation: Integer read Get_Animation write Set_Animation; + property BackColor: Integer read Get_BackColor write Set_BackColor; + property Bold: Integer read Get_Bold write Set_Bold; + property Emboss: Integer read Get_Emboss write Set_Emboss; + property ForeColor: Integer read Get_ForeColor write Set_ForeColor; + property Hidden: Integer read Get_Hidden write Set_Hidden; + property Engrave: Integer read Get_Engrave write Set_Engrave; + property Italic: Integer read Get_Italic write Set_Italic; + property Kerning: Single read Get_Kerning write Set_Kerning; + property LanguageID: Integer read Get_LanguageID write Set_LanguageID; + property Name: WideString read Get_Name write Set_Name; + property Outline: Integer read Get_Outline write Set_Outline; + property Position: Single read Get_Position write Set_Position; + property Protected_: Integer read Get_Protected_ write Set_Protected_; + property Shadow: Integer read Get_Shadow write Set_Shadow; + property Size: Single read Get_Size write Set_Size; + property SmallCaps: Integer read Get_SmallCaps write Set_SmallCaps; + property Spacing: Single read Get_Spacing write Set_Spacing; + property StrikeThrough: Integer read Get_StrikeThrough write Set_StrikeThrough; + property Subscript: Integer read Get_Subscript write Set_Subscript; + property Superscript: Integer read Get_Superscript write Set_Superscript; + property Underline: Integer read Get_Underline write Set_Underline; + property Weight: Integer read Get_Weight write Set_Weight; + end; + +// *********************************************************************// +// DispIntf: ITextFontDisp +// Flags: (4544) Dual NonExtensible OleAutomation Dispatchable +// GUID: {8CC497C3-A1DF-11CE-8098-00AA0047BE5D} +// *********************************************************************// + ITextFontDisp = dispinterface + ['{8CC497C3-A1DF-11CE-8098-00AA0047BE5D}'] + property Duplicate: ITextFont dispid 0; + function CanChange: Integer; dispid 769; + function IsEqual(const pFont: ITextFont): Integer; dispid 770; + procedure Reset(Value: Integer); dispid 771; + property Style: Integer dispid 772; + property AllCaps: Integer dispid 773; + property Animation: Integer dispid 774; + property BackColor: Integer dispid 775; + property Bold: Integer dispid 776; + property Emboss: Integer dispid 777; + property ForeColor: Integer dispid 784; + property Hidden: Integer dispid 785; + property Engrave: Integer dispid 786; + property Italic: Integer dispid 787; + property Kerning: Single dispid 788; + property LanguageID: Integer dispid 789; + property Name: WideString dispid 790; + property Outline: Integer dispid 791; + property Position: Single dispid 792; + property Protected_: Integer dispid 793; + property Shadow: Integer dispid 800; + property Size: Single dispid 801; + property SmallCaps: Integer dispid 802; + property Spacing: Single dispid 803; + property StrikeThrough: Integer dispid 804; + property Subscript: Integer dispid 805; + property Superscript: Integer dispid 806; + property Underline: Integer dispid 807; + property Weight: Integer dispid 808; + end; + +// *********************************************************************// +// Interface: ITextPara +// Flags: (4544) Dual NonExtensible OleAutomation Dispatchable +// GUID: {8CC497C4-A1DF-11CE-8098-00AA0047BE5D} +// *********************************************************************// + ITextPara = interface(IDispatch) + ['{8CC497C4-A1DF-11CE-8098-00AA0047BE5D}'] + function Get_Duplicate: ITextPara; safecall; + procedure Set_Duplicate(const ppPara: ITextPara); safecall; + function CanChange: Integer; safecall; + function IsEqual(const pPara: ITextPara): Integer; safecall; + procedure Reset(Value: Integer); safecall; + function Get_Style: Integer; safecall; + procedure Set_Style(pValue: Integer); safecall; + function Get_Alignment: Integer; safecall; + procedure Set_Alignment(pValue: Integer); safecall; + function Get_Hyphenation: Integer; safecall; + procedure Set_Hyphenation(pValue: Integer); safecall; + function Get_FirstLineIndent: Single; safecall; + function Get_KeepTogether: Integer; safecall; + procedure Set_KeepTogether(pValue: Integer); safecall; + function Get_KeepWithNext: Integer; safecall; + procedure Set_KeepWithNext(pValue: Integer); safecall; + function Get_LeftIndent: Single; safecall; + function Get_LineSpacing: Single; safecall; + function Get_LineSpacingRule: Integer; safecall; + function Get_ListAlignment: Integer; safecall; + procedure Set_ListAlignment(pValue: Integer); safecall; + function Get_ListLevelIndex: Integer; safecall; + procedure Set_ListLevelIndex(pValue: Integer); safecall; + function Get_ListStart: Integer; safecall; + procedure Set_ListStart(pValue: Integer); safecall; + function Get_ListTab: Single; safecall; + procedure Set_ListTab(pValue: Single); safecall; + function Get_ListType: Integer; safecall; + procedure Set_ListType(pValue: Integer); safecall; + function Get_NoLineNumber: Integer; safecall; + procedure Set_NoLineNumber(pValue: Integer); safecall; + function Get_PageBreakBefore: Integer; safecall; + procedure Set_PageBreakBefore(pValue: Integer); safecall; + function Get_RightIndent: Single; safecall; + procedure Set_RightIndent(pValue: Single); safecall; + procedure SetIndents(StartIndent: Single; LeftIndent: Single; RightIndent: Single); safecall; + procedure SetLineSpacing(LineSpacingRule: Integer; LineSpacing: Single); safecall; + function Get_SpaceAfter: Single; safecall; + procedure Set_SpaceAfter(pValue: Single); safecall; + function Get_SpaceBefore: Single; safecall; + procedure Set_SpaceBefore(pValue: Single); safecall; + function Get_WidowControl: Integer; safecall; + procedure Set_WidowControl(pValue: Integer); safecall; + function Get_TabCount: Integer; safecall; + procedure AddTab(tbPos: Single; tbAlign: Integer; tbLeader: Integer); safecall; + procedure ClearAllTabs; safecall; + procedure DeleteTab(tbPos: Single); safecall; + procedure GetTab(iTab: Integer; out ptbPos: Single; out ptbAlign: Integer; + out ptbLeader: Integer); safecall; + property Duplicate: ITextPara read Get_Duplicate write Set_Duplicate; + property Style: Integer read Get_Style write Set_Style; + property Alignment: Integer read Get_Alignment write Set_Alignment; + property Hyphenation: Integer read Get_Hyphenation write Set_Hyphenation; + property FirstLineIndent: Single read Get_FirstLineIndent; + property KeepTogether: Integer read Get_KeepTogether write Set_KeepTogether; + property KeepWithNext: Integer read Get_KeepWithNext write Set_KeepWithNext; + property LeftIndent: Single read Get_LeftIndent; + property LineSpacing: Single read Get_LineSpacing; + property LineSpacingRule: Integer read Get_LineSpacingRule; + property ListAlignment: Integer read Get_ListAlignment write Set_ListAlignment; + property ListLevelIndex: Integer read Get_ListLevelIndex write Set_ListLevelIndex; + property ListStart: Integer read Get_ListStart write Set_ListStart; + property ListTab: Single read Get_ListTab write Set_ListTab; + property ListType: Integer read Get_ListType write Set_ListType; + property NoLineNumber: Integer read Get_NoLineNumber write Set_NoLineNumber; + property PageBreakBefore: Integer read Get_PageBreakBefore write Set_PageBreakBefore; + property RightIndent: Single read Get_RightIndent write Set_RightIndent; + property SpaceAfter: Single read Get_SpaceAfter write Set_SpaceAfter; + property SpaceBefore: Single read Get_SpaceBefore write Set_SpaceBefore; + property WidowControl: Integer read Get_WidowControl write Set_WidowControl; + property TabCount: Integer read Get_TabCount; + end; + +// *********************************************************************// +// DispIntf: ITextParaDisp +// Flags: (4544) Dual NonExtensible OleAutomation Dispatchable +// GUID: {8CC497C4-A1DF-11CE-8098-00AA0047BE5D} +// *********************************************************************// + ITextParaDisp = dispinterface + ['{8CC497C4-A1DF-11CE-8098-00AA0047BE5D}'] + property Duplicate: ITextPara dispid 0; + function CanChange: Integer; dispid 1025; + function IsEqual(const pPara: ITextPara): Integer; dispid 1026; + procedure Reset(Value: Integer); dispid 1027; + property Style: Integer dispid 1028; + property Alignment: Integer dispid 1029; + property Hyphenation: Integer dispid 1030; + property FirstLineIndent: Single readonly dispid 1031; + property KeepTogether: Integer dispid 1032; + property KeepWithNext: Integer dispid 1033; + property LeftIndent: Single readonly dispid 1040; + property LineSpacing: Single readonly dispid 1041; + property LineSpacingRule: Integer readonly dispid 1042; + property ListAlignment: Integer dispid 1043; + property ListLevelIndex: Integer dispid 1044; + property ListStart: Integer dispid 1045; + property ListTab: Single dispid 1046; + property ListType: Integer dispid 1047; + property NoLineNumber: Integer dispid 1048; + property PageBreakBefore: Integer dispid 1049; + property RightIndent: Single dispid 1056; + procedure SetIndents(StartIndent: Single; LeftIndent: Single; RightIndent: Single); dispid 1057; + procedure SetLineSpacing(LineSpacingRule: Integer; LineSpacing: Single); dispid 1058; + property SpaceAfter: Single dispid 1059; + property SpaceBefore: Single dispid 1060; + property WidowControl: Integer dispid 1061; + property TabCount: Integer readonly dispid 1062; + procedure AddTab(tbPos: Single; tbAlign: Integer; tbLeader: Integer); dispid 1063; + procedure ClearAllTabs; dispid 1064; + procedure DeleteTab(tbPos: Single); dispid 1065; + procedure GetTab(iTab: Integer; out ptbPos: Single; out ptbAlign: Integer; + out ptbLeader: Integer); dispid 1072; + end; + +// *********************************************************************// +// Interface: ITextStoryRanges +// Flags: (4544) Dual NonExtensible OleAutomation Dispatchable +// GUID: {8CC497C5-A1DF-11CE-8098-00AA0047BE5D} +// *********************************************************************// + ITextStoryRanges = interface(IDispatch) + ['{8CC497C5-A1DF-11CE-8098-00AA0047BE5D}'] + function _NewEnum: IUnknown; safecall; + function Item(Index: Integer): ITextRange; safecall; + function Get_Count: Integer; safecall; + property Count: Integer read Get_Count; + end; + +// *********************************************************************// +// DispIntf: ITextStoryRangesDisp +// Flags: (4544) Dual NonExtensible OleAutomation Dispatchable +// GUID: {8CC497C5-A1DF-11CE-8098-00AA0047BE5D} +// *********************************************************************// + ITextStoryRangesDisp = dispinterface + ['{8CC497C5-A1DF-11CE-8098-00AA0047BE5D}'] + function _NewEnum: IUnknown; dispid -4; + function Item(Index: Integer): ITextRange; dispid 0; + property Count: Integer readonly dispid 2; + end; + +// *********************************************************************// +// Interface: ITextDocument2 +// Flags: (4544) Dual NonExtensible OleAutomation Dispatchable +// GUID: {01C25500-4268-11D1-883A-3C8B00C10000} +// *********************************************************************// + ITextDocument2 = interface(ITextDocument) + ['{01C25500-4268-11D1-883A-3C8B00C10000}'] + procedure AttachMsgFilter(const pFilter: IUnknown); safecall; + procedure SetEffectColor(Index: Integer; cr: LongWord); safecall; + procedure GetEffectColor(Index: Integer; out pcr: LongWord); safecall; + function Get_CaretType: Integer; safecall; + procedure Set_CaretType(pCaretType: Integer); safecall; + function GetImmContext: Integer; safecall; + procedure ReleaseImmContext(Context: Integer); safecall; + procedure GetPreferredFont(cp: Integer; CodePage: Integer; Option: Integer; + curCodepage: Integer; curFontSize: Integer; out pbstr: WideString; + out pPitchAndFamily: Integer; out pNewFontSize: Integer); safecall; + function Get_NotificationMode: Integer; safecall; + procedure Set_NotificationMode(pMode: Integer); safecall; + procedure GetClientRect(Type_: Integer; out pLeft: Integer; out pTop: Integer; + out pRight: Integer; out pBottom: Integer); safecall; + function Get_SelectionEx: ITextSelection; safecall; + procedure GetWindow(out phWnd: Integer); safecall; + procedure GetFEFlags(out pFlags: Integer); safecall; + procedure UpdateWindow; safecall; + procedure CheckTextLimit(cch: Integer; var pcch: Integer); safecall; + procedure IMEInProgress(Mode: Integer); safecall; + procedure SysBeep; safecall; + procedure Update(Mode: Integer); safecall; + procedure Notify(Notify: Integer); safecall; + property CaretType: Integer read Get_CaretType write Set_CaretType; + property NotificationMode: Integer read Get_NotificationMode write Set_NotificationMode; + property SelectionEx: ITextSelection read Get_SelectionEx; + end; + +// *********************************************************************// +// DispIntf: ITextDocument2Disp +// Flags: (4544) Dual NonExtensible OleAutomation Dispatchable +// GUID: {01C25500-4268-11D1-883A-3C8B00C10000} +// *********************************************************************// + ITextDocument2Disp = dispinterface + ['{01C25500-4268-11D1-883A-3C8B00C10000}'] + procedure AttachMsgFilter(const pFilter: IUnknown); dispid 21; + procedure SetEffectColor(Index: Integer; cr: LongWord); dispid 22; + procedure GetEffectColor(Index: Integer; out pcr: LongWord); dispid 23; + property CaretType: Integer dispid 24; + function GetImmContext: Integer; dispid 25; + procedure ReleaseImmContext(Context: Integer); dispid 26; + procedure GetPreferredFont(cp: Integer; CodePage: Integer; Option: Integer; + curCodepage: Integer; curFontSize: Integer; out pbstr: WideString; + out pPitchAndFamily: Integer; out pNewFontSize: Integer); dispid 27; + property NotificationMode: Integer dispid 28; + procedure GetClientRect(Type_: Integer; out pLeft: Integer; out pTop: Integer; + out pRight: Integer; out pBottom: Integer); dispid 29; + property SelectionEx: ITextSelection readonly dispid 30; + procedure GetWindow(out phWnd: Integer); dispid 31; + procedure GetFEFlags(out pFlags: Integer); dispid 32; + procedure UpdateWindow; dispid 33; + procedure CheckTextLimit(cch: Integer; var pcch: Integer); dispid 34; + procedure IMEInProgress(Mode: Integer); dispid 35; + procedure SysBeep; dispid 36; + procedure Update(Mode: Integer); dispid 37; + procedure Notify(Notify: Integer); dispid 38; + property Name: WideString readonly dispid 0; + property Selection: ITextSelection readonly dispid 1; + property StoryCount: Integer readonly dispid 2; + property StoryRanges: ITextStoryRanges readonly dispid 3; + property Saved: Integer dispid 4; + property DefaultTabStop: Single dispid 5; + procedure New; dispid 6; + procedure Open(const pVar: OleVariant; Flags: Integer; CodePage: Integer); dispid 7; + procedure Save(const pVar: OleVariant; Flags: Integer; CodePage: Integer); dispid 8; + function Freeze: Integer; dispid 9; + function Unfreeze: Integer; dispid 10; + procedure BeginEditCollection; dispid 11; + procedure EndEditCollection; dispid 12; + function Undo(Count: Integer): Integer; dispid 13; + function Redo(Count: Integer): Integer; dispid 14; + function Range(cp1: Integer; cp2: Integer): ITextRange; dispid 15; + function RangeFromPoint(x: Integer; y: Integer): ITextRange; dispid 16; + end; + +// *********************************************************************// +// Interface: ITextMsgFilter +// Flags: (128) NonExtensible +// GUID: {A3787420-4267-11D1-883A-3C8B00C10000} +// *********************************************************************// + ITextMsgFilter = interface(IUnknown) + ['{A3787420-4267-11D1-883A-3C8B00C10000}'] + function AttachDocument(var hwnd: _RemotableHandle; const pTextDoc: ITextDocument2): HResult; stdcall; + function HandleMessage(var pmsg: SYSUINT; var pwparam: UINT_PTR; var plparam: LONG_PTR; + out plres: LONG_PTR): HResult; stdcall; + function AttachMsgFilter(const pMsgFilter: ITextMsgFilter): HResult; stdcall; + end; + +implementation + +uses System.Win.ComObj; + +end. diff --git a/developer/src/common/web/test-helpers/TestCompilerCallbacks.ts b/developer/src/common/web/test-helpers/TestCompilerCallbacks.ts index 04e5734f18..711944f12d 100644 --- a/developer/src/common/web/test-helpers/TestCompilerCallbacks.ts +++ b/developer/src/common/web/test-helpers/TestCompilerCallbacks.ts @@ -5,6 +5,7 @@ import { CompilerEvent, CompilerCallbacks, CompilerPathCallbacks, CompilerFileSy CompilerFileSystemAsyncCallbacks, CompilerErrorSeverity} from '@keymanapp/developer-utils'; import { fileURLToPath } from 'url'; +import { Suite } from 'mocha'; const { TEST_SAVE_FIXTURES } = process.env; @@ -12,16 +13,27 @@ const { TEST_SAVE_FIXTURES } = process.env; * A CompilerCallbacks implementation for testing */ export class TestCompilerCallbacks implements CompilerCallbacks { - /* TestCompilerCallbacks */ - + private readonly suite: Suite; messages: CompilerEvent[] = []; readonly _net: TestCompilerNetAsyncCallbacks; readonly _fsAsync: DefaultCompilerFileSystemAsyncCallbacks = new DefaultCompilerFileSystemAsyncCallbacks(this); - constructor(basePath?: string) { + constructor(suite?: Suite, basePath?: string) { if(basePath) { this._net = new TestCompilerNetAsyncCallbacks(basePath); } + this.suite = suite; + if(this.suite) { + const _this = this; + this.suite.beforeEach(function() { + _this.clear(); + }); + this.suite.afterEach(function() { + if(this.currentTest?.isFailed()) { + _this.printMessages(); + } + }) + } } clear() { @@ -55,7 +67,7 @@ export class TestCompilerCallbacks implements CompilerCallbacks { loadFile(filename: string): Uint8Array { try { - return fs.readFileSync(filename); + return fs.readFileSync(filename) as Uint8Array; } catch(e) { if (e.code === 'ENOENT') { return null; @@ -162,7 +174,7 @@ class TestCompilerNetAsyncCallbacks implements CompilerNetAsyncCallbacks { // missing file, this is okay return null; } - const data: Uint8Array = fs.readFileSync(p); + const data = fs.readFileSync(p) as Uint8Array; return data; } diff --git a/developer/src/common/web/utils/package.json b/developer/src/common/web/utils/package.json index 0c31f64e46..06d23d5364 100644 --- a/developer/src/common/web/utils/package.json +++ b/developer/src/common/web/utils/package.json @@ -11,7 +11,7 @@ "dependencies": { "@keymanapp/common-types": "*", "eventemitter3": "^5.0.0", - "fast-xml-parser": "^5.3.4", + "fast-xml-parser": "^5.3.8", "path-browserify": "^1.0.1", "restructure": "^3.0.1", "sax": ">=0.6.0", 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 917af99e70..c3d797ad99 100644 --- a/developer/src/common/web/utils/src/developer-utils-messages.ts +++ b/developer/src/common/web/utils/src/developer-utils-messages.ts @@ -79,4 +79,10 @@ export class DeveloperUtilsMessages { this.ERROR_ProjectFileCouldNotBeRead, `Project file could not be read` ); + + static ERROR_NotAPackageFile = SevError | 0x000D; + static Error_NotAPackageFile = () => m( + this.ERROR_NotAPackageFile, + `Package source file is not a valid .kps file because it is missing the root element.`, + ); }; diff --git a/developer/src/common/web/utils/src/types/kps/kps-file-reader.ts b/developer/src/common/web/utils/src/types/kps/kps-file-reader.ts index c41a30ebb4..379bb3668f 100644 --- a/developer/src/common/web/utils/src/types/kps/kps-file-reader.ts +++ b/developer/src/common/web/utils/src/types/kps/kps-file-reader.ts @@ -22,19 +22,17 @@ export class KpsFileReader { public read(file: Uint8Array): KpsPackage { const data = new TextDecoder().decode(file); - const kpsPackage = (() => { - let a: KpsPackage; + let kpsPackage: KpsPackage = null; + try { + kpsPackage = new KeymanXMLReader('kps') + .parse(data) as KpsPackage; + } catch(e) { + this.callbacks.reportMessage(DeveloperUtilsMessages.Error_InvalidPackageFile({e})); + return null; + } - try { - a = new KeymanXMLReader('kps') - .parse(data) as KpsPackage; - } catch(e) { - this.callbacks.reportMessage(DeveloperUtilsMessages.Error_InvalidPackageFile({e})); - } - return a; - })(); - - if(!kpsPackage) { + if(!kpsPackage?.Package) { + this.callbacks.reportMessage(DeveloperUtilsMessages.Error_NotAPackageFile()); return null; } @@ -42,12 +40,12 @@ export class KpsFileReader { } private boxArrays(data: KpsPackage): KpsPackage { - boxXmlArray(data.Package?.Files, 'File'); - boxXmlArray(data.Package?.Keyboards, 'Keyboard'); - boxXmlArray(data.Package?.LexicalModels, 'LexicalModel'); - boxXmlArray(data.Package?.RelatedPackages, 'RelatedPackage'); + boxXmlArray(data.Package.Files, 'File'); + boxXmlArray(data.Package.Keyboards, 'Keyboard'); + boxXmlArray(data.Package.LexicalModels, 'LexicalModel'); + boxXmlArray(data.Package.RelatedPackages, 'RelatedPackage'); - if(data.Package?.Keyboards?.Keyboard) { + if(data.Package.Keyboards?.Keyboard) { for(const k of data.Package.Keyboards.Keyboard) { boxXmlArray(k.Examples, 'Example'); boxXmlArray(k.Languages, 'Language'); @@ -56,17 +54,17 @@ export class KpsFileReader { } } - if(data.Package?.LexicalModels?.LexicalModel) { + if(data.Package.LexicalModels?.LexicalModel) { for(const lm of data.Package.LexicalModels.LexicalModel) { boxXmlArray(lm.Languages, 'Language'); } } - boxXmlArray(data.Package?.RelatedPackages, 'RelatedPackage'); - boxXmlArray(data.Package?.StartMenu?.Items, 'Item'); - boxXmlArray(data.Package?.Strings, 'String'); + boxXmlArray(data.Package.RelatedPackages, 'RelatedPackage'); + boxXmlArray(data.Package.StartMenu?.Items, 'Item'); + boxXmlArray(data.Package.Strings, 'String'); - if(data.Package?.Info) { + if(data.Package.Info) { // If no properties are defined on an subitem, then it will be a // string, and needs to be boxed into a KpsInfoItem object const info: any = data.Package.Info; diff --git a/developer/src/common/web/utils/test/fixtures/kps/error_not_a_package_file.kps b/developer/src/common/web/utils/test/fixtures/kps/error_not_a_package_file.kps new file mode 100644 index 0000000000..05af827783 --- /dev/null +++ b/developer/src/common/web/utils/test/fixtures/kps/error_not_a_package_file.kps @@ -0,0 +1,4 @@ + + + + diff --git a/developer/src/common/web/utils/test/kpj/kpj-file-reader.tests.ts b/developer/src/common/web/utils/test/kpj/kpj-file-reader.tests.ts index 358edb01da..a70d4dd42c 100644 --- a/developer/src/common/web/utils/test/kpj/kpj-file-reader.tests.ts +++ b/developer/src/common/web/utils/test/kpj/kpj-file-reader.tests.ts @@ -6,13 +6,15 @@ import { KPJFileReader } from "../../src/types/kpj/kpj-file-reader.js"; import { KeymanDeveloperProjectFile10, KeymanDeveloperProjectType } from '../../src/types/kpj/keyman-developer-project.js'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; -const callbacks = new TestCompilerCallbacks(); describe('kpj-file-reader', function () { + + const callbacks = new TestCompilerCallbacks(this); + it('kpj-file-reader should read a valid file', async function() { const kpjPath = 'khmer_angkor.kpj'; const path = makePathToFixture('kpj', kpjPath); - const input = fs.readFileSync(path); + const input = fs.readFileSync(path) as Uint8Array; const reader = new KPJFileReader(callbacks); const kpj = reader.read(input); reader.validate(kpj); @@ -124,7 +126,7 @@ describe('kpj-file-reader', function () { it('should load a v1.0 keyboard project with missing ', async function() { const path = makePathToFixture('kpj', 'project-missing-file', 'project_missing_file.kpj'); - const input = fs.readFileSync(path); + const input = fs.readFileSync(path) as Uint8Array; const reader = new KPJFileReader(callbacks); const kpj = reader.read(input); reader.validate(kpj); @@ -140,7 +142,7 @@ describe('kpj-file-reader', function () { it('should load a v1.0 keyboard project with missing ', async function() { const path = makePathToFixture('kpj', 'project-missing-file', 'project_missing_files.kpj'); - const input = fs.readFileSync(path); + const input = fs.readFileSync(path) as Uint8Array; const reader = new KPJFileReader(callbacks); const kpj = reader.read(input); reader.validate(kpj); diff --git a/developer/src/common/web/utils/test/kpj/kpj-file-writer.tests.ts b/developer/src/common/web/utils/test/kpj/kpj-file-writer.tests.ts index d1a7cdddb1..b2c7464e6c 100644 --- a/developer/src/common/web/utils/test/kpj/kpj-file-writer.tests.ts +++ b/developer/src/common/web/utils/test/kpj/kpj-file-writer.tests.ts @@ -1,4 +1,7 @@ -import * as fs from 'fs'; +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ +import * as fs from 'node:fs'; import 'mocha'; import {assert} from 'chai'; import { makePathToFixture } from '../helpers/index.js'; @@ -8,13 +11,13 @@ import { KPJFileWriter } from '../../src/types/kpj/kpj-file-writer.js'; import { KeymanDeveloperProjectOptions } from '../../src/types/kpj/keyman-developer-project.js'; import { SymbolUtils } from '../../src/symbol-utils.js'; -const callbacks = new TestCompilerCallbacks(); - describe('kpj-file-writer', function () { + const callbacks = new TestCompilerCallbacks(this); + it('kpj-file-writer should write a valid v1.0 file', async function() { const kpjPath = 'khmer_angkor.kpj'; const path = makePathToFixture('kpj', kpjPath); - const input = fs.readFileSync(path); + const input = fs.readFileSync(path) as Uint8Array; const reader = new KPJFileReader(callbacks); const inputKpj = reader.read(input); reader.validate(inputKpj); @@ -53,7 +56,7 @@ describe('kpj-file-writer', function () { it('kpj-file-writer should write a valid v2.0 file', async function() { const kpjPath = 'khmer_angkor.kpj'; const path = makePathToFixture('kpj', kpjPath); - const input = fs.readFileSync(path); + const input = fs.readFileSync(path) as Uint8Array; const reader = new KPJFileReader(callbacks); const inputKpj = reader.read(input); reader.validate(inputKpj); diff --git a/developer/src/common/web/utils/test/kps/kps-file-reader.tests.ts b/developer/src/common/web/utils/test/kps/kps-file-reader.tests.ts index 117a48a281..d73f9a526a 100644 --- a/developer/src/common/web/utils/test/kps/kps-file-reader.tests.ts +++ b/developer/src/common/web/utils/test/kps/kps-file-reader.tests.ts @@ -4,7 +4,7 @@ * Created by mcdurdin on 2024-10-16 */ -import * as fs from 'fs'; +import * as fs from 'node:fs'; import 'mocha'; import {assert} from 'chai'; @@ -17,11 +17,13 @@ import { KpsFileWriter } from '../../src/types/kps/kps-file-writer.js'; import { SymbolUtils } from '../../src/symbol-utils.js'; import { DeveloperUtilsMessages } from '../../src/developer-utils-messages.js'; -const callbacks = new TestCompilerCallbacks(); describe('kps-file-reader', function () { + + const callbacks = new TestCompilerCallbacks(this); + it('kps-file-reader should read a valid file', function() { - const input = fs.readFileSync(makePathToFixture('kps', 'khmer_angkor.kps')); + const input = fs.readFileSync(makePathToFixture('kps', 'khmer_angkor.kps')) as Uint8Array; const reader = new KpsFileReader(callbacks); const kps = reader.read(input); @@ -58,7 +60,7 @@ describe('kps-file-reader', function () { }); it('kps-file-reader should round-trip with kps-file-writer', function() { - const input = fs.readFileSync(makePathToFixture('kps', 'khmer_angkor.kps')); + const input = fs.readFileSync(makePathToFixture('kps', 'khmer_angkor.kps')) as Uint8Array; const reader = new KpsFileReader(callbacks); // Remove XML metadata symbols to reduce clutter for testing purposes const kps = SymbolUtils.removeSymbols(reader.read(input)); @@ -75,7 +77,7 @@ describe('kps-file-reader', function () { // ERROR_InvalidPackageFile it('should generate ERROR_InvalidPackageFile if package source file contains invalid XML', async function() { - const input = fs.readFileSync(makePathToFixture('kps', 'error_invalid_package_file.kps')); + const input = fs.readFileSync(makePathToFixture('kps', 'error_invalid_package_file.kps')) as Uint8Array; const reader = new KpsFileReader(callbacks); const kps = reader.read(input); @@ -83,4 +85,16 @@ describe('kps-file-reader', function () { assert.lengthOf(callbacks.messages, 1); assert.isTrue(callbacks.hasMessage(DeveloperUtilsMessages.ERROR_InvalidPackageFile)); }); + + // ERROR_NotAPackageFile + + it(`should generate ERROR_NotAPackageFile when the package source file is valid XML but does not have a root element`, function () { + const input = fs.readFileSync(makePathToFixture('kps', 'error_not_a_package_file.kps')) as Uint8Array; + const reader = new KpsFileReader(callbacks); + const kps = reader.read(input); + + assert.isNull(kps); + assert.isTrue(callbacks.hasMessage(DeveloperUtilsMessages.ERROR_NotAPackageFile)); + }); + }); diff --git a/developer/src/kmc-analyze/package.json b/developer/src/kmc-analyze/package.json index c7e2ebabe2..432bb26e01 100644 --- a/developer/src/kmc-analyze/package.json +++ b/developer/src/kmc-analyze/package.json @@ -29,8 +29,6 @@ }, "devDependencies": { "@keymanapp/resources-gosh": "*", - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "c8": "^7.12.0", "chalk": "^2.4.2", "typescript": "^5.4.5" diff --git a/developer/src/kmc-analyze/test/analyzer-messages.tests.ts b/developer/src/kmc-analyze/test/analyzer-messages.tests.ts index 7839c7fe39..ce4e021968 100644 --- a/developer/src/kmc-analyze/test/analyzer-messages.tests.ts +++ b/developer/src/kmc-analyze/test/analyzer-messages.tests.ts @@ -11,17 +11,7 @@ import { AnalyzeOskCharacterUse } from '../src/osk-character-use/index.js'; describe('AnalyzerMessages', function () { - const callbacks = new TestCompilerCallbacks(); - - this.beforeEach(function() { - callbacks.clear(); - }); - - this.afterEach(function() { - if(this.currentTest?.isFailed()) { - callbacks.printMessages(); - } - }); + const callbacks = new TestCompilerCallbacks(this); it('should have a valid AnalyzerMessages object', function() { return verifyCompilerMessagesObject(AnalyzerMessages, CompilerErrorNamespace.Analyzer); diff --git a/developer/src/kmc-analyze/test/osk-character-use-format.tests.ts b/developer/src/kmc-analyze/test/osk-character-use-format.tests.ts index 9acd87403f..22852af2b4 100644 --- a/developer/src/kmc-analyze/test/osk-character-use-format.tests.ts +++ b/developer/src/kmc-analyze/test/osk-character-use-format.tests.ts @@ -7,23 +7,13 @@ import { AnalyzeOskCharacterUse } from '../src/osk-character-use/index.js'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; describe('AnalyzeOskCharacterUse output formats', function() { - const callbacks = new TestCompilerCallbacks(); + const callbacks = new TestCompilerCallbacks(this); const dummyStrings = { 'a': [{ filename: 'file1.kvks', count: 1 }], 'b': [{ filename: 'file2.kvks', count: 2 }] }; - this.beforeEach(function() { - callbacks.clear(); - }); - - this.afterEach(function() { - if (this.currentTest?.isFailed()) { - callbacks.printMessages(); - } - }); - it('generates .txt format correctly', function() { const a = new AnalyzeOskCharacterUse(callbacks, { includeCounts: true }); a.unitTestEndPoints.addStrings(['a', 'b'], 'testfile.kvks'); diff --git a/developer/src/kmc-analyze/test/osk-character-use-messages.tests.ts b/developer/src/kmc-analyze/test/osk-character-use-messages.tests.ts index 540ef3cb41..362b694c07 100644 --- a/developer/src/kmc-analyze/test/osk-character-use-messages.tests.ts +++ b/developer/src/kmc-analyze/test/osk-character-use-messages.tests.ts @@ -9,7 +9,7 @@ import { AnalyzerMessages } from '../src/analyzer-messages.js'; import { makePathToFixture } from './helpers/index.js'; describe('AnalyzeOskCharacterUse warnings', function() { - const callbacks = new TestCompilerCallbacks(); + const callbacks = new TestCompilerCallbacks(this); const MOCK_MAP_NO_COUNTS = makePathToFixture( 'osk-character-use', @@ -20,16 +20,6 @@ describe('AnalyzeOskCharacterUse warnings', function() { 'mock-map-with-counts.json' ); - this.beforeEach(function() { - callbacks.clear(); - }); - - this.afterEach(function() { - if (this.currentTest?.isFailed()) { - callbacks.printMessages(); - } - }); - it('warns if previous map did not include counts but includeCounts=true', function() { const a = new AnalyzeOskCharacterUse(callbacks, { includeCounts: true @@ -37,7 +27,7 @@ describe('AnalyzeOskCharacterUse warnings', function() { const result = a.unitTestEndPoints.loadPreviousMap(MOCK_MAP_NO_COUNTS); assert.isNotNull(result, 'Expected map to be loaded successfully'); - + assert.isTrue( callbacks.hasMessage(AnalyzerMessages.WARN_PreviousMapDidNotIncludeCounts), 'Expected Warn_PreviousMapDidNotIncludeCounts warning' diff --git a/developer/src/kmc-analyze/test/osk-character-use.tests.ts b/developer/src/kmc-analyze/test/osk-character-use.tests.ts index dac60dcfb6..6bd7001099 100644 --- a/developer/src/kmc-analyze/test/osk-character-use.tests.ts +++ b/developer/src/kmc-analyze/test/osk-character-use.tests.ts @@ -17,17 +17,7 @@ const SIL_KHMER_KVKS = makePathToFixture('khmer', "sil_khmer.kvks"); const KBDKHMR_WITH_SIL_KHMER_JSON = makePathToFixture('khmer', "KbdKhmr-with-sil_khmer.json"); describe('AnalyzeOskCharacterUse', function () { - const callbacks = new TestCompilerCallbacks(); - - this.beforeEach(function() { - callbacks.clear(); - }); - - this.afterEach(function() { - if(this.currentTest?.isFailed()) { - callbacks.printMessages(); - } - }); + const callbacks = new TestCompilerCallbacks(this); it('generates a mapping file from 2 input files', async function() { // functional test diff --git a/developer/src/kmc-convert/package.json b/developer/src/kmc-convert/package.json index b10a1d08d2..f84f55996c 100644 --- a/developer/src/kmc-convert/package.json +++ b/developer/src/kmc-convert/package.json @@ -34,7 +34,6 @@ "devDependencies": { "@keymanapp/developer-test-helpers": "*", "@keymanapp/resources-gosh": "*", - "@types/mocha": "^5.2.7", "@types/node": "^20.4.1", "@types/semver": "^7.3.12", "ajv": "^8.12.0", diff --git a/developer/src/kmc-copy/src/KeymanProjectCopier.ts b/developer/src/kmc-copy/src/KeymanProjectCopier.ts index 545163aded..4218cc6e94 100644 --- a/developer/src/kmc-copy/src/KeymanProjectCopier.ts +++ b/developer/src/kmc-copy/src/KeymanProjectCopier.ts @@ -201,7 +201,7 @@ export class KeymanProjectCopier implements KeymanCompiler { */ private getLocalFolderProject(source: string): string { source = this.normalizePath(source); - const projectSource = this.normalizePath(this.callbacks.path.join(source, this.callbacks.path.basename(source) + KeymanFileTypes.Source.Project)); + const projectSource = this.normalizePath(this.callbacks.path.resolve(this.callbacks.path.join(source, this.callbacks.path.basename(source) + KeymanFileTypes.Source.Project))); if(!this.callbacks.fs.existsSync(projectSource)) { this.callbacks.reportMessage(CopierMessages.Error_CannotFindInputProject({project: source})); return null; @@ -216,7 +216,7 @@ export class KeymanProjectCopier implements KeymanCompiler { * @returns */ private getLocalFileProject(source: string): string { - return this.normalizePath(source); + return this.normalizePath(this.callbacks.path.resolve(source)); } /** diff --git a/developer/src/kmc-copy/test/copier-messages.tests.ts b/developer/src/kmc-copy/test/copier-messages.tests.ts index 0312910860..ea0e9bf624 100644 --- a/developer/src/kmc-copy/test/copier-messages.tests.ts +++ b/developer/src/kmc-copy/test/copier-messages.tests.ts @@ -14,17 +14,7 @@ import { KeymanProjectCopier } from '../src/KeymanProjectCopier.js'; import { makePathToFixture } from './helpers/index.js'; describe('CopierMessages', function () { - const callbacks = new TestCompilerCallbacks(makePathToFixture('online')); - - this.beforeEach(function() { - callbacks.clear(); - }); - - this.afterEach(function() { - if(this.currentTest?.isFailed()) { - callbacks.printMessages(); - } - }); + const callbacks = new TestCompilerCallbacks(this, makePathToFixture('online')); it('should have a valid CopierMessages object', function() { return verifyCompilerMessagesObject(CopierMessages, CompilerErrorNamespace.Copier); diff --git a/developer/src/kmc-copy/test/copier.tests.ts b/developer/src/kmc-copy/test/copier.tests.ts index 697e58e0b5..2acf5bf12c 100644 --- a/developer/src/kmc-copy/test/copier.tests.ts +++ b/developer/src/kmc-copy/test/copier.tests.ts @@ -23,7 +23,7 @@ function normalizeNewLine(s: string): string { } describe('KeymanProjectCopier', function() { - const callbacks = new TestCompilerCallbacks(makePathToFixture('online')); + const callbacks = new TestCompilerCallbacks(this, makePathToFixture('online')); this.beforeAll(function() { if(TEST_SAVE_ARTIFACTS) { @@ -32,16 +32,6 @@ describe('KeymanProjectCopier', function() { } }); - this.beforeEach(function() { - callbacks.clear(); - }); - - this.afterEach(function() { - if(this.currentTest?.isFailed()) { - callbacks.printMessages(); - } - }); - const tests = [ // diff --git a/developer/src/kmc-generate/test/abstract-generator.tests.ts b/developer/src/kmc-generate/test/abstract-generator.tests.ts index 6b3667b2e9..5a0cf800dd 100644 --- a/developer/src/kmc-generate/test/abstract-generator.tests.ts +++ b/developer/src/kmc-generate/test/abstract-generator.tests.ts @@ -3,26 +3,16 @@ */ import 'mocha'; -import * as path from 'path'; -import * as os from 'os'; -import * as fs from 'fs'; +import * as path from 'node:path'; +import * as os from 'node:os'; +import * as fs from 'node:fs'; import { assert } from 'chai'; import { AbstractGenerator, GeneratorArtifacts } from '../src/abstract-generator.js'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { options } from './shared-options.js'; describe('AbstractGenerator', function () { - const callbacks = new TestCompilerCallbacks(); - - this.beforeEach(function() { - callbacks.clear(); - }); - - this.afterEach(function() { - if(this.currentTest.isFailed()) { - callbacks.printMessages(); - } - }); + const callbacks = new TestCompilerCallbacks(this); it('should write out files successfully', async function() { const ag = new AbstractGenerator(); @@ -40,7 +30,7 @@ describe('AbstractGenerator', function () { }; assert.isTrue(await ag.write(artifacts)); assert.isTrue(fs.existsSync(filename)); - const buf = fs.readFileSync(filename); + const buf = fs.readFileSync(filename) as Uint8Array; assert.deepEqual(buf, data); fs.unlinkSync(filename); diff --git a/developer/src/kmc-generate/test/basic-generator.tests.ts b/developer/src/kmc-generate/test/basic-generator.tests.ts index f5cff4044a..d40dc59b33 100644 --- a/developer/src/kmc-generate/test/basic-generator.tests.ts +++ b/developer/src/kmc-generate/test/basic-generator.tests.ts @@ -32,9 +32,10 @@ class BasicGeneratorTest extends BasicGenerator { } describe('BasicGenerator', function () { + const callbacks = new TestCompilerCallbacks(this); + it('should configure default settings', async function() { const bg = new BasicGenerator(); - const callbacks = new TestCompilerCallbacks(); assert.isTrue(await bg.init(callbacks, options)); assert.isTrue(bg.test_preGenerate()); @@ -45,7 +46,6 @@ describe('BasicGenerator', function () { const bg = new BasicGeneratorTest(); bg.setup(); - const callbacks = new TestCompilerCallbacks(); assert.isTrue(await bg.init(callbacks, options)); assert.isTrue(bg.test_preGenerate()); diff --git a/developer/src/kmc-generate/test/generator-messages.tests.ts b/developer/src/kmc-generate/test/generator-messages.tests.ts index b5d3a8837a..d497f43b43 100644 --- a/developer/src/kmc-generate/test/generator-messages.tests.ts +++ b/developer/src/kmc-generate/test/generator-messages.tests.ts @@ -22,17 +22,7 @@ import { LexicalModelGenerator } from '../src/lexical-model-generator.js'; import { options } from './shared-options.js'; describe('GeneratorMessages', function () { - const callbacks = new TestCompilerCallbacks(); - - this.beforeEach(function() { - callbacks.clear(); - }); - - this.afterEach(function() { - if(this.currentTest.isFailed()) { - callbacks.printMessages(); - } - }); + const callbacks = new TestCompilerCallbacks(this); it('should have a valid GeneratorMessages object', function() { return verifyCompilerMessagesObject(GeneratorMessages, CompilerErrorNamespace.Generator); @@ -78,7 +68,6 @@ describe('GeneratorMessages', function () { targets: ['invalid'] }; const bg = new BasicGenerator(); - const callbacks = new TestCompilerCallbacks(); assert(await bg.init(callbacks, testOptions)); assert.isFalse(bg.test_preGenerate()); assert.isTrue(callbacks.hasMessage(GeneratorMessages.ERROR_InvalidTarget)); @@ -89,7 +78,6 @@ describe('GeneratorMessages', function () { id: '???invalid', }; const kkg = new KeymanKeyboardGenerator(); - const callbacks = new TestCompilerCallbacks(); assert.isTrue(await kkg.init(callbacks, testOptions)); assert.isNull(await kkg.run()); assert.isTrue(callbacks.hasMessage(GeneratorMessages.ERROR_InvalidKeymanKeyboardId)); @@ -100,7 +88,6 @@ describe('GeneratorMessages', function () { id: '???invalid', }; const lkg = new LdmlKeyboardGenerator(); - const callbacks = new TestCompilerCallbacks(); assert.isTrue(await lkg.init(callbacks, testOptions)); assert.isNull(await lkg.run()); assert.isTrue(callbacks.hasMessage(GeneratorMessages.ERROR_InvalidLdmlKeyboardId)); @@ -111,7 +98,6 @@ describe('GeneratorMessages', function () { id: 'example.???.invalid', }; const lmg = new LexicalModelGenerator(); - const callbacks = new TestCompilerCallbacks(); assert.isTrue(await lmg.init(callbacks, testOptions)); assert.isNull(await lmg.run()); assert.isTrue(callbacks.hasMessage(GeneratorMessages.ERROR_InvalidLexicalModelId)); diff --git a/developer/src/kmc-generate/test/keyman-keyboard-generator.tests.ts b/developer/src/kmc-generate/test/keyman-keyboard-generator.tests.ts index 61bb710cb2..7c8f993d6b 100644 --- a/developer/src/kmc-generate/test/keyman-keyboard-generator.tests.ts +++ b/developer/src/kmc-generate/test/keyman-keyboard-generator.tests.ts @@ -33,6 +33,8 @@ function getFilenames(p: string, base?: string): string[] { describe('KeymanKeyboardGenerator', function () { let clock: sinon.SinonFakeTimers; + const callbacks = new TestCompilerCallbacks(this); + before(function() { // We will always be 12 April 2024 to match test fixtures clock = sinon.useFakeTimers(new Date(2024, 3, 12)); @@ -44,7 +46,6 @@ describe('KeymanKeyboardGenerator', function () { it('should generate a Keyman keyboard from provided options', async function() { const generator = new KeymanKeyboardGenerator(); - const callbacks = new TestCompilerCallbacks(); assert(await generator.init(callbacks, options)); const result = await generator.run(); assert.exists(result); diff --git a/developer/src/kmc-generate/test/ldml-keyboard-generator.tests.ts b/developer/src/kmc-generate/test/ldml-keyboard-generator.tests.ts index ca64e4d18d..d97dcecfb3 100644 --- a/developer/src/kmc-generate/test/ldml-keyboard-generator.tests.ts +++ b/developer/src/kmc-generate/test/ldml-keyboard-generator.tests.ts @@ -33,6 +33,8 @@ function getFilenames(p: string, base?: string): string[] { describe('LdmlKeyboardGenerator', function () { let clock: sinon.SinonFakeTimers; + const callbacks = new TestCompilerCallbacks(this); + before(function() { // We will always be 12 April 2024 to match test fixtures clock = sinon.useFakeTimers(new Date(2024, 3, 12)); @@ -44,7 +46,6 @@ describe('LdmlKeyboardGenerator', function () { it('should generate a LDML keyboard from provided options', async function() { const generator = new LdmlKeyboardGenerator(); - const callbacks = new TestCompilerCallbacks(); assert(await generator.init(callbacks, options)); const result = await generator.run(); assert.exists(result); diff --git a/developer/src/kmc-generate/test/lexical-model-generator.tests.ts b/developer/src/kmc-generate/test/lexical-model-generator.tests.ts index 39908f25c9..f54dc5290c 100644 --- a/developer/src/kmc-generate/test/lexical-model-generator.tests.ts +++ b/developer/src/kmc-generate/test/lexical-model-generator.tests.ts @@ -35,6 +35,8 @@ function getFilenames(p: string, base?: string): string[] { describe('LexicalModelGenerator', function () { let clock: sinon.SinonFakeTimers; + const callbacks = new TestCompilerCallbacks(this); + before(function() { // We will always be 12 April 2024 to match test fixtures clock = sinon.useFakeTimers(new Date(2024, 3, 12)); @@ -46,7 +48,6 @@ describe('LexicalModelGenerator', function () { it('should generate a lexical model from provided options', async function() { const generator = new LexicalModelGenerator(); - const callbacks = new TestCompilerCallbacks(); const opts: GeneratorOptions = {...options}; opts.id = 'sample.en.sample'; opts.targets = [KeymanTargets.KeymanTarget.any]; diff --git a/developer/src/kmc-keyboard-info/package.json b/developer/src/kmc-keyboard-info/package.json index 5b0c463b3a..db256a6c78 100644 --- a/developer/src/kmc-keyboard-info/package.json +++ b/developer/src/kmc-keyboard-info/package.json @@ -29,8 +29,6 @@ "@keymanapp/langtags": "*" }, "devDependencies": { - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "c8": "^7.12.0", "chalk": "^2.4.2", "typescript": "^5.4.5" diff --git a/developer/src/kmc-keyboard-info/test/keyboard-info-compiler-messages.tests.ts b/developer/src/kmc-keyboard-info/test/keyboard-info-compiler-messages.tests.ts index fcd9dfb8c1..7bb355faef 100644 --- a/developer/src/kmc-keyboard-info/test/keyboard-info-compiler-messages.tests.ts +++ b/developer/src/kmc-keyboard-info/test/keyboard-info-compiler-messages.tests.ts @@ -8,8 +8,6 @@ import { makePathToFixture } from './helpers/index.js'; import { KeyboardInfoCompiler } from '../src/keyboard-info-compiler.js'; import { KeyboardInfoFile } from '../src/keyboard-info-file.js'; -const callbacks = new TestCompilerCallbacks(); - const KHMER_ANGKOR_JS = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.js'); const KHMER_ANGKOR_KPS = makePathToFixture('khmer_angkor', 'source', 'khmer_angkor.kps'); const KHMER_ANGKOR_KMP = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.kmp'); @@ -24,15 +22,7 @@ const KHMER_ANGKOR_SOURCES = { describe('KeyboardInfoCompilerMessages', function () { - this.beforeEach(function() { - callbacks.clear(); - }); - - this.afterEach(function() { - if(this.currentTest?.isFailed()) { - callbacks.printMessages(); - } - }) + const callbacks = new TestCompilerCallbacks(this); it('should have a valid KeyboardInfoCompilerMessages object', function() { return verifyCompilerMessagesObject(KeyboardInfoCompilerMessages, CompilerErrorNamespace.KeyboardInfoCompiler); diff --git a/developer/src/kmc-keyboard-info/test/keyboard-info-compiler.tests.ts b/developer/src/kmc-keyboard-info/test/keyboard-info-compiler.tests.ts index 0bb17e539e..b9345e7508 100644 --- a/developer/src/kmc-keyboard-info/test/keyboard-info-compiler.tests.ts +++ b/developer/src/kmc-keyboard-info/test/keyboard-info-compiler.tests.ts @@ -10,18 +10,6 @@ import { KMX, KeymanFileTypes, KeymanTargets, KmpJsonFile } from '@keymanapp/com import { CompilerCallbacks } from '@keymanapp/developer-utils'; import { KeyboardInfoFile, KeyboardInfoFileLanguage, KeyboardInfoFilePlatform } from './keyboard-info-file.js'; -const callbacks = new TestCompilerCallbacks(); - -beforeEach(function() { - callbacks.clear(); -}); - -afterEach(function() { - if(this.currentTest?.isFailed()) { - callbacks.printMessages(); - } -}); - const KHMER_ANGKOR_KPJ = makePathToFixture('khmer_angkor', 'khmer_angkor.kpj'); const KHMER_ANGKOR_JS = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.js'); const KHMER_ANGKOR_KPS = makePathToFixture('khmer_angkor', 'source', 'khmer_angkor.kps'); @@ -87,6 +75,9 @@ const JAVA_DISPLAY_FONT_INFO = { family: "Java", source: [ JAVA_DISPLAY_FONT ] } const JAVA_OSK_FONT_INFO = { family: "Java Kbd", source: [ JAVA_OSK_FONT ] }; describe('keyboard-info-compiler', function () { + + const callbacks = new TestCompilerCallbacks(this); + it('compile a .keyboard_info file correctly', async function() { const kpjFilename = KHMER_ANGKOR_KPJ; const buildKeyboardInfoFilename = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.keyboard_info'); @@ -844,6 +835,9 @@ describe('keyboard-info-compiler', function () { }); describe('fillLanguageMetadata', function() { + + const callbacks = new TestCompilerCallbacks(this); + const tests: { bcp47: string, lang: KeyboardInfoFileLanguage, commonScript: string }[] = [ // 'und' language subtag diff --git a/developer/src/kmc-kmn/package.json b/developer/src/kmc-kmn/package.json index f9c82fb4e9..88a172dfbb 100644 --- a/developer/src/kmc-kmn/package.json +++ b/developer/src/kmc-kmn/package.json @@ -32,8 +32,6 @@ }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "@types/semver": "^7.3.12", "@types/sinon": "^10.0.13", "@types/sinon-chai": "^3.2.9", diff --git a/developer/src/kmc-kmn/test/compiler.tests.ts b/developer/src/kmc-kmn/test/compiler.tests.ts index cb190f519d..7b67e3dd58 100644 --- a/developer/src/kmc-kmn/test/compiler.tests.ts +++ b/developer/src/kmc-kmn/test/compiler.tests.ts @@ -11,6 +11,8 @@ const keyboardsDir = __dirname + '/../../../../../common/test/keyboards/'; const baselineDir = keyboardsDir + 'baseline/'; describe('Compiler class', function() { + const callbacks = new TestCompilerCallbacks(this); + it('should throw on failure', async function() { const compiler = new KmnCompiler(); const callbacks : any = null; // ERROR @@ -25,14 +27,12 @@ describe('Compiler class', function() { it('should start', async function() { const compiler = new KmnCompiler(); - const callbacks = new TestCompilerCallbacks(); assert(await compiler.init(callbacks, null)); assert(compiler.verifyInitialized()); }); it('should compile a basic keyboard', async function() { const compiler = new KmnCompiler(); - const callbacks = new TestCompilerCallbacks(); assert(await compiler.init(callbacks, {saveDebug: true, shouldAddCompilerVersion: false})); assert(compiler.verifyInitialized()); @@ -60,7 +60,6 @@ describe('Compiler class', function() { it('should build all baseline fixtures', async function() { this.timeout(10000); // there are quite a few fixtures, sometimes CI agents are slow const compiler = new KmnCompiler(); - const callbacks = new TestCompilerCallbacks(); assert(await compiler.init(callbacks, {saveDebug: true, shouldAddCompilerVersion: false})); assert(compiler.verifyInitialized()); @@ -90,7 +89,6 @@ describe('Compiler class', function() { it('should compile a keyboard with visual keyboard', async function() { const compiler = new KmnCompiler(); - const callbacks = new TestCompilerCallbacks(); assert.isTrue(await compiler.init(callbacks, { saveDebug: true, shouldAddCompilerVersion: false, diff --git a/developer/src/kmc-kmn/test/features.tests.ts b/developer/src/kmc-kmn/test/features.tests.ts index 1f82a03ed5..6d8f0bde19 100644 --- a/developer/src/kmc-kmn/test/features.tests.ts +++ b/developer/src/kmc-kmn/test/features.tests.ts @@ -7,19 +7,14 @@ import { KMX, KmxFileReader } from '@keymanapp/common-types'; describe('Keyboard compiler features', function() { let compiler: KmnCompiler = null; - let callbacks: TestCompilerCallbacks = null; + const callbacks = new TestCompilerCallbacks(this); this.beforeAll(async function() { compiler = new KmnCompiler(); - callbacks = new TestCompilerCallbacks(); assert(await compiler.init(callbacks, {saveDebug: true})); assert(compiler.verifyInitialized()); }); - beforeEach(function() { - callbacks.clear(); - }); - // Test each Keyman file version target const versions = [ diff --git a/developer/src/kmc-kmn/test/kmn-compiler-messages.tests.ts b/developer/src/kmc-kmn/test/kmn-compiler-messages.tests.ts index 055535513d..f241c02606 100644 --- a/developer/src/kmc-kmn/test/kmn-compiler-messages.tests.ts +++ b/developer/src/kmc-kmn/test/kmn-compiler-messages.tests.ts @@ -7,7 +7,7 @@ import { KmnCompilerMessageRanges, KmnCompilerMessages } from '../src/compiler/k import { makePathToFixture } from './helpers/index.js'; describe('KmnCompilerMessages', function () { - const callbacks = new TestCompilerCallbacks(); + const callbacks = new TestCompilerCallbacks(this); it('should have a valid KmnCompilerMessages object', function() { return verifyCompilerMessagesObject(KmnCompilerMessages, CompilerErrorNamespace.KmnCompiler); diff --git a/developer/src/kmc-kmn/test/kmw/kmw-compiler.tests.ts b/developer/src/kmc-kmn/test/kmw/kmw-compiler.tests.ts index a1008416aa..9fb4563d29 100644 --- a/developer/src/kmc-kmn/test/kmw/kmw-compiler.tests.ts +++ b/developer/src/kmc-kmn/test/kmw/kmw-compiler.tests.ts @@ -25,7 +25,7 @@ const generateTestFilenames = (id: string) => ({ }); describe('KeymanWeb Compiler', function() { - const callbacks = new TestCompilerCallbacks(); + const callbacks = new TestCompilerCallbacks(this); const kmnCompiler: KmnCompiler = new KmnCompiler(); this.beforeAll(async function() { @@ -35,13 +35,6 @@ describe('KeymanWeb Compiler', function() { })); }); - this.afterEach(function() { - if(this.currentTest?.isFailed() || debug) { - callbacks.printMessages(); - } - callbacks.clear(); - }); - it('should compile a complex keyboard', async function() { await run_test_keyboard(kmnCompiler, 'khmer_angkor'); }); diff --git a/developer/src/kmc-kmn/test/kmw/kmw-messages.tests.ts b/developer/src/kmc-kmn/test/kmw/kmw-messages.tests.ts index a69db77020..175afdfe7a 100644 --- a/developer/src/kmc-kmn/test/kmw/kmw-messages.tests.ts +++ b/developer/src/kmc-kmn/test/kmw/kmw-messages.tests.ts @@ -7,7 +7,7 @@ import { KmnCompiler } from '../../src/main.js'; import { CompilerErrorNamespace } from '@keymanapp/developer-utils'; describe('KmwCompilerMessages', function () { - const callbacks = new TestCompilerCallbacks(); + const callbacks = new TestCompilerCallbacks(this); it('should have a valid KmwCompilerMessages object', function() { return verifyCompilerMessagesObject(KmwCompilerMessages, CompilerErrorNamespace.KmwCompiler); diff --git a/developer/src/kmc-kmn/test/wasm-uset.tests.ts b/developer/src/kmc-kmn/test/wasm-uset.tests.ts index ad67b35246..11a3e9a487 100644 --- a/developer/src/kmc-kmn/test/wasm-uset.tests.ts +++ b/developer/src/kmc-kmn/test/wasm-uset.tests.ts @@ -6,6 +6,8 @@ import { KmnCompilerMessages } from '../src/compiler/kmn-compiler-messages.js'; import { compilerErrorFormatCode } from '@keymanapp/developer-utils'; describe('Compiler UnicodeSet function', function() { + const callbacks = new TestCompilerCallbacks(this); + it('should fixup "short" \\u{} escapes', function () { assert.equal(KmnCompiler.fixNewPattern(`\\u{A}`), `\\u000A`); // " assert.equal(KmnCompiler.fixNewPattern(`\\u{22}`), `\\u0022`); // " @@ -24,14 +26,12 @@ describe('Compiler UnicodeSet function', function() { it('should start', async function() { const compiler = new KmnCompiler(); - const callbacks = new TestCompilerCallbacks(); assert(await compiler.init(callbacks, null)); assert(compiler.verifyInitialized()); }); it('should compile a basic uset', async function() { const compiler = new KmnCompiler(); - const callbacks = new TestCompilerCallbacks(); assert(await compiler.init(callbacks, null)); assert(compiler.verifyInitialized()); @@ -49,7 +49,6 @@ describe('Compiler UnicodeSet function', function() { }); it('should compile a more complex uset', async function() { const compiler = new KmnCompiler(); - const callbacks = new TestCompilerCallbacks(); assert(await compiler.init(callbacks, null)); assert(compiler.verifyInitialized()); @@ -69,7 +68,6 @@ describe('Compiler UnicodeSet function', function() { }); it('should compile an even more complex uset', async function() { const compiler = new KmnCompiler(); - const callbacks = new TestCompilerCallbacks(); assert(await compiler.init(callbacks, null)); assert(compiler.verifyInitialized()); @@ -96,7 +94,6 @@ describe('Compiler UnicodeSet function', function() { }); it('should fail in various ways', async function() { const compiler = new KmnCompiler(); - const callbacks = new TestCompilerCallbacks(); assert(await compiler.init(callbacks, null)); assert(compiler.verifyInitialized()); // map from string to failing error diff --git a/developer/src/kmc-ldml/package.json b/developer/src/kmc-ldml/package.json index 9e2a1c6831..1a586eec78 100644 --- a/developer/src/kmc-ldml/package.json +++ b/developer/src/kmc-ldml/package.json @@ -35,8 +35,6 @@ "@keymanapp/developer-test-helpers": "*", "@keymanapp/resources-gosh": "*", "@types/common-tags": "^1.8.4", - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "@types/semver": "^7.3.12", "abnf": "^4.3.1", "c8": "^7.12.0", diff --git a/developer/src/kmc-model-info/package.json b/developer/src/kmc-model-info/package.json index c747a922c4..7d1e7f2fef 100644 --- a/developer/src/kmc-model-info/package.json +++ b/developer/src/kmc-model-info/package.json @@ -34,8 +34,6 @@ "@keymanapp/developer-utils": "*" }, "devDependencies": { - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "c8": "^7.12.0", "chalk": "^2.4.2", "typescript": "^5.4.5" diff --git a/developer/src/kmc-model-info/test/model-info-compiler.tests.ts b/developer/src/kmc-model-info/test/model-info-compiler.tests.ts index 4b0a85624e..5f87ad0f21 100644 --- a/developer/src/kmc-model-info/test/model-info-compiler.tests.ts +++ b/developer/src/kmc-model-info/test/model-info-compiler.tests.ts @@ -6,13 +6,9 @@ import { makePathToFixture } from './helpers/index.js'; import { ModelInfoCompiler } from '../src/model-info-compiler.js'; import { KmpCompiler } from '@keymanapp/kmc-package'; -const callbacks = new TestCompilerCallbacks(); - -beforeEach(function() { - callbacks.clear(); -}); - describe('model-info-compiler', function () { + const callbacks = new TestCompilerCallbacks(this); + it('compile a .model_info file correctly', async function() { const kpjFilename = makePathToFixture('sil.cmo.bw', 'sil.cmo.bw.model.kpj'); const kpsFilename = makePathToFixture('sil.cmo.bw', 'source', 'sil.cmo.bw.model.kps'); diff --git a/developer/src/kmc-model/package.json b/developer/src/kmc-model/package.json index 481822e686..52ef40c425 100644 --- a/developer/src/kmc-model/package.json +++ b/developer/src/kmc-model/package.json @@ -37,8 +37,6 @@ "devDependencies": { "@keymanapp/developer-test-helpers": "*", "@keymanapp/models-templates": "*", - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "c8": "^7.12.0", "chalk": "^2.4.2", "esbuild": "^0.25.0" diff --git a/developer/src/kmc-model/test/compile-model-with-pseudoclosure.tests.ts b/developer/src/kmc-model/test/compile-model-with-pseudoclosure.tests.ts index c4d3334b6a..6a8b55af7e 100644 --- a/developer/src/kmc-model/test/compile-model-with-pseudoclosure.tests.ts +++ b/developer/src/kmc-model/test/compile-model-with-pseudoclosure.tests.ts @@ -7,7 +7,7 @@ import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { LexicalModelTypes } from '@keymanapp/common-types'; describe('LexicalModelCompiler - pseudoclosure compilation + use', function () { - const callbacks = new TestCompilerCallbacks(); + const callbacks = new TestCompilerCallbacks(this); const MODEL_ID = 'example.qaa.trivial'; const PATH = makePathToFixture(MODEL_ID); diff --git a/developer/src/kmc-model/test/compile-model.tests.ts b/developer/src/kmc-model/test/compile-model.tests.ts index e5c9e86c15..5c6a8ab104 100644 --- a/developer/src/kmc-model/test/compile-model.tests.ts +++ b/developer/src/kmc-model/test/compile-model.tests.ts @@ -7,7 +7,7 @@ import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { KeymanFileTypes } from '@keymanapp/common-types'; describe('LexicalModelCompiler', function () { - const callbacks = new TestCompilerCallbacks(); + const callbacks = new TestCompilerCallbacks(this); this.timeout(5000); diff --git a/developer/src/kmc-model/test/compile-trie.tests.ts b/developer/src/kmc-model/test/compile-trie.tests.ts index 81de56987e..24f05bb7ec 100644 --- a/developer/src/kmc-model/test/compile-trie.tests.ts +++ b/developer/src/kmc-model/test/compile-trie.tests.ts @@ -11,10 +11,7 @@ import { TrieModel } from '@keymanapp/models-templates'; import { LexicalModelTypes } from '@keymanapp/common-types'; describe('LexicalModelCompiler', function () { - const callbacks = new TestCompilerCallbacks(); - this.beforeEach(function() { - callbacks.clear(); - }); + const callbacks = new TestCompilerCallbacks(this); describe('#generateLexicalModelCode', function () { it('should compile a trivial word list', async function () { diff --git a/developer/src/kmc-model/test/messages.tests.ts b/developer/src/kmc-model/test/messages.tests.ts index f9d3eb07b8..8d3bb10c08 100644 --- a/developer/src/kmc-model/test/messages.tests.ts +++ b/developer/src/kmc-model/test/messages.tests.ts @@ -8,17 +8,7 @@ import { makePathToFixture } from './helpers/index.js'; describe('ModelCompilerMessages', function () { - const callbacks = new TestCompilerCallbacks(); - - this.beforeEach(function() { - callbacks.clear(); - }); - - this.afterEach(function() { - if(this.currentTest?.isFailed()) { - callbacks.printMessages(); - } - }); + const callbacks = new TestCompilerCallbacks(this); it('should have a valid ModelCompilerMessages object', function() { return verifyCompilerMessagesObject(ModelCompilerMessages, CompilerErrorNamespace.ModelCompiler); diff --git a/developer/src/kmc-model/test/parse-wordlist.tests.ts b/developer/src/kmc-model/test/parse-wordlist.tests.ts index ada01fbc2b..ba4f6cb92b 100644 --- a/developer/src/kmc-model/test/parse-wordlist.tests.ts +++ b/developer/src/kmc-model/test/parse-wordlist.tests.ts @@ -22,11 +22,10 @@ const SENCOTEN_WORDLIST = { describe('parsing a word list', function () { - const testCallbacks = new TestCompilerCallbacks(); + const callbacks = new TestCompilerCallbacks(this); beforeEach(function () { - testCallbacks.clear(); - setCompilerCallbacks(testCallbacks); + setCompilerCallbacks(callbacks); }); afterEach(function () { @@ -43,12 +42,12 @@ describe('parsing a word list', function () { const withoutBOM: WordList = {}; parseWordListFromContents(withoutBOM, file); assert.deepEqual(withoutBOM, expected, "expected regular file to parse properly"); - assert.isEmpty(testCallbacks.messages); + assert.isEmpty(callbacks.messages); const withBOM: WordList = {}; parseWordListFromContents(withBOM, `${BOM}${file}`) assert.deepEqual(withBOM, expected, "expected BOM to be ignored"); - assert.isEmpty(testCallbacks.messages); + assert.isEmpty(callbacks.messages); }); it('should read word lists in UTF-8', function () { @@ -58,7 +57,7 @@ describe('parsing a word list', function () { parseWordListFromFilename(wordlist, filename); assert.deepEqual(wordlist, SENCOTEN_WORDLIST); - assert.isEmpty(testCallbacks.messages); + assert.isEmpty(callbacks.messages); }); it('should read word lists in UTF-16 little-endian (with BOM)', function () { @@ -69,7 +68,7 @@ describe('parsing a word list', function () { parseWordListFromFilename(wordlist, filename); assert.deepEqual(wordlist, SENCOTEN_WORDLIST); - assert.isEmpty(testCallbacks.messages); + assert.isEmpty(callbacks.messages); }); it('should NOT read word lists in UTF-16 big-endian (with BOM)', function () { @@ -106,22 +105,22 @@ describe('parsing a word list', function () { assert.deepEqual(repeatedWords, expected); - assert.lengthOf(testCallbacks.messages, 4); + assert.lengthOf(callbacks.messages, 4); // hello has been seen multiple times: - assert.isTrue(testCallbacks.hasMessage(ModelCompilerMessages.HINT_DuplicateWordInSameFile)); + assert.isTrue(callbacks.hasMessage(ModelCompilerMessages.HINT_DuplicateWordInSameFile)); // helló and hello + U+0301 have both been seen: - assert.isTrue(testCallbacks.hasMessage(ModelCompilerMessages.HINT_MixedNormalizationForms)); + assert.isTrue(callbacks.hasMessage(ModelCompilerMessages.HINT_MixedNormalizationForms)); // Let's parse another file: - testCallbacks.clear(); + callbacks.clear(); // Now, parse a DIFFERENT file, but with an NFD entry. parseWordListFromContents(repeatedWords, "hello\u0301\t5\n"); - assert.lengthOf(testCallbacks.messages, 1); + assert.lengthOf(callbacks.messages, 1); // hello + U+0301 (NFD) has been seen, but... - assert.isTrue(testCallbacks.hasMessage(ModelCompilerMessages.HINT_MixedNormalizationForms)); + assert.isTrue(callbacks.hasMessage(ModelCompilerMessages.HINT_MixedNormalizationForms)); // BUT! We have not seen a duplicate **within the same file** - assert.isFalse(testCallbacks.hasMessage(ModelCompilerMessages.HINT_DuplicateWordInSameFile)); + assert.isFalse(callbacks.hasMessage(ModelCompilerMessages.HINT_DuplicateWordInSameFile)); assert.deepEqual(repeatedWords, { hello: expected['hello'], diff --git a/developer/src/kmc-model/test/punctuation.tests.ts b/developer/src/kmc-model/test/punctuation.tests.ts index c3679f08d9..e0f1d661b4 100644 --- a/developer/src/kmc-model/test/punctuation.tests.ts +++ b/developer/src/kmc-model/test/punctuation.tests.ts @@ -6,12 +6,13 @@ import { makePathToFixture, compileModelSourceCode } from './helpers/index.js'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; describe('LexicalModelCompiler', function () { + const callbacks = new TestCompilerCallbacks(this); + describe('specifying punctuation', function () { const MODEL_ID = 'example.qaa.trivial'; const PATH = makePathToFixture(MODEL_ID); it('should compile punctuation into the generated code', async function () { - const callbacks = new TestCompilerCallbacks(); const compiler = new LexicalModelCompiler(); assert.isTrue(await compiler.init(callbacks, null)); diff --git a/developer/src/kmc-package/package.json b/developer/src/kmc-package/package.json index edfba42073..e441b5d57b 100644 --- a/developer/src/kmc-package/package.json +++ b/developer/src/kmc-package/package.json @@ -36,8 +36,6 @@ }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "c8": "^7.12.0", "chalk": "^2.4.2", "typescript": "^5.4.5" diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index 4999336cee..141547354b 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -206,7 +206,8 @@ export class KmpCompiler implements KeymanCompiler { fileVersion: null, keymanDeveloperVersion: KEYMAN_VERSION.VERSION }, - options: {} + options: {}, + info: {}, }; // diff --git a/developer/src/kmc-package/src/compiler/package-validation.ts b/developer/src/kmc-package/src/compiler/package-validation.ts index 725822be82..33fca76b97 100644 --- a/developer/src/kmc-package/src/compiler/package-validation.ts +++ b/developer/src/kmc-package/src/compiler/package-validation.ts @@ -70,7 +70,7 @@ export class PackageValidation { } private checkForModelsAndKeyboardsInSamePackage(kmpJson: KmpJsonFile.KmpJsonFile): boolean { - if(kmpJson.lexicalModels?.length > 0 && kmpJson.keyboards?.length > 0) { + if((kmpJson.lexicalModels?.length ?? 0) > 0 && (kmpJson.keyboards?.length ?? 0) > 0) { this.callbacks.reportMessage(PackageCompilerMessages.Error_PackageCannotContainBothModelsAndKeyboards()); return false; } @@ -120,7 +120,7 @@ export class PackageValidation { } for(const keyboard of kmpJson.keyboards) { - if(!this.checkForDuplicatedOrNonMinimalLanguages('keyboard', keyboard.id, keyboard.languages)) { + if(!this.checkForDuplicatedOrNonMinimalLanguages('keyboard', keyboard.id, keyboard.languages ?? [])) { return false; } // Note: package-version-validation verifies that there is a corresponding @@ -131,7 +131,7 @@ export class PackageValidation { } private checkContentFiles(kmpJson: KmpJsonFile.KmpJsonFile, outputFilename: string): boolean { - for(const file of kmpJson.files) { + for(const file of kmpJson.files ?? []) { if(!this.checkContentFile(file, outputFilename)) { return false; } @@ -202,7 +202,7 @@ export class PackageValidation { return true; } - private checkPackageInfo(file: KmpJsonFile.KmpJsonFile) { + private checkPackageInfo(file: KmpJsonFile.KmpJsonFile): boolean { if(!file.info || !file.info.name || !file.info.name.description.trim()) { this.callbacks.reportMessage(PackageCompilerMessages.Error_PackageNameCannotBeBlank()); return false; @@ -214,11 +214,11 @@ export class PackageValidation { /* c8 ignore next 3 */ if (match === null) { this.callbacks.reportMessage(PackageCompilerMessages.Error_InvalidAuthorEmail({email:file.info.author.url})); - return null; + return false; } if(!isValidEmail(match[2])) { this.callbacks.reportMessage(PackageCompilerMessages.Error_InvalidAuthorEmail({email:file.info.author.url})); - return null; + return false; } } diff --git a/developer/src/kmc-package/test/messages.tests.ts b/developer/src/kmc-package/test/messages.tests.ts index 21f1e0e4a2..934226e024 100644 --- a/developer/src/kmc-package/test/messages.tests.ts +++ b/developer/src/kmc-package/test/messages.tests.ts @@ -10,15 +10,10 @@ import { makePathToFixture } from './helpers/index.js'; import { KmpCompiler } from '../src/compiler/kmp-compiler.js'; import { CompilerErrorNamespace, CompilerOptions } from '@keymanapp/developer-utils'; -const callbacks = new TestCompilerCallbacks(); describe('PackageCompilerMessages', function () { - this.afterEach(function() { - if(this.currentTest.isFailed()) { - callbacks.printMessages(); - } - }); + const callbacks = new TestCompilerCallbacks(this); it('should have a valid PackageCompilerMessages object', function() { return verifyCompilerMessagesObject(PackageCompilerMessages, CompilerErrorNamespace.PackageCompiler); diff --git a/developer/src/kmc-package/test/package-compiler.tests.ts b/developer/src/kmc-package/test/package-compiler.tests.ts index 33b00e6f00..f2d71e77a6 100644 --- a/developer/src/kmc-package/test/package-compiler.tests.ts +++ b/developer/src/kmc-package/test/package-compiler.tests.ts @@ -21,22 +21,14 @@ describe('KmpCompiler', function () { 'example.qaa.sencoten', 'withfolders.qaa.sencoten', ]; - const callbacks = new TestCompilerCallbacks(); + const callbacks = new TestCompilerCallbacks(this); let kmpCompiler: KmpCompiler = null; this.beforeAll(async function() { - callbacks.clear(); kmpCompiler = new KmpCompiler(); assert.isTrue(await kmpCompiler.init(callbacks, null)); }); - this.afterEach(function() { - if(this.currentTest?.isFailed()) { - callbacks.printMessages(); - } - callbacks.clear(); - }); - for (const modelID of MODELS) { const kpsPath = modelID.includes('withfolders') ? makePathToFixture(modelID, 'source', `${modelID}.model.kps`) : makePathToFixture(modelID, `${modelID}.model.kps`); @@ -144,8 +136,6 @@ describe('KmpCompiler', function () { */ it(`should transform a .kps file for a keyboard package to a correct kmp.json`, function () { - callbacks.clear(); - const kpsPath = makePathToFixture('kmp.json', 'ahom_star.kps'); const kmpJsonRefPath = makePathToFixture('kmp.json', 'kmp.json'); @@ -171,13 +161,9 @@ describe('KmpCompiler', function () { }); it(`should support .kps 17.0 metadata correctly`, function () { - callbacks.clear(); - const kpsPath = makePathToFixture('kmp_2.0', 'khmer_angkor.kps'); const kmpJsonRefPath = makePathToFixture('kmp_2.0', 'kmp.json'); - debugger; - const kmpJsonActual = kmpCompiler.transformKpsToKmpObject(kpsPath); if(kmpJsonActual == null) { callbacks.printMessages(); @@ -205,8 +191,6 @@ describe('KmpCompiler', function () { it('should warn on absolute paths', async function() { this.timeout(10000); // building a zip file can sometimes be slow - callbacks.clear(); - const kpsPath = makePathToFixture('absolute_path', 'source', 'absolute_path.kps'); const kmpCompiler = new KmpCompiler(); assert.isTrue(await kmpCompiler.init(callbacks, null)); @@ -233,8 +217,6 @@ describe('KmpCompiler', function () { it('should normalize DOS pathnames from \\ to /', async function() { // this.timeout(10000); // building a zip file can sometimes be slow - callbacks.clear(); - const kpsPath = makePathToFixture('normalize_paths', 'source', 'khmer_angkor.kps'); const kmpCompiler = new KmpCompiler(); assert.isTrue(await kmpCompiler.init(callbacks, null)); @@ -315,7 +297,7 @@ describe('KmpCompiler', function () { assert.isFalse(validation.validate(inputFilename, outputFilename, kmpJson)); }); - it(`should reject an package that contains itself`, function () { + it(`should reject a package that contains itself`, function () { const inputFilename = makePathToFixture('invalid', 'error_package_must_not_contain_itself.kps'); const outputFilename = makePathToFixture('invalid', 'error_package_must_not_contain_itself.kmp'); const kmpJson = kmpCompiler.transformKpsToKmpObject(inputFilename); diff --git a/developer/src/kmc-package/test/versioning.tests.ts b/developer/src/kmc-package/test/versioning.tests.ts index adcba30019..91ec523e65 100644 --- a/developer/src/kmc-package/test/versioning.tests.ts +++ b/developer/src/kmc-package/test/versioning.tests.ts @@ -11,6 +11,7 @@ import { makePathToFixture } from './helpers/index.js'; // test results documented below. describe('package versioning', function () { + const callbacks = new TestCompilerCallbacks(this); const cases: [string,string][] = [ ['test-single-version-1-package', 'test1.kps'], @@ -33,7 +34,6 @@ describe('package versioning', function () { for(const [ caseTitle, filename ] of cases) { it(caseTitle, async function () { - const callbacks = new TestCompilerCallbacks(); const kmpCompiler = new KmpCompiler(); assert.isTrue(await kmpCompiler.init(callbacks, null)); diff --git a/developer/src/kmc-package/test/windows-package-installer-compiler.tests.ts b/developer/src/kmc-package/test/windows-package-installer-compiler.tests.ts index f61b65d8aa..c4843942a7 100644 --- a/developer/src/kmc-package/test/windows-package-installer-compiler.tests.ts +++ b/developer/src/kmc-package/test/windows-package-installer-compiler.tests.ts @@ -9,6 +9,8 @@ import { makePathToFixture } from './helpers/index.js'; import { WindowsPackageInstallerCompiler, WindowsPackageInstallerSources } from '../src/compiler/windows-package-installer-compiler.js'; describe('WindowsPackageInstallerCompiler', function () { + const callbacks = new TestCompilerCallbacks(this); + it(`should build an SFX archive`, async function () { this.timeout(10000); // this test can take a little while to run @@ -22,7 +24,6 @@ describe('WindowsPackageInstallerCompiler', function () { appName: 'Testing', }; - const callbacks = new TestCompilerCallbacks(); const compiler = new WindowsPackageInstallerCompiler(); assert.isTrue(await compiler.init(callbacks, {sources})); @@ -44,7 +45,7 @@ describe('WindowsPackageInstallerCompiler', function () { const zipBuffer = sfxBuffer.slice(setupExeSize); // Verify setup.exe sfx loader - const setupExeFixture = fs.readFileSync(sources.setupExeFilename); + const setupExeFixture = fs.readFileSync(sources.setupExeFilename) as Uint8Array; assert.deepEqual(setupExe, setupExeFixture); // Load the zip from the buffer @@ -68,7 +69,7 @@ khmer_angkor.kmp assert.equal(setupInf.trim(), setupInfFixture.trim()); const verifyFile = async (filename: string) => { - const fixture = fs.readFileSync(filename); + const fixture = fs.readFileSync(filename) as Uint8Array; const file = await zipFile.file(path.basename(filename)).async('uint8array'); assert.deepEqual(file, fixture, `File in zip '${filename}' did not match fixture`); }; diff --git a/developer/src/kmc/package.json b/developer/src/kmc/package.json index de0acd3c4f..6e0f47a32e 100644 --- a/developer/src/kmc/package.json +++ b/developer/src/kmc/package.json @@ -54,8 +54,6 @@ ], "devDependencies": { "@sentry/cli": "^2.31.0", - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "c8": "^7.12.0", "esbuild": "^0.25.0", "typescript": "^5.4.5" diff --git a/developer/src/kmc/src/commands/copy.ts b/developer/src/kmc/src/commands/copy.ts index 9cfef2b957..b53c9eab93 100644 --- a/developer/src/kmc/src/commands/copy.ts +++ b/developer/src/kmc/src/commands/copy.ts @@ -81,7 +81,7 @@ async function doCopy(callbacks: NodeCompilerCallbacks, sources: string | string const dest = options.outPath; callbacks.reportMessage(InfrastructureMessages.Info_CopyingProject({source, dest})); - const result = runCopier(callbacks, source, options); + const result = await runCopier(callbacks, source, options); if(result) { callbacks.reportMessage(InfrastructureMessages.Info_ProjectCopiedSuccessfully({source, dest})); } else { diff --git a/developer/src/kmc/test/build.tests.ts b/developer/src/kmc/test/build.tests.ts index f8356db54b..b615c97f49 100644 --- a/developer/src/kmc/test/build.tests.ts +++ b/developer/src/kmc/test/build.tests.ts @@ -1,3 +1,6 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { clearOptions } from '../src/util/options.js'; import { assert } from 'chai'; @@ -5,8 +8,6 @@ import 'mocha'; import { BuildProject } from '../src/commands/buildClasses/BuildProject.js'; import { makePathToFixture } from './helpers/index.js'; -const callbacks = new TestCompilerCallbacks(); - interface CompilerWarningsAsErrorsTruthTable { cli: boolean; kpj: boolean; @@ -14,8 +15,9 @@ interface CompilerWarningsAsErrorsTruthTable { }; describe('compilerWarningsAsErrors', function () { - beforeEach(() => { - callbacks.clear(); + const callbacks = new TestCompilerCallbacks(this); + + this.beforeEach(function() { clearOptions(); }); diff --git a/developer/src/kmc/test/extendedCompilerOptions.tests.ts b/developer/src/kmc/test/extendedCompilerOptions.tests.ts index b2f04c81b7..b6950d6981 100644 --- a/developer/src/kmc/test/extendedCompilerOptions.tests.ts +++ b/developer/src/kmc/test/extendedCompilerOptions.tests.ts @@ -1,3 +1,6 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { assert } from 'chai'; import 'mocha'; @@ -10,10 +13,7 @@ interface MessageTest {input: string, result: CompilerMessageOverride}; interface InvalidMessageTest {input: string, code: number}; describe('commandOptionsMessageToCompilerOptionsMessage', function () { - const callbacks = new TestCompilerCallbacks(); - this.beforeEach(function() { - callbacks.clear(); - }); + const callbacks = new TestCompilerCallbacks(this); const valid: MessageTest[] = [ // Test the allowable coercions diff --git a/developer/src/kmc/test/fileLists.tests.ts b/developer/src/kmc/test/fileLists.tests.ts index 5092ff849d..94e379e864 100644 --- a/developer/src/kmc/test/fileLists.tests.ts +++ b/developer/src/kmc/test/fileLists.tests.ts @@ -1,11 +1,12 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { assert } from 'chai'; import 'mocha'; import { makePathToFixture } from './helpers/index.js'; import { expandFileList, expandFileLists } from '../src/util/fileLists.js'; -const callbacks = new TestCompilerCallbacks(); - // expandFileList expands each file name relative to the file list's supplied // filename, so we need to compare against the full path. const expectedFileList = [ @@ -21,11 +22,9 @@ const expectedFiles = [ 'file5.kmn' ]; -beforeEach(function() { - callbacks.clear(); -}); - describe('expandFileList', function () { + const callbacks = new TestCompilerCallbacks(this); + it('should report a missing filelist correctly', async function() { const path = makePathToFixture('file-lists', 'does-not-exist.txt'); @@ -45,6 +44,8 @@ describe('expandFileList', function () { }); describe('expandFileLists', function () { + const callbacks = new TestCompilerCallbacks(this); + it('should splice a filelist in correctly', async function() { // We just use this to test the splicing so no path resolution is made const files = [ diff --git a/developer/src/kmc/test/generateKmnKeyboard.tests.ts b/developer/src/kmc/test/generateKmnKeyboard.tests.ts index 811adc49b3..a5e39675d4 100644 --- a/developer/src/kmc/test/generateKmnKeyboard.tests.ts +++ b/developer/src/kmc/test/generateKmnKeyboard.tests.ts @@ -1,3 +1,6 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; @@ -28,17 +31,15 @@ describe('generateKeymanKeyboard', function() { description: 'Sample Keyboard Test', }; - const callbacks: TestCompilerCallbacks = new TestCompilerCallbacks(); + const callbacks: TestCompilerCallbacks = new TestCompilerCallbacks(this); let outPath: string = null; this.beforeEach(function() { - callbacks.clear(); outPath = fs.mkdtempSync(path.join(os.tmpdir(), 'kmc-')); }); this.afterEach(function() { if(this.currentTest.isFailed()) { - callbacks.printMessages(); console.error(`Output kept at ${outPath}`); } else { if(outPath) fs.rmSync(outPath, {recursive: true, force: true}); diff --git a/developer/src/kmc/test/generateLdmlKeyboard.tests.ts b/developer/src/kmc/test/generateLdmlKeyboard.tests.ts index f28d1fec8f..3b92b31f87 100644 --- a/developer/src/kmc/test/generateLdmlKeyboard.tests.ts +++ b/developer/src/kmc/test/generateLdmlKeyboard.tests.ts @@ -1,3 +1,6 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; @@ -28,17 +31,15 @@ describe('generateLdmlKeyboard', function() { description: 'Sample Keyboard Test', }; - const callbacks: TestCompilerCallbacks = new TestCompilerCallbacks(); + const callbacks: TestCompilerCallbacks = new TestCompilerCallbacks(this); let outPath: string = null; this.beforeEach(function() { - callbacks.clear(); outPath = fs.mkdtempSync(path.join(os.tmpdir(), 'kmc-')); }); this.afterEach(function() { if(this.currentTest.isFailed()) { - callbacks.printMessages(); console.error(`Output kept at ${outPath}`); } else { if(outPath) fs.rmSync(outPath, {recursive: true, force: true}); diff --git a/developer/src/kmc/test/generateLexicalModel.tests.ts b/developer/src/kmc/test/generateLexicalModel.tests.ts index 47a48098b8..7ed7f5eaf9 100644 --- a/developer/src/kmc/test/generateLexicalModel.tests.ts +++ b/developer/src/kmc/test/generateLexicalModel.tests.ts @@ -1,3 +1,6 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; @@ -27,17 +30,15 @@ describe('generateLexicalModel', function() { description: 'Sample Model Test', }; - const callbacks: TestCompilerCallbacks = new TestCompilerCallbacks(); + const callbacks: TestCompilerCallbacks = new TestCompilerCallbacks(this); let outPath: string = null; this.beforeEach(function() { - callbacks.clear(); outPath = fs.mkdtempSync(path.join(os.tmpdir(), 'kmc-')); }); this.afterEach(function() { if(this.currentTest.isFailed()) { - callbacks.printMessages(); console.error(`Output kept at ${outPath}`); } else { if(outPath) fs.rmSync(outPath, {recursive: true, force: true}); diff --git a/developer/src/kmc/test/project-build.tests.ts b/developer/src/kmc/test/project-build.tests.ts index 87a627b749..3e0bc56daf 100644 --- a/developer/src/kmc/test/project-build.tests.ts +++ b/developer/src/kmc/test/project-build.tests.ts @@ -1,3 +1,6 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; import { assert } from 'chai'; import 'mocha'; @@ -6,9 +9,10 @@ import { makePathToFixture } from './helpers/index.js'; import { InfrastructureMessages } from '../src/messages/infrastructureMessages.js'; import { clearOptions } from '../src/util/options.js'; -const callbacks = new TestCompilerCallbacks(); describe('BuildProject', function () { + const callbacks = new TestCompilerCallbacks(this); + it('should build a keyboard project', async function() { clearOptions(); const builder = new BuildProject(); diff --git a/developer/src/samples/imsample/.gitignore b/developer/src/samples/imsample/.gitignore new file mode 100644 index 0000000000..fa2abae434 --- /dev/null +++ b/developer/src/samples/imsample/.gitignore @@ -0,0 +1,2 @@ +IMSample.exp +IMSample.lib \ No newline at end of file diff --git a/developer/src/server/build.sh b/developer/src/server/build.sh index 73eb5c9b45..f2ff0c35d6 100755 --- a/developer/src/server/build.sh +++ b/developer/src/server/build.sh @@ -52,10 +52,6 @@ function clean_server() { function configure_server() { node_select_version_and_npm_ci - # See https://github.com/bubenshchykov/ngrok/issues/254, https://github.com/bubenshchykov/ngrok/pull/255 - # TODO: this is horrible; is there a way we can avoid this? - rm -f "$KEYMAN_ROOT"/node_modules/ngrok/bin/ngrok - rm -f "$KEYMAN_ROOT"/node_modules/ngrok/bin/ngrok.exe } function build_addins() { @@ -130,9 +126,6 @@ function installer_server() { pushd "$PRODBUILDTEMP" npm install --omit=dev --omit=optional - # See https://github.com/bubenshchykov/ngrok/issues/254, https://github.com/bubenshchykov/ngrok/pull/255 - rm -f node_modules/ngrok/bin/ngrok - rm -f node_modules/ngrok/bin/ngrok.exe popd # Dependencies are required in build/ but we need to copy them in manually diff --git a/developer/src/server/package.json b/developer/src/server/package.json index 37192ca6f3..4d30ea9844 100644 --- a/developer/src/server/package.json +++ b/developer/src/server/package.json @@ -10,11 +10,11 @@ "license": "MIT", "dependencies": { "@keymanapp/developer-utils": "*", + "@ngrok/ngrok": "^1.7.0", "@sentry/node": "^7.57.0", "chalk": "^4.1.2", "express": "^4.20.0", - "multer": "^2.0.2", - "ngrok": "^5.0.0-beta.2", + "multer": "^2.1.1", "open": "^8.4.0", "restructure": "^3.0.1", "sax": ">=0.6.0", @@ -29,9 +29,7 @@ "@keymanapp/keyman-version": "*", "@keymanapp/resources-gosh": "*", "@types/express": "^4.17.13", - "@types/mocha": "^9.1.0", "@types/multer": "^1.4.12", - "@types/node": "^20.4.1", "@types/ws": "^8.2.2", "copyfiles": "^2.4.1", "node-gyp": "^10.2.0", diff --git a/developer/src/server/src/config.ts b/developer/src/server/src/config.ts index 8d6c967855..e60ec6d0a0 100644 --- a/developer/src/server/src/config.ts +++ b/developer/src/server/src/config.ts @@ -8,7 +8,6 @@ export class Configuration { public readonly lockFilename: string; public readonly pidFilename: string; public readonly configFilename: string; - public readonly ngrokBinPath: string; /* Configuration values - set in config.json by TIKE */ @@ -17,7 +16,6 @@ export class Configuration { /* ngrok Configuration */ public readonly useNgrok: boolean; - public readonly ngrokControlPort: number; public readonly ngrokToken: string; public readonly ngrokVisible: boolean; @@ -42,8 +40,6 @@ export class Configuration { // ngrok configuration this.useNgrok = cfg?.useNgrok ?? false; - this.ngrokBinPath = this.appDataPath + 'bin/'; - this.ngrokControlPort = cfg?.ngrokControlPort ?? 8009; this.ngrokToken = cfg?.ngrokToken ?? ''; this.ngrokVisible = cfg?.ngrokVisible ?? false; } diff --git a/developer/src/server/src/index.ts b/developer/src/server/src/index.ts index 85de2fcce6..36fd8431d8 100644 --- a/developer/src/server/src/index.ts +++ b/developer/src/server/src/index.ts @@ -1,5 +1,4 @@ import * as fs from 'node:fs'; -import * as os from 'node:os'; import * as path from 'node:path'; import chalk from 'chalk'; import express from 'express'; @@ -77,7 +76,7 @@ export async function run() { setupRoutes(app, upload, wsServer, environment); - /* Start the server */ + /* Start the web server */ let server = null; try { @@ -87,6 +86,8 @@ export async function run() { // TODO handle and cleanup EADDRINUSE, throw anything else } + /* Attach the web socket server */ + server.on('upgrade', (request, socket, head) => { wsServer.handleUpgrade(request, socket, head, socket => { wsServer.emit('connection', socket, request); @@ -96,45 +97,66 @@ export async function run() { /* Launch ngrok if enabled */ configuration.ngrokEndpoint = ''; + if(configuration.useNgrok) { + await startNGrok(); + } - if(configuration.useNgrok - && os.platform() == 'win32' - && fs.existsSync(path.join(configuration.ngrokBinPath, 'ngrok.exe')) - ) { - const ngrok: any = await import('ngrok'); - (async function() { - configuration.ngrokEndpoint = await ngrok.connect({ - proto: 'http', - addr: configuration.port, - authtoken: configuration.ngrokToken, - binPath: () => configuration.ngrokBinPath, - onLogEvent: (msg: string) => { - if(options.ngrokLog) { - console.log(chalk.cyan(('\n'+msg).split('\n').join('\n[ngrok] ').trim())); - } - }, - onStatusChange: (state: string) => { - if(state == 'connected') { - setTimeout(async () => { - const api = ngrok.getApi(); - const tunnels = await api.listTunnels(); - configuration.ngrokEndpoint = tunnels.tunnels[0]?.public_url ?? ''; - console.log(chalk.blueBright('ngrok tunnel established at %s'), configuration.ngrokEndpoint); - }, 1000); - } else if(state == 'closed') { - configuration.ngrokEndpoint = ''; - console.log(chalk.blueBright('ngrok tunnel closed')); - } + /* Load the tray icon */ + + tray.start(configuration.port, configuration.ngrokEndpoint); +} + +async function loadNGrok() { + try { + return await import('@ngrok/ngrok'); + } catch(e) { + // ngrok can fail import if it cannot load its dependent binary library, + // either because it is missing or because it has missing dependencies + // itself. But we don't want to crash the server if that happens + console.error(e); + return null; + } +} + +async function startNGrok() { + console.log('Attempting to start ngrok'); + const ngrok = await loadNGrok(); + if(!ngrok) { + return false; + } + + try { + let started = false; + const listener = await ngrok.forward({ + proto: 'http', + addr: configuration.port, + authtoken: configuration.ngrokToken, + onLogEvent: (msg: string) => { + if(options.ngrokLog) { + console.log(chalk.cyan(('\n'+msg).split('\n').join('\n[ngrok] ').trim())); } - }); - console.log(chalk.blueBright('ngrok tunnel initially established at %s'), configuration.ngrokEndpoint); - tray.start(configuration.port, configuration.ngrokEndpoint); - })(); - } - else { - /* Load the tray icon */ - tray.start(configuration.port, configuration.ngrokEndpoint); + }, + onStatusChange: (state: string) => { + if(state == 'connected' && started) { + // We only announce reconnection after initial start + configuration.ngrokEndpoint = listener.url() ?? ''; + console.log(chalk.blueBright('ngrok tunnel reconnected at %s'), configuration.ngrokEndpoint); + } else if(state == 'closed') { + configuration.ngrokEndpoint = ''; + console.log(chalk.blueBright('ngrok tunnel closed')); + } + } + }); + started = true; + configuration.ngrokEndpoint = listener.url(); + console.log(chalk.blueBright('ngrok tunnel established at %s'), configuration.ngrokEndpoint); + } catch(e) { + configuration.ngrokEndpoint = ''; + console.error(chalk.red('ngrok tunnel failed to connect with an error: %s'), e); + return false; } + + return true; } function getRunningInstancePid(pidFilename: string) { diff --git a/developer/src/server/src/ngrok.d.ts b/developer/src/server/src/ngrok.d.ts deleted file mode 100644 index 61f00d7a0b..0000000000 --- a/developer/src/server/src/ngrok.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'ngrok'; \ No newline at end of file diff --git a/developer/src/server/src/win32/trayicon/addon-src/package-lock.json b/developer/src/server/src/win32/trayicon/addon-src/package-lock.json index ab91216cd4..246f1b5ffe 100644 --- a/developer/src/server/src/win32/trayicon/addon-src/package-lock.json +++ b/developer/src/server/src/win32/trayicon/addon-src/package-lock.json @@ -18,25 +18,6 @@ "node-gyp": "^12.1.0" } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", - "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -90,6 +71,14 @@ "node": ">= 14" } }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -98,6 +87,17 @@ "file-uri-to-path": "1.0.0" } }, + "node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/cacache": { "version": "20.0.3", "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.3.tgz", @@ -318,14 +318,14 @@ } }, "node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -642,9 +642,9 @@ } }, "node_modules/tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.10.tgz", + "integrity": "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", @@ -717,19 +717,6 @@ } }, "dependencies": { - "@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==" - }, - "@isaacs/brace-expansion": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", - "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", - "requires": { - "@isaacs/balanced-match": "^4.0.1" - } - }, "@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -768,6 +755,11 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==" }, + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" + }, "bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -776,6 +768,14 @@ "file-uri-to-path": "1.0.0" } }, + "brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "requires": { + "balanced-match": "^4.0.2" + } + }, "cacache": { "version": "20.0.3", "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.3.tgz", @@ -936,11 +936,11 @@ } }, "minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "requires": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" } }, "minipass": { @@ -1166,9 +1166,9 @@ } }, "tar": { - "version": "7.5.7", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz", - "integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.10.tgz", + "integrity": "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==", "requires": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", diff --git a/developer/src/setup/UfrmRun.pas b/developer/src/setup/UfrmRun.pas index 291dd7b374..08aef3dc4e 100644 --- a/developer/src/setup/UfrmRun.pas +++ b/developer/src/setup/UfrmRun.pas @@ -55,11 +55,13 @@ type InstallSize: Integer; end; + TInstallUILevel = (uiSilent, uiProgressOnly, uiFull); + TfrmRun = class(TSetupForm) public procedure cmdExitClick(Sender: TObject); procedure lblTitleClick(Sender: TObject); - procedure cmdInstallKeymanDesktopClick(Sender: TObject); + procedure cmdInstallKeymanDeveloperClick(Sender: TObject); procedure FormCreate(Sender: TObject); override; procedure FormShow(Sender: TObject); override; @@ -71,17 +73,13 @@ type FDownloadFilename: WideString; FDownloadURL: WideString; FNewVersion, FInstalledVersion, FInstallerVersion: TMSIInfo; - //FInstalledVersion: WideString; FContinueSetup: Boolean; FWhiteBrush: THandle; FErrorLog: TStream; FPromptForReboot: Boolean; // I3355 // I3500 - FSilent: Boolean; + FUILevel: TInstallUILevel; FOfflineByRequest: Boolean; FOnline: Boolean; - //FInstalledProductCode: WideString; - //FInstallerProductCode: WideString; - //FNewProductCode: WideString; procedure LogError(const msg: WideString; ShowDialogIfNotSilent: Boolean = True); @@ -107,7 +105,7 @@ type function DialogID: Integer; override; constructor Create(AOwner: TComponent); override; destructor Destroy; override; - function DoInstall(Silent, PromptForReboot: Boolean): Boolean; // I3355 // I3500 + function DoInstall(UILevel: TInstallUILevel; PromptForReboot: Boolean): Boolean; // I3355 // I3500 property ContinueSetup: Boolean read FContinueSetup write FContinueSetup; property Offline: Boolean read FOfflineByRequest write SetOfflineByRequest; end; @@ -225,7 +223,7 @@ end; function TfrmRun.InstallNewVersion: Boolean; begin Result := False; - if not FSilent and (MessageDlgW(FInstallInfo.Text(ssQueryUpdateVersion, [FNewVersion.InstallSize div 1024, FNewVersion.Version]), // I2680 + if (FUILevel = uiFull) and (MessageDlgW(FInstallInfo.Text(ssQueryUpdateVersion, [FNewVersion.InstallSize div 1024, FNewVersion.Version]), // I2680 mtConfirmation, mbOkCancel, 0) = mrCancel) then Exit; @@ -320,12 +318,12 @@ end; procedure TfrmRun.WMUserFormShown(var Message: TMessage); begin if FContinueSetup then - DoInstall(False, True); // I3355 // I3500 + DoInstall(uiFull, True); // I3355 // I3500 end; -procedure TfrmRun.cmdInstallKeymanDesktopClick(Sender: TObject); +procedure TfrmRun.cmdInstallKeymanDeveloperClick(Sender: TObject); begin - if DoInstall(False, True) then // I1901 and I2680.4 // I3355 // I3500 + if DoInstall(uiFull, True) then // I1901 and I2680.4 // I3355 // I3500 ModalResult := mrOk; end; @@ -392,11 +390,10 @@ begin end; end; -function TfrmRun.DoInstall(Silent, PromptForReboot: Boolean): Boolean; // I1901 // I3355 // I3500 +function TfrmRun.DoInstall(UILevel: TInstallUILevel; PromptForReboot: Boolean): Boolean; // I1901 // I3355 // I3500 begin FPromptForReboot := PromptForReboot; // I3355 // I3500 - FSilent := Silent; - + FUILevel := UILevel; SetCursor(LoadCursor(0, IDC_WAIT)); try @@ -671,8 +668,10 @@ begin FCacheFileName := CacheMSIFile(ExtPath + FInstallInfo.MSIFileName); // I3310 - if FSilent then + if FUILevel = uiSilent then MsiSetInternalUI(INSTALLUILEVEL_NONE, nil) + else if FUILevel = uiProgressOnly then + MsiSetInternalUI(INSTALLUILEVEL_BASIC, nil) else MsiSetInternalUI(INSTALLUILEVEL_FULL, nil); res := MsiInstallProductW(PWideChar(FCacheFileName), PWideChar('DISABLEFINALOPTIONS=1 '+ReinstallMode)); @@ -694,16 +693,13 @@ begin end; end; - if not FSilent then + if FUILevel = uiFull then MsiSetInternalUI(INSTALLUILEVEL_FULL, nil); if FNewVersion.Filename <> '' then begin //Run the setup app silently // I2680.5 TUtilExecute.WaitForProcess('"'+ExtPath + FNewVersion.FileName+'" -s -o', ExtPath, SW_SHOW, WaitFor); // I3349 - //if not FSilent then - // MsiSetInternalUI(INSTALLUILEVEL_BASIC, nil); - //CheckMSIResult(MsiApplyPatchW(PWideChar(ExtPath + FNewVersion.Filename), nil, INSTALLTYPE_DEFAULT, '')); end; end; @@ -712,7 +708,7 @@ function TfrmRun.ProcessCommand(ID, NotificationCode: Integer; begin Result := True; case ID of - IDOK: cmdInstallKeymanDesktopClick(Self); + IDOK: cmdInstallKeymanDeveloperClick(Self); IDCANCEL: cmdExitClick(Self); else Result := False; end; @@ -727,7 +723,7 @@ procedure TfrmRun.LogError(const msg: WideString; ShowDialogIfNotSilent: Boolean const nl: WideString = #13#10; begin - if not FSilent and ShowDialogIfNotSilent then + if (FUILevel = uiFull) and ShowDialogIfNotSilent then ShowMessageW(msg); if not Assigned(FErrorLog) then begin diff --git a/developer/src/setup/bootstrapmain.pas b/developer/src/setup/bootstrapmain.pas index 469a6a64e1..1a8380f368 100644 --- a/developer/src/setup/bootstrapmain.pas +++ b/developer/src/setup/bootstrapmain.pas @@ -1,18 +1,18 @@ (* Name: bootstrapmain Copyright: Copyright (C) SIL International. - Documentation: - Description: + Documentation: + Description: Create Date: 4 Jun 2007 Modified Date: 12 Aug 2014 Authors: mcdurdin - Related Files: - Dependencies: + Related Files: + Dependencies: - Bugs: - Todo: - Notes: + Bugs: + Todo: + Notes: History: 04 Jun 2007 - mcdurdin - Initial version 05 Jun 2007 - mcdurdin - I817 - Fix -x option 19 Jun 2007 - mcdurdin - I817 - Translate to Unicode and remove Forms dependence @@ -123,12 +123,12 @@ begin DeletePath(TempPath); // I3310 end; -procedure ProcessCommandLine(var FPromptForReboot, FSilent, FOffline, FExtractOnly, FContinueSetup: Boolean; var FExtractPath: WideString); // I3355 // I3500 +procedure ProcessCommandLine(var FUILevel: TInstallUILevel; var FPromptForReboot, FOffline, FExtractOnly, FContinueSetup: Boolean; var FExtractPath: WideString); // I3355 // I3500 var i: Integer; begin FPromptForReboot := True; // I3355 // I3500 - FSilent := False; + FUILevel := uiFull; FOffline := False; FExtractOnly := False; FContinueSetup := False; @@ -139,13 +139,13 @@ begin FContinueSetup := True else if WideSameText(ParamStr(i), '-au') then // I2849 begin - FSilent := True; + FUILevel := uiProgressOnly; FOffline := True; end else if WideSameText(ParamStr(i), '-s') then begin FPromptForReboot := False; // I3355 // I3500 - FSilent := True; + FUILevel := uiSilent; end else if WideSameText(ParamStr(i), '-o') then FOffline := True @@ -169,7 +169,7 @@ var FExtractOnly: Boolean; FContinueSetup: Boolean; FPromptForReboot: Boolean; // I3355 // I3500 - FSilent: Boolean; + FUILevel: TInstallUILevel; FOffline: Boolean; FExtractOnly_Path: WideString; BEGIN @@ -193,11 +193,11 @@ BEGIN { Display the dialog } //Cancel_From_InfoWin := False; - ProcessCommandLine(FPromptForReboot, FSilent, FOffline, FExtractOnly, FContinueSetup, FExtractOnly_Path); // I3355 // I3500 + ProcessCommandLine(FUILevel, FPromptForReboot, FOffline, FExtractOnly, FContinueSetup, FExtractOnly_Path); // I3355 // I3500 if FExtractOnly then begin - DoExtractOnly(FSilent, FExtractOnly_Path); + DoExtractOnly(FUILevel = uiSilent, FExtractOnly_Path); Exit; end; @@ -224,8 +224,8 @@ BEGIN try ContinueSetup := FContinueSetup; Offline := FOffline; - if FSilent - then DoInstall(True, FPromptForReboot) // I3355 // I3500 + if FUILevel <> uiFull + then DoInstall(FUILevel, FPromptForReboot) // I3355 // I3500 else ShowModal; finally Free; diff --git a/developer/src/tike/child/Keyman.Developer.UI.Debug.UfrmLdmlKeyboardDebug.dfm b/developer/src/tike/child/Keyman.Developer.UI.Debug.UfrmLdmlKeyboardDebug.dfm index cf5e4b735a..614ff28392 100644 --- a/developer/src/tike/child/Keyman.Developer.UI.Debug.UfrmLdmlKeyboardDebug.dfm +++ b/developer/src/tike/child/Keyman.Developer.UI.Debug.UfrmLdmlKeyboardDebug.dfm @@ -1,92 +1,94 @@ -inherited frmLdmlKeyboardDebug: TfrmLdmlKeyboardDebug - Left = 85 - Top = 229 - BorderIcons = [biSystemMenu] - BorderStyle = bsSizeToolWin - Caption = 'Debug' - ClientHeight = 132 - ClientWidth = 755 - Font.Name = 'MS Sans Serif' - FormStyle = fsStayOnTop - KeyPreview = True - Position = poDefault - OnClose = FormClose - OnDestroy = FormDestroy - OnResize = FormResize - ExplicitWidth = 771 - ExplicitHeight = 171 - PixelsPerInch = 96 - TextHeight = 13 - object memo: TKeymanDeveloperDebuggerMemo - Left = 0 - Top = 0 - Width = 755 - Height = 73 - Cursor = crIBeam - Align = alClient - BorderStyle = bsNone - Font.Charset = ANSI_CHARSET - Font.Color = clWindowText - Font.Height = -11 - Font.Name = 'Arial' - Font.Style = [] - ParentFont = False - PlainText = True - PopupMenu = mnuPopup - ScrollBars = ssVertical - TabOrder = 0 - OnChange = memoChange - OnClick = memoClick - OnEnter = memoGotFocus - OnKeyUp = memoKeyUp - OnMessage = memoMessage - IsDebugging = False - end - object sgChars: TStringGrid - Left = 0 - Top = 73 - Width = 755 - Height = 59 - Align = alBottom - ColCount = 1 - DefaultDrawing = False - FixedCols = 0 - RowCount = 2 - FixedRows = 0 - ScrollBars = ssNone - TabOrder = 1 - OnDrawCell = sgCharsDrawCell - ColWidths = ( - 64) - RowHeights = ( - 24 - 24) - end - object mnuPopup: TPopupMenu - Images = frmKeymanDeveloper.lstImages - Left = 228 - Top = 73 - object mnuPopupFont: TMenuItem - Action = modActionsDebugger.actDebugViewFont - end - object mnuPopupResetToQuotedFont: TMenuItem - Action = modActionsDebugger.actDebugViewDefaultFont - end - object N1: TMenuItem - Caption = '-' - end - object mnuCut: TMenuItem - Action = modActionsMain.actEditCut - end - object mnuCopy: TMenuItem - Action = modActionsMain.actEditCopy - end - object mnuPaste: TMenuItem - Action = modActionsMain.actEditPaste - end - end - object appEvents: TApplicationEvents - Left = 368 - Top = 72 - end -end +inherited frmLdmlKeyboardDebug: TfrmLdmlKeyboardDebug + Left = 85 + Top = 229 + BorderIcons = [biSystemMenu] + BorderStyle = bsSizeToolWin + Caption = 'Debug' + ClientHeight = 132 + ClientWidth = 755 + Font.Name = 'MS Sans Serif' + FormStyle = fsStayOnTop + KeyPreview = True + Position = poDefault + OnClose = FormClose + OnDestroy = FormDestroy + OnResize = FormResize + ExplicitWidth = 771 + ExplicitHeight = 171 + PixelsPerInch = 96 + TextHeight = 13 + object memo: TKeymanDeveloperDebuggerMemo + Left = 0 + Top = 0 + Width = 755 + Height = 73 + Cursor = crIBeam + Align = alClient + BorderStyle = bsNone + Font.Charset = ANSI_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Arial' + Font.Style = [] + ParentFont = False + PlainText = True + PopupMenu = mnuPopup + ScrollBars = ssVertical + TabOrder = 0 + OnChange = memoChange + OnClick = memoClick + OnDblClick = memoDblClick + OnEnter = memoGotFocus + OnKeyUp = memoKeyUp + OnSelectionChange = memoSelMove + OnMessage = memoMessage + IsDebugging = False + end + object sgChars: TStringGrid + Left = 0 + Top = 73 + Width = 755 + Height = 59 + Align = alBottom + ColCount = 1 + DefaultDrawing = False + FixedCols = 0 + RowCount = 2 + FixedRows = 0 + ScrollBars = ssNone + TabOrder = 1 + OnDrawCell = sgCharsDrawCell + ColWidths = ( + 64) + RowHeights = ( + 24 + 24) + end + object mnuPopup: TPopupMenu + Images = frmKeymanDeveloper.lstImages + Left = 228 + Top = 73 + object mnuPopupFont: TMenuItem + Action = modActionsDebugger.actDebugViewFont + end + object mnuPopupResetToQuotedFont: TMenuItem + Action = modActionsDebugger.actDebugViewDefaultFont + end + object N1: TMenuItem + Caption = '-' + end + object mnuCut: TMenuItem + Action = modActionsMain.actEditCut + end + object mnuCopy: TMenuItem + Action = modActionsMain.actEditCopy + end + object mnuPaste: TMenuItem + Action = modActionsMain.actEditPaste + end + end + object appEvents: TApplicationEvents + Left = 368 + Top = 72 + end +end diff --git a/developer/src/tike/child/Keyman.Developer.UI.Debug.UfrmLdmlKeyboardDebug.pas b/developer/src/tike/child/Keyman.Developer.UI.Debug.UfrmLdmlKeyboardDebug.pas index 4a7aaf6066..b4b38fe28d 100644 --- a/developer/src/tike/child/Keyman.Developer.UI.Debug.UfrmLdmlKeyboardDebug.pas +++ b/developer/src/tike/child/Keyman.Developer.UI.Debug.UfrmLdmlKeyboardDebug.pas @@ -74,6 +74,7 @@ type procedure memoClick(Sender: TObject); // I4808 procedure memoKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); procedure FormResize(Sender: TObject); + procedure memoDblClick(Sender: TObject); private FFileName: string; debugkeyboard: TDebugKeyboard; @@ -108,6 +109,7 @@ type function SetKeyEventContext: Boolean; function HandleMemoKeydown(var Message: TMessage): Boolean; procedure Run(vkey: Word); + procedure UpdateMemoSelection; protected function GetHelpTopic: string; override; @@ -206,13 +208,13 @@ end; procedure TfrmLdmlKeyboardDebug.memoGotFocus(Sender: TObject); begin - memoSelMove(memo); + UpdateMemoSelection; end; procedure TfrmLdmlKeyboardDebug.memoKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); begin - memoSelMove(memo); + UpdateMemoSelection; end; function TfrmLdmlKeyboardDebug.HandleMemoKeydown(var Message: TMessage): Boolean; @@ -391,7 +393,7 @@ procedure TfrmLdmlKeyboardDebug.Run(vkey: Word); dk: TDeadKeyInfo; begin Result.Selection := memo.Selection; - Result.Length := Length(memo.Text); + Result.Length := Length(memo.GetTextCR); for dk in FDeadkeys do begin if dk.Position >= Result.Selection.Start @@ -406,7 +408,7 @@ procedure TfrmLdmlKeyboardDebug.Run(vkey: Word); L: Integer; s: string; begin - s := memo.Text; + s := memo.GetTextCR; L := Length(s); for dk in FDeadkeys do if dk.SavedPosition < 0 then @@ -430,10 +432,6 @@ procedure TfrmLdmlKeyboardDebug.Run(vkey: Word); if GetKeyState(VK_CONTROL) >= 0 then begin - if (vk = VK_RETURN) and (GetKeyState(VK_MENU) >= 0) then - begin - memo.SelText := #13#10; - end; Exit; end; @@ -591,13 +589,13 @@ begin RealignMemoSelectionState(state); selection := memo.Selection; end; - lhs := Copy(memo.Text, 1, selection.Start); + lhs := Copy(memo.GetTextCR, 1, selection.Start); end else lhs := ''; - context := Copy(memo.Text, lhs.Length + 1, selection.Start - lhs.Length); - rhs := Copy(memo.Text, lhs.Length + context.Length + 1, MaxInt); + context := Copy(memo.GetTextCR, lhs.Length + 1, selection.Start - lhs.Length); + rhs := Copy(memo.GetTextCR, lhs.Length + context.Length + 1, MaxInt); // Reinsert the context @@ -635,7 +633,7 @@ begin SendMessage(memo.Handle, (WM_USER + 97) {EM_SETTEXTEX}, NativeUint(@ste), NativeUInt(PChar(str))); selection.Start := lhs.Length + output.Length; selection.Finish := selection.Start; - selection.Anchor := selection.Start; + selection.Caret := selection.Start; memo.Selection := selection; finally memo.Lines.EndUpdate; @@ -820,22 +818,32 @@ end; procedure TfrmLdmlKeyboardDebug.memoChange(Sender: TObject); begin UpdateDeadkeys; - memoSelMove(memo); + UpdateMemoSelection; end; procedure TfrmLdmlKeyboardDebug.memoClick(Sender: TObject); begin - memoSelMove(memo); + UpdateMemoSelection; end; +procedure TfrmLdmlKeyboardDebug.memoDblClick(Sender: TObject); +begin + UpdateMemoSelection; +end; + procedure TfrmLdmlKeyboardDebug.memoSelMove(Sender: TObject); +begin + UpdateMemoSelection; +end; + +procedure TfrmLdmlKeyboardDebug.UpdateMemoSelection; begin if memo.Focused then begin frmKeymanDeveloper.barStatus.Panels[0].Text := 'Debugger Active'; end; - if not memo.ReadOnly and not memo.SelectionChanging then + if not memo.ReadOnly then begin UpdateCharacterGrid; // I4808 end; @@ -844,20 +852,22 @@ end; procedure TfrmLdmlKeyboardDebug.UpdateCharacterGrid; // I4808 var start, len: Integer; + text: string; begin if csDestroying in ComponentState then Exit; start := memo.SelStart; len := memo.SelLength; - if start + len > Length(memo.Text) then + text := memo.GetTextCR; + if start + len > text.Length then begin // RichEdit has a virtual final character, which is selected when // pressing Ctrl+A, etc. - len := Length(memo.Text) - start; + len := text.Length - start; end; - TCharacterGridRenderer.Fill(sgChars, memo.Text, FDeadkeys, start, len, - memo.Selection.Anchor, True); + TCharacterGridRenderer.Fill(sgChars, text, FDeadkeys, start, len, + memo.Selection.Caret, True); TCharacterGridRenderer.Size(sgChars, memo.Font); end; diff --git a/developer/src/tike/child/UfrmDebug.dfm b/developer/src/tike/child/UfrmDebug.dfm index 7642552667..8e7eeafeba 100644 --- a/developer/src/tike/child/UfrmDebug.dfm +++ b/developer/src/tike/child/UfrmDebug.dfm @@ -1,93 +1,95 @@ -inherited frmDebug: TfrmDebug - Left = 85 - Top = 229 - BorderIcons = [biSystemMenu] - BorderStyle = bsSizeToolWin - Caption = 'Debug' - ClientHeight = 132 - ClientWidth = 755 - Font.Name = 'MS Sans Serif' - FormStyle = fsStayOnTop - KeyPreview = True - Position = poDefault - OnClose = FormClose - OnDestroy = FormDestroy - OnResize = FormResize - ExplicitWidth = 771 - ExplicitHeight = 171 - PixelsPerInch = 96 - TextHeight = 13 - object memo: TKeymanDeveloperDebuggerMemo - Left = 0 - Top = 0 - Width = 755 - Height = 73 - Cursor = crIBeam - Align = alClient - BorderStyle = bsNone - Font.Charset = ANSI_CHARSET - Font.Color = clWindowText - Font.Height = -11 - Font.Name = 'Arial' - Font.Style = [] - ParentFont = False - PlainText = True - PopupMenu = mnuPopup - ScrollBars = ssVertical - TabOrder = 0 - OnChange = memoChange - OnClick = memoClick - OnEnter = memoGotFocus - OnExit = memoLostFocus - OnKeyUp = memoKeyUp - OnMessage = memoMessage - IsDebugging = False - end - object sgChars: TStringGrid - Left = 0 - Top = 73 - Width = 755 - Height = 59 - Align = alBottom - ColCount = 1 - DefaultDrawing = False - FixedCols = 0 - RowCount = 2 - FixedRows = 0 - ScrollBars = ssNone - TabOrder = 1 - OnDrawCell = sgCharsDrawCell - ColWidths = ( - 64) - RowHeights = ( - 24 - 24) - end - object mnuPopup: TPopupMenu - Images = frmKeymanDeveloper.lstImages - Left = 228 - Top = 73 - object mnuPopupFont: TMenuItem - Action = modActionsDebugger.actDebugViewFont - end - object mnuPopupResetToQuotedFont: TMenuItem - Action = modActionsDebugger.actDebugViewDefaultFont - end - object N1: TMenuItem - Caption = '-' - end - object mnuCut: TMenuItem - Action = modActionsMain.actEditCut - end - object mnuCopy: TMenuItem - Action = modActionsMain.actEditCopy - end - object mnuPaste: TMenuItem - Action = modActionsMain.actEditPaste - end - end - object appEvents: TApplicationEvents - Left = 368 - Top = 72 - end -end +inherited frmDebug: TfrmDebug + Left = 85 + Top = 229 + BorderIcons = [biSystemMenu] + BorderStyle = bsSizeToolWin + Caption = 'Debug' + ClientHeight = 132 + ClientWidth = 755 + Font.Name = 'MS Sans Serif' + FormStyle = fsStayOnTop + KeyPreview = True + Position = poDefault + OnClose = FormClose + OnDestroy = FormDestroy + OnResize = FormResize + ExplicitWidth = 771 + ExplicitHeight = 171 + PixelsPerInch = 96 + TextHeight = 13 + object memo: TKeymanDeveloperDebuggerMemo + Left = 0 + Top = 0 + Width = 755 + Height = 73 + Cursor = crIBeam + Align = alClient + BorderStyle = bsNone + Font.Charset = ANSI_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Arial' + Font.Style = [] + ParentFont = False + PlainText = True + PopupMenu = mnuPopup + ScrollBars = ssVertical + TabOrder = 0 + OnChange = memoChange + OnClick = memoClick + OnDblClick = memoDblClick + OnEnter = memoGotFocus + OnExit = memoLostFocus + OnKeyUp = memoKeyUp + OnSelectionChange = memoSelMove + OnMessage = memoMessage + IsDebugging = False + end + object sgChars: TStringGrid + Left = 0 + Top = 73 + Width = 755 + Height = 59 + Align = alBottom + ColCount = 1 + DefaultDrawing = False + FixedCols = 0 + RowCount = 2 + FixedRows = 0 + ScrollBars = ssNone + TabOrder = 1 + OnDrawCell = sgCharsDrawCell + ColWidths = ( + 64) + RowHeights = ( + 24 + 24) + end + object mnuPopup: TPopupMenu + Images = frmKeymanDeveloper.lstImages + Left = 228 + Top = 73 + object mnuPopupFont: TMenuItem + Action = modActionsDebugger.actDebugViewFont + end + object mnuPopupResetToQuotedFont: TMenuItem + Action = modActionsDebugger.actDebugViewDefaultFont + end + object N1: TMenuItem + Caption = '-' + end + object mnuCut: TMenuItem + Action = modActionsMain.actEditCut + end + object mnuCopy: TMenuItem + Action = modActionsMain.actEditCopy + end + object mnuPaste: TMenuItem + Action = modActionsMain.actEditPaste + end + end + object appEvents: TApplicationEvents + Left = 368 + Top = 72 + end +end diff --git a/developer/src/tike/child/UfrmDebug.pas b/developer/src/tike/child/UfrmDebug.pas index 6137cab712..354592e72f 100644 --- a/developer/src/tike/child/UfrmDebug.pas +++ b/developer/src/tike/child/UfrmDebug.pas @@ -86,6 +86,7 @@ type procedure memoClick(Sender: TObject); // I4808 procedure memoKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); procedure FormResize(Sender: TObject); + procedure memoDblClick(Sender: TObject); private FDebugVisible: Boolean; @@ -118,6 +119,7 @@ type function GetStatusText: string; procedure SetStatusText(Value: string); procedure UpdateDebugStatusForm; // I4809 + procedure UpdateMemoSelection; private FDebugCore: TDebugCore; @@ -147,6 +149,7 @@ type procedure SetCurrentEvent(Value: Integer); procedure FinishBatch; procedure StartBatch; + procedure RecordBreadcrumb(const message: string); protected function GetHelpTopic: string; override; @@ -306,13 +309,13 @@ begin if UIStatus = duiReadyForInput then UIStatus := duiFocusedForInput; - memoSelMove(memo); + UpdateMemoSelection; end; procedure TfrmDebug.memoKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); begin - memoSelMove(memo); + UpdateMemoSelection; end; procedure TfrmDebug.memoLostFocus(Sender: TObject); @@ -463,9 +466,8 @@ begin modifier := modifier and not KM_CORE_MODIFIER_LCTRL; end; - TKeymanSentryClient.Instance.Breadcrumb('default', - Format('ProcessKeyEvent: vk=%x mod=%x scan=%x beforeText="%s" beforeSelection=%s',[vkey, modifier, scan, Copy(memo.GetTextCR, 1, 256), memo.Selection.ToString]), - 'debugger'); + RecordBreadcrumb( + Format('ProcessKeyEvent: vk=%x mod=%x scan=%x beforeText="%s" beforeSelection=%s savedSelection=%s',[vkey, modifier, scan, Copy(memo.GetTextCR, 1, 256), memo.Selection.ToString, FSavedSelection.ToString])); if not SetKeyEventContext then Exit(False); @@ -601,12 +603,17 @@ end; { Stores } +procedure TfrmDebug.RecordBreadcrumb(const message: string); +begin + // TODO(v20): eliminate breadcrumbs once underlying bug #11706 is confirmed fixed + TKeymanSentryClient.Instance.Breadcrumb('default', message, 'debugger'); + // OutputDebugString(PChar(message)); +end; + procedure TfrmDebug.ExecuteEvent(n: Integer); begin - TKeymanSentryClient.Instance.Breadcrumb('default', - Format('ExecuteEvent(%d/%d): [%s]; b:%s; s:%s', - [n, FEvents.Count, FEvents[n].ToString, memo.Selection.ToString, FSavedSelection.ToString]), - 'debugger'); + RecordBreadcrumb(Format('ExecuteEvent(%d/%d): [%s] beforeSelection=%s savedSelection=%s', + [n, FEvents.Count, FEvents[n].ToString, memo.Selection.ToString, FSavedSelection.ToString])); memo.ReadOnly := False; memo.Selection := FSavedSelection; @@ -1323,22 +1330,32 @@ end; procedure TfrmDebug.memoChange(Sender: TObject); begin UpdateDeadkeys; - memoSelMove(memo); + UpdateMemoSelection; end; procedure TfrmDebug.memoClick(Sender: TObject); begin - memoSelMove(memo); + UpdateMemoSelection; +end; + +procedure TfrmDebug.memoDblClick(Sender: TObject); +begin + UpdateMemoSelection; end; procedure TfrmDebug.memoSelMove(Sender: TObject); +begin + UpdateMemoSelection; +end; + +procedure TfrmDebug.UpdateMemoSelection; begin if memo.Focused then begin frmKeymanDeveloper.barStatus.Panels[0].Text := 'Debugger Active'; end; - if not memo.ReadOnly and not memo.SelectionChanging then + if not memo.ReadOnly then begin FSavedSelection := memo.Selection; UpdateCharacterGrid; // I4808 @@ -1363,7 +1380,7 @@ begin len := t.Length - start; end; - TCharacterGridRenderer.Fill(sgChars, t, FDeadkeys, start, len, memo.Selection.Anchor); + TCharacterGridRenderer.Fill(sgChars, t, FDeadkeys, start, len, memo.Selection.Caret); TCharacterGridRenderer.Size(sgChars, memo.Font); end; diff --git a/developer/src/tike/debug/Keyman.UI.Debug.CharacterGridRenderer.pas b/developer/src/tike/debug/Keyman.UI.Debug.CharacterGridRenderer.pas index c38bf8f358..482a75db6c 100644 --- a/developer/src/tike/debug/Keyman.UI.Debug.CharacterGridRenderer.pas +++ b/developer/src/tike/debug/Keyman.UI.Debug.CharacterGridRenderer.pas @@ -22,7 +22,7 @@ type class procedure Fill(grid: TStringGrid; const text: string; deadkeys: TDebugDeadkeyInfoList; - SelStart, SelLength, SelAnchor: Integer; + SelStart, SelLength, Caret: Integer; DeadkeysAreCalledMarkers: Boolean = False); static; class procedure Render(grid: TStringGrid; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState; CharFont: TFont); static; @@ -37,7 +37,7 @@ uses class procedure TCharacterGridRenderer.Fill(grid: TStringGrid; const text: string; deadkeys: TDebugDeadkeyInfoList; - SelStart, SelLength, SelAnchor: Integer; + SelStart, SelLength, Caret: Integer; DeadkeysAreCalledMarkers: Boolean); type TCellType = (ctChar, ctDeadkey); @@ -171,10 +171,10 @@ begin X := 0; FillGrid(Before, 0, X); - if SelAnchor = SelStart then + if Caret = SelStart then FillGridCursor(X); FillGrid(Selection, CELL_SELECTED, X); - if SelAnchor <> SelStart then + if Caret <> SelStart then FillGridCursor(X); FillGrid(After, 0, X); end; diff --git a/developer/src/tike/dialogs/Keyman.Developer.UI.UfrmServerOptions.dfm b/developer/src/tike/dialogs/Keyman.Developer.UI.UfrmServerOptions.dfm index 07f0979d87..ca4ba95a6d 100644 --- a/developer/src/tike/dialogs/Keyman.Developer.UI.UfrmServerOptions.dfm +++ b/developer/src/tike/dialogs/Keyman.Developer.UI.UfrmServerOptions.dfm @@ -1,165 +1,181 @@ -object frmServerOptions: TfrmServerOptions - Left = 0 - Top = 0 - BorderIcons = [biSystemMenu] - BorderStyle = bsDialog - Caption = 'Keyman Developer Server Options' - ClientHeight = 378 - ClientWidth = 433 - Color = clBtnFace - Font.Charset = DEFAULT_CHARSET - Font.Color = clWindowText - Font.Height = -11 - Font.Name = 'Tahoma' - Font.Style = [] - OldCreateOrder = False - Position = poScreenCenter - OnCreate = FormCreate - PixelsPerInch = 96 - TextHeight = 13 - object Label1: TLabel - Left = 8 - Top = 96 - Width = 403 - Height = 39 - AutoSize = False - Caption = - 'ngrok is a service that allows you to temporarily make your loca' + - 'l Server instance public accessible, so that you can view it fro' + - 'm any device or share it with other users for testing your keybo' + - 'ards, models and packages.' - WordWrap = True - end - object lblDefaultPort: TLabel - Left = 8 - Top = 19 - Width = 58 - Height = 13 - Caption = '&Default port' - FocusControl = editDefaultPort - end - object cmdOK: TButton - Left = 272 - Top = 346 - Width = 73 - Height = 25 - Caption = 'OK' - Default = True - ModalResult = 1 - TabOrder = 5 - OnClick = cmdOKClick - end - object cmdCancel: TButton - Left = 352 - Top = 346 - Width = 73 - Height = 25 - Cancel = True - Caption = 'Cancel' - ModalResult = 2 - TabOrder = 6 - end - object gbSetup: TGroupBox - Left = 8 - Top = 144 - Width = 417 - Height = 129 - Caption = 'Setup' - TabOrder = 3 - object lblAuthToken: TLabel - Left = 16 - Top = 93 - Width = 100 - Height = 13 - Caption = '&Authentication token' - FocusControl = editAuthToken - end - object lblVersion: TLabel - Left = 183 - Top = 29 - Width = 43 - Height = 13 - Caption = '(version)' - end - object lblGetToken: TLabel - Left = 360 - Top = 93 - Width = 47 - Height = 13 - Cursor = crHandPoint - Caption = 'Get token' - Font.Charset = DEFAULT_CHARSET - Font.Color = clBlue - Font.Height = -11 - Font.Name = 'Tahoma' - Font.Style = [fsUnderline] - ParentFont = False - OnClick = lblGetTokenClick - end - object editAuthToken: TEdit - Left = 122 - Top = 90 - Width = 231 - Height = 21 - TabOrder = 2 - end - object cmdDownload: TButton - Left = 16 - Top = 24 - Width = 161 - Height = 25 - Caption = 'Download or &update ngrok' - TabOrder = 0 - OnClick = cmdDownloadClick - end - object cmdCreateAccount: TButton - Left = 16 - Top = 55 - Width = 161 - Height = 25 - Caption = '&Create free ngrok account...' - TabOrder = 1 - OnClick = cmdCreateAccountClick - end - end - object gbAdvanced: TGroupBox - Left = 8 - Top = 279 - Width = 417 - Height = 61 - Caption = 'Advanced Options' - TabOrder = 4 - object chkServerShowConsoleWindow: TCheckBox - Left = 16 - Top = 27 - Width = 257 - Height = 17 - Caption = '&Show Server console window on start' - TabOrder = 0 - end - end - object editDefaultPort: TEdit - Left = 84 - Top = 16 - Width = 61 - Height = 21 - NumbersOnly = True - TabOrder = 0 - end - object chkUseNgrok: TCheckBox - Left = 8 - Top = 73 - Width = 313 - Height = 17 - Caption = 'Use &ngrok to provide public url for Server' - TabOrder = 2 - end - object chkLeaveServerRunning: TCheckBox - Left = 8 - Top = 43 - Width = 257 - Height = 17 - Caption = '&Leave Server running after closing IDE' - TabOrder = 1 - end -end +object frmServerOptions: TfrmServerOptions + Left = 0 + Top = 0 + BorderIcons = [biSystemMenu] + BorderStyle = bsDialog + Caption = 'Keyman Developer Server Options' + ClientHeight = 407 + ClientWidth = 433 + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + OldCreateOrder = False + Position = poScreenCenter + OnCreate = FormCreate + PixelsPerInch = 96 + TextHeight = 13 + object Label1: TLabel + Left = 8 + Top = 96 + Width = 403 + Height = 39 + AutoSize = False + Caption = + 'ngrok is a service that allows you to temporarily make your loca' + + 'l Server instance public accessible, so that you can view it fro' + + 'm any device or share it with other users for testing your keybo' + + 'ards, models and packages.' + WordWrap = True + end + object lblDefaultPort: TLabel + Left = 8 + Top = 19 + Width = 58 + Height = 13 + Caption = '&Default port' + FocusControl = editDefaultPort + end + object cmdOK: TButton + Left = 272 + Top = 374 + Width = 73 + Height = 25 + Caption = 'OK' + Default = True + ModalResult = 1 + TabOrder = 5 + OnClick = cmdOKClick + end + object cmdCancel: TButton + Left = 352 + Top = 374 + Width = 73 + Height = 25 + Cancel = True + Caption = 'Cancel' + ModalResult = 2 + TabOrder = 6 + end + object gbSetup: TGroupBox + Left = 8 + Top = 144 + Width = 417 + Height = 157 + Caption = 'Setup' + TabOrder = 3 + object lblAuthToken: TLabel + Left = 16 + Top = 117 + Width = 100 + Height = 13 + Caption = '&Authentication token' + FocusControl = editAuthToken + end + object lblNgrokInstallState: TLabel + Left = 183 + Top = 29 + Width = 87 + Height = 13 + Caption = '(installation state)' + end + object lblGetToken: TLabel + Left = 360 + Top = 117 + Width = 47 + Height = 13 + Cursor = crHandPoint + Caption = 'Get token' + Font.Charset = DEFAULT_CHARSET + Font.Color = clBlue + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [fsUnderline] + ParentFont = False + OnClick = lblGetTokenClick + end + object lblVCRedistInstallState: TLabel + Left = 183 + Top = 57 + Width = 87 + Height = 13 + Caption = '(installation state)' + end + object editAuthToken: TEdit + Left = 122 + Top = 114 + Width = 231 + Height = 21 + TabOrder = 3 + end + object cmdDownload: TButton + Left = 16 + Top = 24 + Width = 161 + Height = 25 + Caption = 'Download or &update ngrok' + TabOrder = 0 + OnClick = cmdDownloadClick + end + object cmdCreateAccount: TButton + Left = 16 + Top = 79 + Width = 161 + Height = 25 + Caption = '&Create free ngrok account...' + TabOrder = 2 + OnClick = cmdCreateAccountClick + end + object cmdInstallVCRedist: TButton + Left = 16 + Top = 52 + Width = 161 + Height = 25 + Caption = '&Install required redistributable' + TabOrder = 1 + OnClick = cmdInstallVCRedistClick + end + end + object gbAdvanced: TGroupBox + Left = 8 + Top = 307 + Width = 417 + Height = 61 + Caption = 'Advanced Options' + TabOrder = 4 + object chkServerShowConsoleWindow: TCheckBox + Left = 16 + Top = 27 + Width = 257 + Height = 17 + Caption = '&Show Server console window on start' + TabOrder = 0 + end + end + object editDefaultPort: TEdit + Left = 84 + Top = 16 + Width = 61 + Height = 21 + NumbersOnly = True + TabOrder = 0 + end + object chkUseNgrok: TCheckBox + Left = 8 + Top = 73 + Width = 313 + Height = 17 + Caption = 'Use &ngrok to provide public url for Server' + TabOrder = 2 + end + object chkLeaveServerRunning: TCheckBox + Left = 8 + Top = 43 + Width = 257 + Height = 17 + Caption = '&Leave Server running after closing IDE' + TabOrder = 1 + end +end diff --git a/developer/src/tike/dialogs/Keyman.Developer.UI.UfrmServerOptions.pas b/developer/src/tike/dialogs/Keyman.Developer.UI.UfrmServerOptions.pas index 8a5170e0d1..4a2225e564 100644 --- a/developer/src/tike/dialogs/Keyman.Developer.UI.UfrmServerOptions.pas +++ b/developer/src/tike/dialogs/Keyman.Developer.UI.UfrmServerOptions.pas @@ -4,8 +4,10 @@ interface uses System.Classes, + System.Net.HttpClient, System.SysUtils, System.Variants, + System.Win.Registry, Vcl.Controls, Vcl.Dialogs, Vcl.Forms, @@ -28,35 +30,40 @@ type gbAdvanced: TGroupBox; chkServerShowConsoleWindow: TCheckBox; cmdCreateAccount: TButton; - lblVersion: TLabel; + lblNgrokInstallState: TLabel; Label1: TLabel; lblGetToken: TLabel; lblDefaultPort: TLabel; editDefaultPort: TEdit; chkUseNgrok: TCheckBox; chkLeaveServerRunning: TCheckBox; + cmdInstallVCRedist: TButton; + lblVCRedistInstallState: TLabel; procedure FormCreate(Sender: TObject); procedure cmdOKClick(Sender: TObject); procedure cmdDownloadClick(Sender: TObject); procedure cmdCreateAccountClick(Sender: TObject); procedure lblGetTokenClick(Sender: TObject); + procedure cmdInstallVCRedistClick(Sender: TObject); private + FInstallStateChanged: Boolean; FDownloadProgress: TfrmDownloadProgress; + FDownloadObject: string; procedure UpdateVersionLabel; function NgrokPath: string; procedure DownloadNgrok(AOwner: TfrmDownloadProgress; var Result: Boolean); procedure HttpReceiveData(const Sender: TObject; AContentLength, AReadCount: Int64; var Abort: Boolean); - { Private declarations } + procedure DownloadVCRedist(AOwner: TfrmDownloadProgress; + var Result: Boolean); + function DoDownload(AOwner: TfrmDownloadProgress; const objectName, url: string): IHTTPResponse; + function RedistInstallerPath: string; public - { Public declarations } end; implementation uses - System.Net.HttpClient, - System.Zip, Winapi.ShlObj, KeymanDeveloperOptions, @@ -73,8 +80,19 @@ const // to be stable, as they are used in other apps also. SUrlNgrokAuthToken = 'https://dashboard.ngrok.com/get-started/your-authtoken'; SUrlNgrokSignup = 'https://dashboard.ngrok.com/signup'; - SUrlNgrokDownload = 'https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-windows-386.zip'; - SNGrokExe = 'ngrok.exe'; + + // Note, we manually update the version of ngrok as required in subsequent + // releases, rather than trying to handle a potentially moving target. This + // should match developer/src/server/package.json version for ngrok. + SNgrokVersion = 'v1.7.0'; + SUrlNgrokDownload = 'https://github.com/ngrok/ngrok-javascript/releases/download/'+SNgrokVersion+'/ngrok.win32-ia32-msvc.node'; + SNgrokNodeModuleFilename = 'ngrok-win32-ia32-msvc.node'; + + // VC++ Redistributable - presence, filename, and permanent URL + SUrlVCRedistDownload = 'https://aka.ms/vc14/vc_redist.x86.exe'; + SVCRedistExeFilename = 'VC_redist.x86.exe'; + SRegKey_VCRedist = 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x86'; + SRegValue_VCRedist_Version = 'Version'; procedure TfrmServerOptions.cmdCreateAccountClick(Sender: TObject); begin @@ -86,104 +104,153 @@ begin Abort := False; if Assigned(FDownloadProgress) then begin - FDownloadProgress.HTTPStatus(nil, 'Downloading ngrok', AReadCount, AContentLength); + FDownloadProgress.HTTPStatus(nil, 'Downloading '+FDownloadObject, AReadCount, AContentLength); FDownloadProgress.HTTPCheckCancel(nil, Abort); end; end; -procedure TfrmServerOptions.DownloadNgrok(AOwner: TfrmDownloadProgress; var Result: Boolean); +function TfrmServerOptions.DoDownload(AOwner: TfrmDownloadProgress; const objectName, url: string): IHTTPResponse; var http: THttpClient; res: IHTTPResponse; - fs: TFileStream; - zip: TZipFile; - ms: TStream; - zh: TZipHeader; begin - Result := False; + Result := nil; + FDownloadObject := objectName; FDownloadProgress := AOwner; http := THttpClient.Create; try http.OnReceiveData := HttpReceiveData; try - res := http.Get(SUrlNgrokDownload); + res := http.Get(url); except on E:ENetHTTPClientException do begin - ShowMessage(Format('Unable to download %s: %s', [SNGrokExe, E.Message])); + ShowMessage(Format('Unable to download %s from %s: %s', [objectName, url, E.Message])); Exit; end; end; if res.StatusCode <> 200 then begin - ShowMessage(Format('Unable to download %s: %d %s', - [SNGrokExe, res.StatusCode, res.StatusText])); + ShowMessage(Format('Unable to download %s from %s: %d %s', + [objectName, url, res.StatusCode, res.StatusText])); Exit; end; - // Download is a zip file with a single executable, so let's extract it - ms := nil; - try - zip := TZipFile.Create; - try - try - zip.Open(res.ContentStream, zmRead); - zip.Read(SNGrokExe, ms, zh); - ms.Position := 0; - finally - zip.Free; - end; - except - on E:Exception do - begin - ShowMessage(Format('Unable to extract %s from downloaded archive: %s', - [SNGrokExe, E.Message])); - Exit; - end; - end; - try - fs := TFileStream.Create(NgrokPath, fmCreate); - except - on E:Exception do - begin - ShowMessage(Format('Unable to save %s: %s', [SNGrokExe, E.Message])); - Exit; - end; - end; - try - fs.CopyFrom(ms, 0); - finally - fs.Free; - end; - finally - ms.Free; - end; + Result := res; finally http.Free; FDownloadProgress := nil; + FDownloadObject := ''; end; end; +procedure TfrmServerOptions.DownloadVCRedist(AOwner: TfrmDownloadProgress; var Result: Boolean); +var + res: IHTTPResponse; + fs: TFileStream; +begin + Result := False; + + res := DoDownload(AOwner, 'Microsoft Visual C++ Redistributable', SUrlVCRedistDownload); + if not Assigned(res) then + Exit; + + // Save the redistributable installer + + try + fs := TFileStream.Create(RedistInstallerPath, fmCreate); + try + fs.CopyFrom(res.ContentStream, 0); + finally + fs.Free; + end; + except + on E:Exception do + begin + ShowMessage(Format('Unable to save %s: %s', [RedistInstallerPath, E.Message])); + Exit; + end; + end; + + // Execute the installer - user will need to follow prompts to complete installation + + Result := TUtilExecute.WaitForProcess('"'+RedistInstallerPath+'"', ExtractFileDir(RedistInstallerPath), SW_SHOWNORMAL); + FInstallStateChanged := True; +end; + +procedure TfrmServerOptions.DownloadNgrok(AOwner: TfrmDownloadProgress; var Result: Boolean); +var + res: IHTTPResponse; + fs: TFileStream; +begin + Result := False; + + res := DoDownload(AOwner, 'ngrok', SUrlNgrokDownload); + if not Assigned(res) then + Exit; + + // Download is a single file, needs to be saved to AppData\Keyman\Keyman Developer\Server\bin\@ngrok\filename + + try + fs := TFileStream.Create(NgrokPath, fmCreate); + except + on E:Exception do + begin + ShowMessage(Format('Unable to save %s: %s', [SNgrokNodeModuleFilename, E.Message])); + Exit; + end; + end; + try + fs.CopyFrom(res.ContentStream, 0); + finally + fs.Free; + end; + + Result := True; + FInstallStateChanged := True; +end; + procedure TfrmServerOptions.cmdDownloadClick(Sender: TObject); var DownloadProgress: TfrmDownloadProgress; begin - TServerDebugAPI.StopServer; + if FKeymanDeveloperOptions.ServerUseNgrok then + begin + // We need to stop the server if NGrok has been enabled because the module + // may be locked and in use + TServerDebugAPI.StopServer; + end; + DownloadProgress := TfrmDownloadProgress.Create(Self); try - DownloadProgress := TfrmDownloadProgress.Create(Self); - try - DownloadProgress.Callback := DownloadNgrok; - DownloadProgress.ShowModal; - finally - DownloadProgress.Free; - end; + DownloadProgress.Callback := DownloadNgrok; + DownloadProgress.ShowModal; finally + DownloadProgress.Free; + UpdateVersionLabel; + if FKeymanDeveloperOptions.ServerUseNgrok then + begin + // Restart the server after ngrok module is installed (although ngrok may + // not run if the dependency is not also installed) + TServerDebugAPI.StartServer; + end; + end; +end; + +procedure TfrmServerOptions.cmdInstallVCRedistClick(Sender: TObject); +var + DownloadProgress: TfrmDownloadProgress; +begin + DownloadProgress := TfrmDownloadProgress.Create(Self); + try + DownloadProgress.Callback := DownloadVCRedist; + DownloadProgress.ShowModal; + finally + DownloadProgress.Free; UpdateVersionLabel; - TServerDebugAPI.StartServer; end; end; @@ -205,7 +272,8 @@ begin (FKeymanDeveloperOptions.ServerKeepAlive <> KeepAlive) or (FKeymanDeveloperOptions.ServerUseNgrok <> UseNgrok) or (FKeymanDeveloperOptions.ServerNgrokToken <> ngrokToken) or - (FKeymanDeveloperOptions.ServerServerShowConsoleWindow <> ngrokKeepVisible); + (FKeymanDeveloperOptions.ServerServerShowConsoleWindow <> ngrokKeepVisible) or + FInstallStateChanged; if Changed then begin @@ -226,7 +294,8 @@ begin chkUseNgrok.Checked := FKeymanDeveloperOptions.ServerUseNgrok; chkLeaveServerRunning.Checked := FKeymanDeveloperOptions.ServerKeepAlive; - lblVersion.Caption := ''; + lblNgrokInstallState.Caption := ''; + lblVCRedistInstallState.Caption := ''; editAuthToken.Text := FKeymanDeveloperOptions.ServerNgrokToken; chkServerShowConsoleWindow.Checked := FKeymanDeveloperOptions.ServerServerShowConsoleWindow; @@ -239,29 +308,36 @@ begin TUtilExecute.URL(SUrlNgrokAuthToken); end; + function TfrmServerOptions.NgrokPath: string; begin - Result := GetFolderPath(CSIDL_APPDATA) + SFolderKeymanDeveloper + '\Server\bin\'; + Result := TServerDebugAPI.ServerBinPath + '\@ngrok'; ForceDirectories(Result); - Result := Result + SNgrokExe; + Result := Result + '\' + SNgrokNodeModuleFilename; +end; + +function TfrmServerOptions.RedistInstallerPath: string; +begin + Result := TServerDebugAPI.ServerBinPath + '\' + SVCRedistExeFilename; end; procedure TfrmServerOptions.UpdateVersionLabel; var - logtext: string; - ec: Integer; + reg: TRegistry; begin - if not FileExists(NgrokPath) then - lblVersion.Caption := 'Ngrok not installed' - else if TUtilExecute.Console(Format('"%s" --version', [NgrokPath]), ExtractFilePath(ParamStr(0)), - logtext, ec) then - begin - if ec = 0 - then lblVersion.Caption := logtext.Trim - else lblVersion.Caption := Format('Error %d running ngrok: %s', [ec, logtext.Trim]) - end - else - lblVersion.Caption := Format('Unable to run ngrok: %s', [SysErrorMessage(GetLastError)]); + if FileExists(NgrokPath) + then lblNgrokInstallState.Caption := 'Installed' + else lblNgrokInstallState.Caption := 'Not installed'; + + lblVCRedistInstallState.Caption := 'Not installed'; + reg := TRegistry.Create; + try + reg.RootKey := HKEY_LOCAL_MACHINE; + if reg.OpenKeyReadOnly(SRegKey_VCRedist) and reg.ValueExists(SRegValue_VCRedist_Version) then + lblVCRedistInstallState.Caption := reg.ReadString(SRegValue_VCRedist_Version); + finally + reg.Free; + end; end; end. diff --git a/developer/src/tike/http/Keyman.Developer.System.ServerAPI.pas b/developer/src/tike/http/Keyman.Developer.System.ServerAPI.pas index c666b49642..565b4ed068 100644 --- a/developer/src/tike/http/Keyman.Developer.System.ServerAPI.pas +++ b/developer/src/tike/http/Keyman.Developer.System.ServerAPI.pas @@ -25,6 +25,8 @@ type class procedure StartServer; static; class procedure StopServer; static; class procedure GetServerURLs(v: TStrings); static; + class function ServerBinPath: string; static; + class procedure CleanupOldNgrok; static; // API endpoints class function IsKeyboardRegistered(const Filename: string): Boolean; static; @@ -65,6 +67,7 @@ uses Keyman.Developer.System.KeymanDeveloperPaths, Keyman.System.KeyboardUtils, Keyman.System.LexicalModelUtils, + RegistryKeys, utilexecute, utilsystem, Winapi.ShlObj, @@ -83,6 +86,12 @@ begin then sw := SW_SHOWNORMAL else sw := SW_HIDE; + // We have an explicit NODE_PATH env var so that Node can + // find our downloaded version of ngrok in %AppData%\Server\bin\@ngrok + // without us risking messing up the user's own node environment (if + // they have one!) + SetEnvironmentVariable('NODE_PATH', PChar(ServerBinPath)); + if not TUtilExecute.Execute(Format('"%s" .', [TKeymanDeveloperPaths.NodePath]), TKeymanDeveloperPaths.ServerPath, sw) then begin @@ -92,6 +101,12 @@ begin LastGetStatusTime := 0; end; +class function TServerDebugAPI.ServerBinPath: string; +begin + Result := TKeymanDeveloperPaths.ServerDataPath + 'bin'; + ForceDirectories(Result); +end; + function QueryFullProcessImageName( hProcess: THandle; dwFlags: DWORD; @@ -189,6 +204,17 @@ begin Result := True; end; +class procedure TServerDebugAPI.CleanupOldNgrok; +begin + // #15625: In February 2026, we moved from npm ngrok module to @ngrok/ngrok. + // This npm module uses a different binary version of ngrok, so we should + // cleanup the old ngrok.exe if we find it. + if FileExists(TServerDebugAPI.ServerBinPath + '\ngrok.exe') then + begin + System.SysUtils.DeleteFile(TServerDebugAPI.ServerBinPath + '\ngrok.exe'); + end; +end; + class procedure TServerDebugAPI.GetServerURLs(v: TStrings); const IPv4Loopback = '127.0.0.1'; {do not localize} diff --git a/developer/src/tike/main/Keyman.Developer.System.Main.pas b/developer/src/tike/main/Keyman.Developer.System.Main.pas index 9f6536a14a..d60897e2f2 100644 --- a/developer/src/tike/main/Keyman.Developer.System.Main.pas +++ b/developer/src/tike/main/Keyman.Developer.System.Main.pas @@ -13,6 +13,7 @@ uses uCEFTypes, KeymanDeveloperOptions, + Keyman.Developer.System.ServerAPI, Keyman.Developer.System.TikeCommandLine, Keyman.System.CEFManager, Keyman.System.KeymanSentryClient, @@ -68,6 +69,7 @@ begin begin InitThemeLibrary; SetThemeAppProperties(STAP_ALLOW_NONCLIENT or STAP_ALLOW_CONTROLS or STAP_ALLOW_WEBCONTENT); + TServerDebugAPI.CleanupOldNgrok; Application.CreateForm(TmodWebHttpServer, modWebHttpServer); try Application.CreateForm(TfrmKeymanDeveloper, frmKeymanDeveloper); diff --git a/developer/src/tike/main/Keyman.Developer.UI.ImportWindowsKeyboardDialogManager.pas b/developer/src/tike/main/Keyman.Developer.UI.ImportWindowsKeyboardDialogManager.pas index d0e526c230..7713cf9b82 100644 --- a/developer/src/tike/main/Keyman.Developer.UI.ImportWindowsKeyboardDialogManager.pas +++ b/developer/src/tike/main/Keyman.Developer.UI.ImportWindowsKeyboardDialogManager.pas @@ -70,6 +70,7 @@ begin iwk.Version := f.Version; iwk.BCP47Tags := f.BCP47Tags; iwk.Author := f.Author; + iwk.Description := f.Description; iwk.Targets := f.Targets; if not iwk.Execute then diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.ProjectUI.pas b/developer/src/tike/project/Keyman.Developer.UI.Project.ProjectUI.pas index 4bd05c588c..9f09db3d8f 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.ProjectUI.pas +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.ProjectUI.pas @@ -29,12 +29,16 @@ function LoadGlobalProjectUI(pt: TProjectType; AFilename: string): TProjectUI; procedure FreeGlobalProjectUI; function IsGlobalProjectUIReady: Boolean; function CreateTempGlobalProjectUI(pt: TProjectType): TProjectUI; +function VerifyNewProjectPathWithUser(const BasePath, ProjectID: string): Boolean; implementation uses System.Hash, System.SysUtils, + System.UITypes, + Vcl.Controls, + Vcl.Dialogs, Winapi.Windows, Keyman.Developer.System.KeymanDeveloperPaths, @@ -144,7 +148,7 @@ end; procedure UnlockProject; begin - if not hLockFile = INVALID_HANDLE_VALUE then + if hLockFile = INVALID_HANDLE_VALUE then begin Exit; end; @@ -153,4 +157,24 @@ begin hLockFile := INVALID_HANDLE_VALUE; end; +function VerifyNewProjectPathWithUser(const BasePath, ProjectID: string): Boolean; +var + ProjectFolder: string; +begin + if not DirectoryExists(BasePath) then + begin + if MessageDlg('The target folder '+BasePath+' does not exist. Create it now?', mtConfirmation, mbOkCancel, 0) = mrCancel then + Exit(False); + end; + + ProjectFolder := IncludeTrailingPathDelimiter(BasePath) + ProjectID; + if DirectoryExists(ProjectFolder) then + begin + MessageDlg('The project folder '+ProjectFolder+' already exists. Please choose a different folder or delete it in File Explorer.', mtError, [mbOk], 0); + Exit(False); + end; + + Result := True; +end; + end. diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmCloneGitHubProjectParameters.pas b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmCloneGitHubProjectParameters.pas index ad042fc872..8e5979da10 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmCloneGitHubProjectParameters.pas +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmCloneGitHubProjectParameters.pas @@ -97,6 +97,7 @@ uses Keyman.Developer.System.HelpTopics, Keyman.Developer.System.Project.Project, Keyman.Developer.System.Project.ProjectFile, + Keyman.Developer.UI.Project.ProjectUI, Keyman.System.KeyboardUtils, Keyman.System.LexicalModelUtils, UfrmMain; @@ -331,29 +332,12 @@ begin end; function TfrmCloneGitHubProjectParameters.Validate: Boolean; -var - ProjectFolder: string; begin if IsSourceProjectALexicalModelProject then Result := TLexicalModelUtils.IsValidLexicalModelID(Trim(editProjectID.Text), True) else Result := TKeyboardUtils.IsValidKeyboardID(Trim(editProjectID.Text), True); - if Result then - begin - if not DirectoryExists(editPath.Text) then - begin - if MessageDlg('The target folder '+editPath.Text+' does not exist. Create it now?', mtConfirmation, mbOkCancel, 0) = mrCancel then - Exit(False); - end; - - ProjectFolder := IncludeTrailingPathDelimiter(editPath.Text) + editProjectID.Text; - if DirectoryExists(ProjectFolder) then - begin - if MessageDlg('The project folder '+ProjectFolder+' already exists. Are you sure you want to overwrite it?', mtWarning, - mbOkCancel, 0) = mrCancel then - Exit(False); - end; - end; + Result := Result and VerifyNewProjectPathWithUser(BasePath, ProjectID); end; procedure TfrmCloneGitHubProjectParameters.UpdateProjectFilename; diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmCloneKeymanCloudProjectParameters.pas b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmCloneKeymanCloudProjectParameters.pas index 557fd24ec2..c3f09a6406 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmCloneKeymanCloudProjectParameters.pas +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmCloneKeymanCloudProjectParameters.pas @@ -94,6 +94,7 @@ uses Keyman.Developer.System.HelpTopics, Keyman.Developer.System.Project.Project, Keyman.Developer.System.Project.ProjectFile, + Keyman.Developer.UI.Project.ProjectUI, Keyman.System.KeyboardUtils, UfrmMain, Upload_Settings; @@ -283,27 +284,9 @@ begin end; function TfrmCloneKeymanCloudProjectParameters.Validate: Boolean; -var - ProjectFolder: string; begin Result := TKeyboardUtils.IsValidKeyboardID(Trim(editKeyboardID.Text), True); - - if Result then - begin - if not DirectoryExists(editPath.Text) then - begin - if MessageDlg('The target folder '+editPath.Text+' does not exist. Create it now?', mtConfirmation, mbOkCancel, 0) = mrCancel then - Exit(False); - end; - - ProjectFolder := IncludeTrailingPathDelimiter(editPath.Text) + editKeyboardID.Text; - if DirectoryExists(ProjectFolder) then - begin - if MessageDlg('The project folder '+ProjectFolder+' already exists. Are you sure you want to overwrite it?', mtWarning, - mbOkCancel, 0) = mrCancel then - Exit(False); - end; - end; + Result := Result and VerifyNewProjectPathWithUser(BasePath, KeyboardID); end; procedure TfrmCloneKeymanCloudProjectParameters.SetKeyboardID(const Value: string); diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmCloneLocalProjectParameters.pas b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmCloneLocalProjectParameters.pas index cfc24801b3..8f5a755e43 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmCloneLocalProjectParameters.pas +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmCloneLocalProjectParameters.pas @@ -89,6 +89,7 @@ uses Keyman.Developer.System.HelpTopics, Keyman.Developer.System.Project.Project, Keyman.Developer.System.Project.ProjectFile, + Keyman.Developer.UI.Project.ProjectUI, Keyman.System.KeyboardUtils, Keyman.System.LexicalModelUtils, UfrmMain; @@ -248,29 +249,12 @@ begin end; function TfrmCloneLocalProjectParameters.Validate: Boolean; -var - ProjectFolder: string; begin if IsSourceProjectALexicalModelProject then Result := TLexicalModelUtils.IsValidLexicalModelID(Trim(editProjectID.Text), True) else Result := TKeyboardUtils.IsValidKeyboardID(Trim(editProjectID.Text), True); - if Result then - begin - if not DirectoryExists(editPath.Text) then - begin - if MessageDlg('The target folder '+editPath.Text+' does not exist. Create it now?', mtConfirmation, mbOkCancel, 0) = mrCancel then - Exit(False); - end; - - ProjectFolder := IncludeTrailingPathDelimiter(editPath.Text) + editProjectID.Text; - if DirectoryExists(ProjectFolder) then - begin - if MessageDlg('The project folder '+ProjectFolder+' already exists. Are you sure you want to overwrite it?', mtWarning, - mbOkCancel, 0) = mrCancel then - Exit(False); - end; - end; + Result := Result and VerifyNewProjectPathWithUser(BasePath, ProjectID); end; procedure TfrmCloneLocalProjectParameters.UpdateProjectFilename; diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewLDMLKeyboardProjectParameters.pas b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewLDMLKeyboardProjectParameters.pas index 4ffede1c08..5d9f13c311 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewLDMLKeyboardProjectParameters.pas +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewLDMLKeyboardProjectParameters.pas @@ -127,6 +127,7 @@ uses Keyman.Developer.System.Project.ProjectFile, Keyman.Developer.System.LDMLKeyboardProjectTemplate, Keyman.Developer.System.ProjectTemplate, + Keyman.Developer.UI.Project.ProjectUI, Keyman.System.KeyboardUtils, UfrmMain; @@ -350,8 +351,6 @@ begin end; function TfrmNewLDMLKeyboardProjectParameters.Validate: Boolean; -var - ProjectFolder: string; begin if not FormValidation.Update then begin @@ -360,23 +359,7 @@ begin end; Result := TKeyboardUtils.IsValidKeyboardID(Trim(editKeyboardID.Text), True); - - if Result then - begin - if not DirectoryExists(editPath.Text) then - begin - if MessageDlg('The target folder '+editPath.Text+' does not exist. Create it now?', mtConfirmation, mbOkCancel, 0) = mrCancel then - Exit(False); - end; - - ProjectFolder := IncludeTrailingPathDelimiter(editPath.Text) + editKeyboardID.Text; - if DirectoryExists(ProjectFolder) then - begin - if MessageDlg('The project folder '+ProjectFolder+' already exists. Are you sure you want to overwrite it?', mtWarning, - mbOkCancel, 0) = mrCancel then - Exit(False); - end; - end; + Result := Result and VerifyNewProjectPathWithUser(BasePath, KeyboardID); end; { Languages Grid } diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewModelProjectParameters.pas b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewModelProjectParameters.pas index 0c238c16dc..a4424f82df 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewModelProjectParameters.pas +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewModelProjectParameters.pas @@ -157,6 +157,7 @@ uses Keyman.Developer.System.Project.ProjectFile, Keyman.Developer.System.ModelProjectTemplate, Keyman.Developer.System.ProjectTemplate, + Keyman.Developer.UI.Project.ProjectUI, Keyman.Developer.UI.UfrmSelectBCP47Language, UfrmMain; @@ -472,8 +473,6 @@ begin end; function TfrmNewModelProjectParameters.Validate: Boolean; -var - ProjectFolder: string; begin if not FormValidation.Update then begin @@ -482,23 +481,7 @@ begin end; Result := TLexicalModelUtils.DoesProjectFilenameFollowLexicalModelConventions(editModelID.Text + Ext_LexicalModelProject); - - if Result then - begin - if not DirectoryExists(editPath.Text) then - begin - if MessageDlg('The target folder '+editPath.Text+' does not exist. Create it now?', mtConfirmation, mbOkCancel, 0) = mrCancel then - Exit(False); - end; - - ProjectFolder := IncludeTrailingPathDelimiter(editPath.Text) + editModelID.Text; - if DirectoryExists(ProjectFolder) then - begin - if MessageDlg('The project folder '+ProjectFolder+' already exists. Are you sure you want to overwrite it?', mtWarning, - mbOkCancel, 0) = mrCancel then - Exit(False); - end; - end; + Result := Result and VerifyNewProjectPathWithUser(BasePath, ModelID); end; { Dynamically update other fields based on filled details } diff --git a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewProjectParameters.pas b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewProjectParameters.pas index c5323a7894..f82d10fb12 100644 --- a/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewProjectParameters.pas +++ b/developer/src/tike/project/Keyman.Developer.UI.Project.UfrmNewProjectParameters.pas @@ -146,6 +146,7 @@ uses Keyman.Developer.System.Project.ProjectFile, Keyman.Developer.System.KeyboardProjectTemplate, Keyman.Developer.System.ProjectTemplate, + Keyman.Developer.UI.Project.ProjectUI, Keyman.Developer.UI.UfrmSelectBCP47Language, Keyman.System.KeyboardUtils, UfrmMain; @@ -482,8 +483,6 @@ begin end; function TfrmNewProjectParameters.Validate: Boolean; -var - ProjectFolder: string; begin if not FormValidation.Update then begin @@ -492,24 +491,8 @@ begin end; Result := TKeyboardUtils.IsValidKeyboardID(Trim(editKeyboardID.Text), True); - - if Result then - begin - if not DirectoryExists(editPath.Text) then - begin - if MessageDlg('The target folder '+editPath.Text+' does not exist. Create it now?', mtConfirmation, mbOkCancel, 0) = mrCancel then - Exit(False); - end; - - ProjectFolder := IncludeTrailingPathDelimiter(editPath.Text) + editKeyboardID.Text; - if DirectoryExists(ProjectFolder) then - begin - if MessageDlg('The project folder '+ProjectFolder+' already exists. Are you sure you want to overwrite it?', mtWarning, - mbOkCancel, 0) = mrCancel then - Exit(False); - end; - end; -end; + Result := Result and VerifyNewProjectPathWithUser(BasePath, KeyboardID); +end; { Languages Grid } diff --git a/developer/src/tike/tike.dpr b/developer/src/tike/tike.dpr index 19f7ed7756..80cd982b86 100644 --- a/developer/src/tike/tike.dpr +++ b/developer/src/tike/tike.dpr @@ -1,6 +1,6 @@ -program tike; - -uses +program tike; + +uses Vcl.Forms, Vcl.Dialogs, Winapi.Windows, @@ -304,17 +304,18 @@ uses Keyman.Developer.UI.Project.UfrmNewProjectParameters in 'project\Keyman.Developer.UI.Project.UfrmNewProjectParameters.pas' {frmNewProjectParameters}, Keyman.Developer.UI.Project.UfrmCloneLocalProjectParameters in 'project\Keyman.Developer.UI.Project.UfrmCloneLocalProjectParameters.pas' {frmCloneLocalProjectParameters}, Keyman.Developer.UI.Project.UfrmCloneKeymanCloudProjectParameters in 'project\Keyman.Developer.UI.Project.UfrmCloneKeymanCloudProjectParameters.pas' {frmCloneKeymanCloudProjectParameters}, - Keyman.Developer.UI.FormValidation in 'main\Keyman.Developer.UI.FormValidation.pas'; + Keyman.Developer.UI.FormValidation in 'main\Keyman.Developer.UI.FormValidation.pas', + tom_TLB in '..\common\delphi\components\tom_TLB.pas'; -{$R *.RES} -{$R ICONS.RES} -{$R VERSION.RES} -{$R MANIFEST.RES} - -// CEF3 needs to set the LARGEADDRESSAWARE flag which allows 32-bit processes to use up to 3GB of RAM. -// If you don't add this flag the rederer process will crash when you try to load large images. -{$SetPEFlags IMAGE_FILE_LARGE_ADDRESS_AWARE} - -begin - RunKeymanDeveloper; -end. +{$R *.RES} +{$R ICONS.RES} +{$R VERSION.RES} +{$R MANIFEST.RES} + +// CEF3 needs to set the LARGEADDRESSAWARE flag which allows 32-bit processes to use up to 3GB of RAM. +// If you don't add this flag the rederer process will crash when you try to load large images. +{$SetPEFlags IMAGE_FILE_LARGE_ADDRESS_AWARE} + +begin + RunKeymanDeveloper; +end. diff --git a/developer/src/tike/tike.dproj b/developer/src/tike/tike.dproj index ba4a86c430..2580d0591e 100644 --- a/developer/src/tike/tike.dproj +++ b/developer/src/tike/tike.dproj @@ -598,6 +598,7 @@ dfm + Cfg_2 diff --git a/docs/build-bot.md b/docs/build-bot.md index 642f285894..fb3d12cbd0 100644 --- a/docs/build-bot.md +++ b/docs/build-bot.md @@ -60,8 +60,9 @@ Build-bot: :[Platforms] ... * `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. +The list of platforms is built from the `available_platforms_regex` variable in +trigger-definitions.inc.sh (which is a superset of `available_platforms`, with +the addition of `common` and `core`). ## Build Level diff --git a/ios/build.sh b/ios/build.sh index fb4df58ed8..81b477932c 100755 --- a/ios/build.sh +++ b/ios/build.sh @@ -1,4 +1,12 @@ #!/usr/bin/env bash +# +# Build Keyman Engine for iPhone and iPad, Keyman for iPhone and iPad, OEM FirstVoices iOS app, +# Samples: KMSample1 and KMSample2 +# +# OEM FirstVoices iOS app requires the following env vars set: +# RELEASE_OEM should be set +# RELEASE_OEM_FIRSTVOICES should be true + ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" @@ -7,6 +15,17 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "$KEYMAN_ROOT/resources/build/utils.inc.sh" +# +# Restrict available OEM publish targets to those that can be built on the current system +# + +oemtargets=() +if [[ ! -z "${RELEASE_OEM+x}" ]]; then + if [[ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]]; then + oemtargets+=(":fv=../oem/firstvoices/ios Builds OEM FirstVoices for iOS platforms") + fi +fi + builder_describe "Builds Keyman Engine and the Keyman app for use on iOS devices - iPhone and iPad." \ "@/resources/tools/check-markdown test:help" \ "clean" \ @@ -18,7 +37,7 @@ builder_describe "Builds Keyman Engine and the Keyman app for use on iOS devices ":help Online documentation" \ ":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" \ + "${oemtargets[@]}" \ "--sim-artifact+ Also outputs a simulator-friendly test artifact corresponding to the build" builder_parse "$@" diff --git a/ios/tools/prepRelease.sh b/ios/tools/prepRelease.sh index 8065aad2f8..c54748b548 100755 --- a/ios/tools/prepRelease.sh +++ b/ios/tools/prepRelease.sh @@ -105,7 +105,7 @@ cd "$WORK_DIR" # FirstVoices app # -if [ "${RELEASE_OEM_FIRSTVOICES}" = true ]; then +if [[ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]]; then FIRSTVOICESAPP_IPA="../oem/firstvoices/ios/build/Build/Products/Release-iphoneos/FirstVoices.ipa" FIRSTVOICESAPP_IPA_DST="firstvoices-ios-${KEYMAN_VERSION_FOR_FILENAME}.ipa" @@ -129,6 +129,6 @@ fi 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 -if [[ ${RELEASE_OEM_FIRSTVOICES} = true ]]; then +if [[ ${RELEASE_OEM_FIRSTVOICES-false} = true ]]; then write_download_info "${UPLOAD_DIR}" "${FIRSTVOICESAPP_IPA_DST}" "FirstVoices Keyboards" ipa ios fi diff --git a/linux/debian/changelog b/linux/debian/changelog index 517b32e339..516340d09b 100644 --- a/linux/debian/changelog +++ b/linux/debian/changelog @@ -1,3 +1,9 @@ +keyman (18.0.247-1) unstable; urgency=medium + + * New upstream release. + + -- Eberhard Beilharz Tue, 10 Mar 2026 17:58:33 +0100 + keyman (18.0.246-1) unstable; urgency=medium * New upstream release. diff --git a/linux/debian/keyman.bash-completion b/linux/debian/keyman.bash-completion index 5f489e0087..ba3ffe590f 100644 --- a/linux/debian/keyman.bash-completion +++ b/linux/debian/keyman.bash-completion @@ -1,4 +1,5 @@ linux/keyman-config/km-config.bash-completion km-config +linux/keyman-config/km-diag-report.bash-completion km-diag-report linux/keyman-config/km-kvk2ldml.bash-completion km-kvk2ldml linux/keyman-config/km-package-get.bash-completion km-package-get linux/keyman-config/km-package-install.bash-completion km-package-install diff --git a/linux/debian/keyman.links b/linux/debian/keyman.links index 7ed08a12f6..01a312f17e 100644 --- a/linux/debian/keyman.links +++ b/linux/debian/keyman.links @@ -1,4 +1,5 @@ /usr/share/keyman-config/km-config /usr/bin/km-config +/usr/share/keyman-config/km-diag-report /usr/bin/km-diag-report /usr/share/keyman-config/km-kvk2ldml /usr/bin/km-kvk2ldml /usr/share/keyman-config/km-package-get /usr/bin/km-package-get /usr/share/keyman-config/km-package-install /usr/bin/km-package-install diff --git a/linux/keyman-config/keyman_config/diag_report.py b/linux/keyman-config/keyman_config/diag_report.py new file mode 100644 index 0000000000..314abd9878 --- /dev/null +++ b/linux/keyman-config/keyman_config/diag_report.py @@ -0,0 +1,533 @@ +#!/usr/bin/python3 +''' +Keyman is copyright (C) SIL Global. MIT License. + +Collects diagnostic information about the system for troubleshooting. +''' +from datetime import datetime +import logging +import os +import platform +import shutil +import subprocess + +from keyman_config.get_kmp import InstallLocation +from keyman_config.ibus_util import get_ibus_version, verify_ibus_daemon, IbusDaemon +from keyman_config.list_installed_kmp import get_installed_kmp +from keyman_config.version import ( + __version__, + __versionwithtag__, + __pkgversion__, + __tier__ +) + + +class DiagReport: + """ + Collects diagnostic information about the system for troubleshooting. + """ + + def _get_keyman_version(self): + """Get Keyman version information.""" + return { + 'version': __version__, + 'version_with_tag': __versionwithtag__, + 'package_version': __pkgversion__, + 'tier': __tier__ + } + + def _get_package_version(self): + """Get the dpkg package version of keyman.""" + try: + result = subprocess.run( + ['dpkg-query', '-W', '-f', '${Version}', 'keyman'], + capture_output=True, check=False) + if result.returncode == 0: + return result.stdout.decode('utf-8').strip() + except FileNotFoundError: + logging.debug('dpkg-query not found') + except Exception as e: + logging.warning('Error getting package version: %s', e) + return None + + def _get_fcitx_version(self): # sourcery skip: use-next + """Get the fcitx5 version if available.""" + try: + result = subprocess.run( + ['fcitx5', '--version'], + capture_output=True, check=False) + if result.returncode == 0: + # Parse output like "fcitx5 version: 5.0.23" + output = result.stdout.decode('utf-8').strip() + for line in output.split('\n'): + if 'version' in line.lower(): + return line.split(':')[-1].strip() + return output + except FileNotFoundError: + logging.debug('fcitx5 not found') + except Exception as e: + logging.warning('Error getting fcitx version: %s', e) + return None + + def _get_os_info(self): + """Get OS distribution information.""" + os_info = { + 'platform': platform.platform(), + 'system': platform.system(), + 'release': platform.release(), + 'machine': platform.machine() + } + + # Try to get freedesktop os-release info + try: + self._get_freedesktop_os_release_info(os_info) + except (OSError, AttributeError): + # Fallback to lsb_release + try: + result = subprocess.run( + ['lsb_release', '-a'], + capture_output=True, check=False) + if result.returncode == 0: + output = result.stdout.decode('utf-8') + for line in output.split('\n'): + if ':' in line: + key, value = line.split(':', 1) + key = key.strip().lower().replace(' ', '_') + os_info[key] = value.strip() + except FileNotFoundError: + logging.debug('lsb_release not found') + except Exception as e: + logging.warning('Error getting OS info via lsb_release: %s', e) + + return os_info + + def _get_freedesktop_os_release_info(self, os_info): + os_release = platform.freedesktop_os_release() + os_info['pretty_name'] = os_release.get('PRETTY_NAME', '') + os_info['version_codename'] = os_release.get('VERSION_CODENAME', '') + + def _get_display_server(self): + """Get the display server type (X11, Wayland, etc.).""" + session_type = os.environ.get('XDG_SESSION_TYPE', '') + wayland_display = os.environ.get('WAYLAND_DISPLAY', '') + display = os.environ.get('DISPLAY', '') + + return { + 'session_type': session_type, + 'wayland_display': wayland_display, + 'display': display + } + + def _get_desktop_environment(self): + """Get the current desktop environment.""" + return { + 'xdg_current_desktop': os.environ.get('XDG_CURRENT_DESKTOP', ''), + 'xdg_session_desktop': os.environ.get('XDG_SESSION_DESKTOP', ''), + 'desktop_session': os.environ.get('DESKTOP_SESSION', '') + } + + def _get_input_method(self): + """Get current input method configuration.""" + return { + 'gtk_im_module': os.environ.get('GTK_IM_MODULE', ''), + 'qt_im_module': os.environ.get('QT_IM_MODULE', ''), + 'xmodifiers': os.environ.get('XMODIFIERS', ''), + } + + def _get_installed_keyboards(self): + """Get list of installed Keyman keyboards.""" + keyboards = { + 'user': [], + 'shared': [], + 'os': [] + } + + for area, location in [ + ('user', InstallLocation.User), + ('shared', InstallLocation.Shared), + ('os', InstallLocation.OS) + ]: + try: + installed = get_installed_kmp(location) + for pkg_id, data in installed.items(): + keyboards[area].append({ + 'id': pkg_id, + 'name': data.get('name', ''), + 'version': data.get('kmpversion', '') + }) + except Exception as e: + logging.warning('Error getting keyboards from %s: %s', area, e) + + return keyboards + + def _get_ibus_engines(self): + """Get list of IBus preload engines.""" + try: + from keyman_config.gsettings import GSettings + ibus_settings = GSettings('org.freedesktop.ibus.general') + engines = ibus_settings.get('preload-engines') + return engines or [] + except Exception as e: + logging.warning('Error getting IBus engines: %s', e) + return [] + + def _get_gnome_input_sources(self): + """Get Gnome input sources if running Gnome.""" + try: + from keyman_config.gnome_keyboards_util import GnomeKeyboardsUtil, is_gnome_desktop + if is_gnome_desktop(): + util = GnomeKeyboardsUtil() + sources = util.read_input_sources() + return sources or [] + except Exception as e: + logging.warning('Error getting Gnome input sources: %s', e) + return [] + + def _get_browser_versions(self): + """Get versions of Firefox, Chromium, Chrome installed via various package managers.""" + return { + 'apt': self._get_apt_browser_versions(), + 'rpm': self._get_rpm_browser_versions(), + 'pacman': self._get_pacman_browser_versions(), + 'snap': self._get_snap_browser_versions(), + 'flatpak': self._get_flatpak_browser_versions(), + } + + def _get_snap_browser_versions(self): + """Get browser versions installed via snap.""" + snap_browsers = {} + for browser in ['firefox', 'chromium']: + try: + result = subprocess.run( + ['snap', 'info', browser], + capture_output=True, + check=False, + ) + if result.returncode == 0: + output = result.stdout.decode('utf-8') + for line in output.split('\n'): + if line.startswith('installed:'): + version = line.split(':', 1)[1].strip() + if version != '-': + snap_browsers[browser] = version + break + except FileNotFoundError: + logging.debug('snap not found') + except Exception as e: + logging.debug('Error checking snap package %s: %s', browser, e) + return snap_browsers + + def _get_flatpak_browser_versions(self): + """Get browser versions installed via flatpak.""" + flatpak_browsers = {} + for app_id in ['org.mozilla.firefox', 'org.chromium.Chromium', 'com.google.Chrome']: + try: + result = subprocess.run( + ['flatpak', 'info', app_id], + capture_output=True, + check=False, + ) + if result.returncode == 0: + output = result.stdout.decode('utf-8') + for line in output.split('\n'): + if line.strip().startswith('Version:'): + version = line.split(':', 1)[1].strip() + app_name = app_id.split('.')[-1].lower() + flatpak_browsers[app_name] = version + break + except FileNotFoundError: + logging.debug('flatpak not found') + except Exception as e: + logging.debug('Error checking flatpak app %s: %s', app_id, e) + return flatpak_browsers + + def _has_command(self, cmd): + result = shutil.which(cmd) + if not result: + logging.debug(f'{cmd} not found') + return False + return True + + def _get_apt_browser_versions(self): + """Get browser versions installed via apt.""" + apt_browsers = {} + if not self._has_command('dpkg-query'): + return apt_browsers + + for browser in ['firefox', 'chromium-browser', 'chromium', 'google-chrome-stable']: + try: + result = subprocess.run( + ['dpkg-query', '-W', '-f', '${Version}', browser], + capture_output=True, + check=False, + ) + if result.returncode == 0: + if version := result.stdout.decode('utf-8').strip(): + apt_browsers[browser] = version + except Exception as e: + logging.debug('Error checking apt package %s: %s', browser, e) + return apt_browsers + + def _get_rpm_browser_versions(self): + """Get browser versions installed via RPM-based package managers (dnf/yum).""" + rpm_browsers = {} + + pm_command = next( + (cmd for cmd in ['dnf', 'yum'] if self._has_command(cmd)), None + ) + if not pm_command: + logging.debug('Neither dnf nor yum package manager found') + return rpm_browsers + + for browser in ['firefox', 'chromium', 'google-chrome-stable']: + try: + result = subprocess.run( + [pm_command, 'info', 'installed', browser], + capture_output=True, + check=False, + ) + if result.returncode == 0: + output = result.stdout.decode('utf-8') + for line in output.split('\n'): + if line.startswith('Version'): + version = line.split(':', 1)[1].strip() + rpm_browsers[browser] = version + break + except Exception as e: + logging.debug('Error checking rpm package %s: %s', browser, e) + + return rpm_browsers + + def _get_pacman_browser_versions(self): + """Get browser versions installed via Pacman (Arch Linux).""" + pacman_browsers = {} + + if not self._has_command('pacman'): + return pacman_browsers + + for browser in ['firefox', 'chromium', 'google-chrome']: + try: + result = subprocess.run( + ['pacman', '-Q', browser], + capture_output=True, + check=False, + ) + if result.returncode == 0: + output = result.stdout.decode('utf-8').strip() + # pacman -Q outputs: "package version" + parts = output.split() + if len(parts) >= 2: + version = parts[1] + pacman_browsers[browser] = version + except Exception as e: + logging.debug('Error checking pacman package %s: %s', browser, e) + + return pacman_browsers + + def _get_default_browser(self): + """Get the default browser with executable path.""" + try: + result = subprocess.run( + ['xdg-mime', 'query', 'default', 'text/html'], + capture_output=True, check=False) + if result.returncode == 0: + if desktop_id := result.stdout.decode('utf-8').strip(): + if desktop_info := self._parse_desktop_file(desktop_id): + return desktop_info + return desktop_id + except FileNotFoundError: + logging.debug('xdg-mime not found') + except Exception as e: + logging.warning('Error getting default browser: %s', e) + return None + + def _parse_desktop_file(self, desktop_id): + """Parse a .desktop file and extract the Exec command and file path.""" + # Common locations for .desktop files + search_paths = [ + os.path.expanduser('~/.local/share/applications'), + '/usr/local/share/applications', + '/usr/share/applications' + ] + + if 'XDG_DATA_DIRS' in os.environ: + data_dirs = os.environ['XDG_DATA_DIRS'] + new_search_paths = [] + new_search_paths.extend( + os.path.join(data_dir, 'applications') + for data_dir in data_dirs.split(os.pathsep) + ) + search_paths = new_search_paths + search_paths + + if 'XDG_DATA_HOME' in os.environ: + search_paths.insert(0, os.path.join(os.environ['XDG_DATA_HOME'], 'applications')) + + for base_path in search_paths: + desktop_file = os.path.join(base_path, desktop_id) + if os.path.isfile(desktop_file): + try: + with open(desktop_file, 'r', encoding='utf-8') as f: + exec_cmd = None + for line in f: + if line.startswith('Exec='): + exec_cmd = line.split('=', 1)[1].strip() + # Remove field codes like %U, %F, %u, %f + exec_cmd = exec_cmd.split('%')[0].strip() + break + return f"{desktop_file} ({exec_cmd})" if exec_cmd else f"{desktop_file}" + except Exception as e: + logging.debug('Error parsing desktop file %s: %s', desktop_file, e) + return None + + def _report_browsers(self, pkg_managers, browsers): + lines = [] + for (key, description) in pkg_managers: + if browsers.get(key): + lines.append(f' {description}:') + lines.extend( + f" - {name}: {version}" + for name, version in browsers[key].items() + ) + return lines or None + + def _get_ibus_state(self): + ibus_state = verify_ibus_daemon(False) + match ibus_state: + case IbusDaemon.RUNNING: + return "Running" + case IbusDaemon.NOT_RUNNING: + return "Not running" + case IbusDaemon.MORE_THAN_ONE: + return "More than one instance running" + case IbusDaemon.ERROR_USER: + return "invalid/unknown user" + case IbusDaemon.ERROR: + return "got exception finding ibus-daemon" + case _: + return f"Unknown state: {ibus_state}" + + def _create_report(self): # sourcery skip: low-code-quality + """Generate a complete diagnostic report as a formatted string.""" + keyman_info = self._get_keyman_version() + lines = [ + "=" * 60, + f"Keyman for Linux Diagnostic Report ({datetime.now().strftime('%Y-%m-%d')})", + "=" * 60, + "", + "--- Keyman Version ---", + *( + f" Version: {keyman_info['version_with_tag']}", + f" Tier: {keyman_info['tier']}", + ), + ] + if keyman_info['package_version']: + lines.append(f" Package version: {keyman_info['package_version']}") + if pkg_version := self._get_package_version(): + lines.append(f" Installed package (dpkg): {pkg_version}") + + # IBus Version + ibus_version = get_ibus_version() + if ibus_version is not None: + lines.extend(("", "--- IBus ---", f" IBus version: {ibus_version or 'unknown'} ({self._get_ibus_state()})")) + if engines := self._get_ibus_engines(): + lines.append(f" Preload engines: {', '.join(engines)}") + + if fcitx_version := self._get_fcitx_version(): + lines.extend(("", "--- Fcitx5 ---", f" Fcitx5 version: {fcitx_version}")) + + # OS Information + lines.extend(("", "--- Operating System ---")) + os_info = self._get_os_info() + if 'pretty_name' in os_info and os_info['pretty_name']: + lines.append(f" OS: {os_info['pretty_name']}") + else: + lines.append(f" Platform: {os_info['platform']}") + if os_info.get('version_codename'): + lines.append(f" Codename: {os_info['version_codename']}") + lines.extend(( + f" Kernel: {os_info['release']}", + f" Architecture: {os_info['machine']}", + + # Display Server + "", + "--- Display Server ---" + )) + display_info = self._get_display_server() + lines.append(f" Session type: {display_info['session_type'] or 'Not set'}") + if display_info['wayland_display']: + lines.append(f" Wayland display: {display_info['wayland_display']}") + if display_info['display']: + lines.append(f" X11 display: {display_info['display']}") + + # Desktop Environment + lines.extend(("", "--- Desktop Environment ---")) + desktop_info = self._get_desktop_environment() + if desktop_info['xdg_current_desktop']: + lines.append(f" Current desktop: {desktop_info['xdg_current_desktop']}") + if desktop_info['xdg_session_desktop']: + lines.append(f" Session desktop: {desktop_info['xdg_session_desktop']}") + if desktop_info['desktop_session']: + lines.append(f" Desktop session: {desktop_info['desktop_session']}") + + # Input Method + lines.extend(("", "--- Input Method Configuration ---")) + im_info = self._get_input_method() + lines.extend(( + f" GTK_IM_MODULE: {im_info['gtk_im_module'] or 'Not set'}", + f" QT_IM_MODULE: {im_info['qt_im_module'] or 'Not set'}", + f" XMODIFIERS: {im_info['xmodifiers'] or 'Not set'}", + )) + if gnome_sources := self._get_gnome_input_sources(): + lines.extend(("", "--- Gnome Input Sources ---")) + for source in gnome_sources: + if isinstance(source, tuple) and len(source) == 2: + lines.append(f" {source[0]}: {source[1]}") + else: + lines.append(f" {source}") + + # Installed Keyboards + lines.extend(("", "--- Installed Keyman Keyboards ---")) + keyboards = self._get_installed_keyboards() + for area in ['user', 'shared', 'os']: + if keyboards[area]: + lines.append(f" {area.capitalize()} keyboards:") + lines.extend( + f" - {kb['name']} (id: {kb['id']}, version: {kb['version']})" + for kb in keyboards[area] + ) + if not any(keyboards.values()): + lines.append("No Keyman keyboards installed") + + # Browsers + lines.extend(("", "--- Web Browsers ---")) + browsers = self._get_browser_versions() + pkg_managers = [ + ('apt', 'APT packages'), + ('rpm', 'RPM packages (dnf/yum)'), + ('pacman', 'Pacman packages (Arch)'), + ('snap', 'Snap packages'), + ('flatpak', 'Flatpak packages'), + ] + if browser_report := self._report_browsers(pkg_managers, browsers): + lines.extend(browser_report) + else: + lines.append("No Firefox, Chromium, or Chrome installations found") + + # Default browser + if default_browser := self._get_default_browser(): + lines.extend((" Default browser:", f" {default_browser}")) + else: + lines.append(" No default browser detected") + + lines.extend(( + "", + "=" * 60, + "End of Diagnostic Report", + "=" * 60 + )) + return '\n'.join(lines) + + +def get_diagnostic_report(): + """Convenience function to generate a diagnostic report.""" + return DiagReport()._create_report() diff --git a/linux/keyman-config/keyman_config/ibus_util.py b/linux/keyman-config/keyman_config/ibus_util.py index 7eec49a373..4e8c084efe 100644 --- a/linux/keyman-config/keyman_config/ibus_util.py +++ b/linux/keyman-config/keyman_config/ibus_util.py @@ -143,20 +143,38 @@ def verify_ibus_daemon(start): return retval -def _get_ibus_version(): - ibus_version = subprocess.run(('ibus', 'version'), check=False, - stdout=subprocess.PIPE).stdout - match = re.search(r'^IBus (.*)\n$', ibus_version.decode('utf-8')) - if match: - logging.info('Running IBus version %s', match.group(1)) - return match.group(1) - logging.warning('Unable to determine IBus version') - return '' +def get_ibus_version(): + """ + Get the installed IBus version string if available. This checks the `ibus` command + output and parses the reported version. + + Returns the version as a string on success, an empty string if the version + cannot be determined, or None if the `ibus` command is not available. + + Returns: + str | None: The detected IBus version string, an empty string if + parsing fails, or None if the `ibus` command is not found. + """ + try: + ibus_version = subprocess.run(('ibus', 'version'), check=False, + stdout=subprocess.PIPE).stdout + if match := re.search(r'^IBus (.*)\n$', ibus_version.decode('utf-8')): + logging.info('Running IBus version %s', match[1]) + return match[1] + logging.warning('Unable to determine IBus version') + return '' + except FileNotFoundError: + logging.warning('ibus command not found') + return None def _start_ibus_daemon(realuser): try: - if version.parse(_get_ibus_version()) >= version.parse('1.5.28'): + ibus_ver = get_ibus_version() + if not ibus_ver: + return + + if version.parse(ibus_ver) >= version.parse('1.5.28'): # IBus ~1.5.28 added the `start` command, so we use that if possible # and let IBus deal with the necessary parameters args = ['ibus', 'start', '-d'] diff --git a/linux/keyman-config/keyman_config/support_widget.py b/linux/keyman-config/keyman_config/support_widget.py new file mode 100644 index 0000000000..c9d868d66f --- /dev/null +++ b/linux/keyman-config/keyman_config/support_widget.py @@ -0,0 +1,182 @@ +#!/usr/bin/python3 +''' +Keyman is copyright (C) SIL Global. MIT License. +''' + +import gi +from datetime import datetime + +gi.require_version('Gtk', '3.0') + +from gi.repository import Gtk + +from keyman_config import _ +from keyman_config.diag_report import get_diagnostic_report + + +class SupportWidget(Gtk.Box): + """Widget for the Support tab in km-config that shows diagnostic information.""" + + def __init__(self) -> None: + super().__init__(orientation=Gtk.Orientation.VERTICAL) + + # Header label + label = Gtk.Label(_("Support")) + label.set_padding(5, 5) + label.set_halign(Gtk.Align.START) + self.pack_start(label, False, False, 10) + + # Description + desc_label = Gtk.Label() + desc_label.set_markup( + _("Generate a diagnostic report to help troubleshoot issues with Keyman. " + "You can copy this report and include it when reporting issues.\n\n" + "This report does not contain personally identifiable information, " + "but it will include system information and Keyman configuration details. " + "See the privacy policy.")) + desc_label.set_use_markup(True) + desc_label.set_line_wrap(True) + desc_label.set_halign(Gtk.Align.START) + desc_label.set_padding(5, 5) + self.pack_start(desc_label, False, False, 0) + + # Button box + button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + button_box.set_margin_start(5) + button_box.set_margin_top(10) + + self.generate_button = Gtk.Button.new_with_mnemonic(_("_Generate Report")) + self.generate_button.set_tooltip_text(_("Generate a diagnostic report")) + self.generate_button.connect("clicked", self.on_generate_clicked) + button_box.pack_start(self.generate_button, False, False, 0) + + self.copy_button = Gtk.Button.new_with_mnemonic(_("_Copy to Clipboard")) + self.copy_button.set_tooltip_text(_("Copy the report to clipboard")) + self.copy_button.connect("clicked", self.on_copy_clicked) + self.copy_button.set_sensitive(False) + button_box.pack_start(self.copy_button, False, False, 0) + + self.save_button = Gtk.Button.new_with_mnemonic(_("_Save to File...")) + self.save_button.set_tooltip_text(_("Save the report to a file")) + self.save_button.connect("clicked", self.on_save_clicked) + self.save_button.set_sensitive(False) + button_box.pack_start(self.save_button, False, False, 0) + + self.pack_start(button_box, False, False, 0) + + # Scrolled window with text view for the report + scrolled = Gtk.ScrolledWindow() + scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + scrolled.set_margin_start(5) + scrolled.set_margin_end(5) + scrolled.set_margin_top(10) + scrolled.set_margin_bottom(5) + + self.text_view = Gtk.TextView() + self.text_view.set_editable(False) + self.text_view.set_cursor_visible(False) + self.text_view.set_monospace(True) + self.text_view.set_wrap_mode(Gtk.WrapMode.WORD) + self.text_buffer = self.text_view.get_buffer() + + scrolled.add(self.text_view) + self.pack_start(scrolled, True, True, 0) + + self.report_text = "" + + def on_generate_clicked(self, button): + """Generate the diagnostic report and display it.""" + # Show a spinner or progress indication + self.generate_button.set_sensitive(False) + self.text_buffer.set_text(_("Generating report...")) + + # Use idle_add to allow the UI to update + from gi.repository import GLib + GLib.idle_add(self._generate_report) + + def _generate_report(self): + """Actually generate the report (called from idle).""" + try: + self.report_text = get_diagnostic_report() + self.text_buffer.set_text(self.report_text) + self.copy_button.set_sensitive(True) + self.save_button.set_sensitive(True) + except Exception as e: + self.text_buffer.set_text(_("Error generating report: ") + str(e)) + finally: + self.generate_button.set_sensitive(True) + return False # Don't repeat + + def on_copy_clicked(self, button): + """Copy the report to clipboard.""" + if self.report_text: + clipboard = Gtk.Clipboard.get_default(self.get_display()) + clipboard.set_text(self.report_text, -1) + # Show a brief notification + self._show_toast(_("Report copied to clipboard")) + + def on_save_clicked(self, button): + """Save the report to a file.""" + if not self.report_text: + return + + dialog = Gtk.FileChooserDialog( + title=_("Save Diagnostic Report"), + parent=self.get_toplevel(), + action=Gtk.FileChooserAction.SAVE) + dialog.add_buttons( + Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, + Gtk.STOCK_SAVE, Gtk.ResponseType.OK) + dialog.set_do_overwrite_confirmation(True) + date_str = datetime.now().strftime("%Y-%m-%d") + dialog.set_current_name(f"keyman-diagnostic-report_{date_str}.txt") + + filter_text = Gtk.FileFilter() + filter_text.set_name(_("Text files")) + filter_text.add_pattern("*.txt") + dialog.add_filter(filter_text) + + filter_all = Gtk.FileFilter() + filter_all.set_name(_("All files")) + filter_all.add_pattern("*") + dialog.add_filter(filter_all) + + response = dialog.run() + if response == Gtk.ResponseType.OK: + filename = dialog.get_filename() + try: + with open(filename, 'w', encoding='utf-8') as f: + f.write(self.report_text) + f.write('\n') + self._show_toast(_("Report saved to ") + filename) + except IOError as e: + error_dialog = Gtk.MessageDialog( + parent=self.get_toplevel(), + flags=0, + message_type=Gtk.MessageType.ERROR, + buttons=Gtk.ButtonsType.OK, + text=_("Error saving file")) + error_dialog.format_secondary_text(str(e)) + error_dialog.run() + error_dialog.destroy() + + dialog.destroy() + + def _show_toast(self, message): + """Show a brief notification message.""" + # For simplicity, we'll just update a label temporarily + # A more sophisticated implementation could use GtkRevealer or similar + from gi.repository import GLib + + original_text = _("_Generate Report") + self.generate_button.set_use_underline(False) + self.generate_button.set_label(message) + self.generate_button.set_sensitive(False) + + def restore(): + self.generate_button.set_label(original_text) + self.generate_button.set_sensitive(True) + self.generate_button.set_use_underline(True) + return False + + GLib.timeout_add(2000, restore) diff --git a/linux/keyman-config/keyman_config/view_installed.py b/linux/keyman-config/keyman_config/view_installed.py index b252260532..e39224b837 100755 --- a/linux/keyman-config/keyman_config/view_installed.py +++ b/linux/keyman-config/keyman_config/view_installed.py @@ -30,6 +30,7 @@ from keyman_config.keyboard_layouts_widget import KeyboardLayoutsWidget from keyman_config.list_installed_kmp import get_installed_kmp from keyman_config.options_widget import OptionsWidget from keyman_config.sentry_handling import SentryErrorHandling +from keyman_config.support_widget import SupportWidget class ViewInstalledWindowBase(Gtk.Window): @@ -154,6 +155,7 @@ class ViewInstalledWindow(ViewInstalledWindowBase): self.refresh_installed_kmp() stack.add_titled(KeyboardLayoutsWidget(self, self.store, self.restart), "KeyboardLayouts", _("Keyboard Layouts")) stack.add_titled(OptionsWidget(self.sentry), "Options", _("Options")) + stack.add_titled(SupportWidget(), "Support", _("Support")) return outerHbox def _add_app_buttons(self): diff --git a/linux/keyman-config/km-diag-report b/linux/keyman-config/km-diag-report new file mode 100755 index 0000000000..b1533009ca --- /dev/null +++ b/linux/keyman-config/km-diag-report @@ -0,0 +1,39 @@ +#!/usr/bin/python3 + +import argparse +import sys + +from keyman_config import add_standard_arguments, initialize_logging, initialize_sentry +from keyman_config.diag_report import get_diagnostic_report + + +def main(): + parser = argparse.ArgumentParser( + description='Generate a diagnostic report of Keyman and system configuration. ' + 'This report can be used for troubleshooting Keyman issues.') + parser.add_argument('-o', '--output', metavar='FILE', + help='write report to FILE instead of stdout') + add_standard_arguments(parser) + + args = parser.parse_args() + + initialize_logging(args) + initialize_sentry() + + report = get_diagnostic_report() + + if args.output: + try: + with open(args.output, 'w', encoding='utf-8') as f: + f.write(report) + f.write('\n') + print(f"Diagnostic report written to {args.output}") + except IOError as e: + print(f"Error writing to file: {e}", file=sys.stderr) + sys.exit(1) + else: + print(report) + + +if __name__ == "__main__": + main() diff --git a/linux/keyman-config/km-diag-report.bash-completion b/linux/keyman-config/km-diag-report.bash-completion new file mode 100644 index 0000000000..56ed558e0b --- /dev/null +++ b/linux/keyman-config/km-diag-report.bash-completion @@ -0,0 +1,18 @@ +# shellcheck disable=SC2148 +# No hashbang for bash completion scripts! They are intended to be sourced, not executed. + +_km-diag-report_completions() +{ + local cur opts + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + opts="-h --help -o --output -v --verbose -vv --veryverbose --version" + + if [[ ${cur} == -* ]] ; then + # shellcheck disable=SC2207 + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi +} + +complete -F _km-diag-report_completions km-diag-report diff --git a/linux/keyman-config/maninc/km-config.inc b/linux/keyman-config/maninc/km-config.inc index 9783cd6b60..5495960067 100644 --- a/linux/keyman-config/maninc/km-config.inc +++ b/linux/keyman-config/maninc/km-config.inc @@ -1,5 +1,5 @@ [ 0.9 github "devicekit/DeviceKit" ~> 5.0 github "ashleymills/Reachability.swift" -github "getsentry/sentry-cocoa" ~> 8.38.0 +github "getsentry/sentry-cocoa" ~> 8.58.0 diff --git a/oem/firstvoices/ios/Cartfile.resolved b/oem/firstvoices/ios/Cartfile.resolved index ca05da1ae9..54775dca04 100644 --- a/oem/firstvoices/ios/Cartfile.resolved +++ b/oem/firstvoices/ios/Cartfile.resolved @@ -1,4 +1,4 @@ github "ashleymills/Reachability.swift" "v5.2.4" -github "devicekit/DeviceKit" "5.5.0" -github "getsentry/sentry-cocoa" "8.44.0" -github "weichsel/ZIPFoundation" "0.9.19" +github "devicekit/DeviceKit" "5.7.0" +github "getsentry/sentry-cocoa" "8.58.0" +github "weichsel/ZIPFoundation" "0.9.20" diff --git a/package-lock.json b/package-lock.json index 7ade9e5441..1b407a6667 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,8 +46,9 @@ }, "devDependencies": { "@microsoft/api-documenter": "^7.28.0", - "@microsoft/api-extractor": "^7.47.3", + "@microsoft/api-extractor": "^7.57.6", "@types/chai": "^4.3.14", + "@types/mocha": "^10.0.10", "@types/node": "^20.4.1", "@typescript-eslint/eslint-plugin": "^7.13.1", "@web/dev-server-esbuild": "^1.0.2", @@ -69,7 +70,7 @@ "sinon": "^17.0.1", "source-map-support": "^0.5.21", "tslib": "^2.5.2", - "typescript": "^5.4.5" + "typescript": "~5.4.5" }, "engines": { "node": "20.16.0" @@ -150,9 +151,8 @@ "restructure": "3.0.1" }, "devDependencies": { - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", - "ajv": "^8.17.1", + + "ajv": "^8.12.0", "ajv-cli": "^5.0.0", "ajv-formats": "^2.1.1", "c8": "^7.12.0", @@ -161,11 +161,6 @@ "typescript": "^5.4.5" } }, - "common/web/types/node_modules/@types/mocha": { - "version": "5.2.7", - "dev": true, - "license": "MIT" - }, "common/web/types/node_modules/ansi-styles": { "version": "3.2.1", "dev": true, @@ -243,7 +238,7 @@ "dependencies": { "@keymanapp/common-types": "*", "eventemitter3": "^5.0.0", - "fast-xml-parser": "^5.3.4", + "fast-xml-parser": "^5.3.8", "path-browserify": "^1.0.1", "restructure": "^3.0.1", "sax": ">=0.6.0", @@ -287,8 +282,6 @@ }, "devDependencies": { "@sentry/cli": "^2.31.0", - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "c8": "^7.12.0", "esbuild": "^0.25.0", "typescript": "^5.4.5" @@ -304,19 +297,11 @@ }, "devDependencies": { "@keymanapp/resources-gosh": "*", - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "c8": "^7.12.0", "chalk": "^2.4.2", "typescript": "^5.4.5" } }, - "developer/src/kmc-analyze/node_modules/@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true - }, "developer/src/kmc-analyze/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -389,7 +374,6 @@ "devDependencies": { "@keymanapp/developer-test-helpers": "*", "@keymanapp/resources-gosh": "*", - "@types/mocha": "^5.2.7", "@types/node": "^20.4.1", "@types/semver": "^7.3.12", "c8": "^7.12.0", @@ -397,13 +381,6 @@ "typescript": "^5.4.5" } }, - "developer/src/kmc-convert/node_modules/@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true, - "license": "MIT" - }, "developer/src/kmc-convert/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -676,19 +653,11 @@ "@keymanapp/langtags": "*" }, "devDependencies": { - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "c8": "^7.12.0", "chalk": "^2.4.2", "typescript": "^5.4.5" } }, - "developer/src/kmc-keyboard-info/node_modules/@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true - }, "developer/src/kmc-keyboard-info/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -761,8 +730,6 @@ }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "@types/semver": "^7.3.12", "@types/sinon": "^10.0.13", "@types/sinon-chai": "^3.2.9", @@ -772,12 +739,6 @@ "typescript": "^5.4.5" } }, - "developer/src/kmc-kmn/node_modules/@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true - }, "developer/src/kmc-kmn/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -854,8 +815,6 @@ "@keymanapp/developer-test-helpers": "*", "@keymanapp/resources-gosh": "*", "@types/common-tags": "^1.8.4", - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "@types/semver": "^7.3.12", "abnf": "^4.3.1", "c8": "^7.12.0", @@ -865,12 +824,6 @@ "typescript": "^5.4.5" } }, - "developer/src/kmc-ldml/node_modules/@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true - }, "developer/src/kmc-ldml/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -944,8 +897,6 @@ "devDependencies": { "@keymanapp/developer-test-helpers": "*", "@keymanapp/models-templates": "*", - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "c8": "^7.12.0", "chalk": "^2.4.2", "esbuild": "^0.25.0" @@ -959,18 +910,11 @@ "@keymanapp/developer-utils": "*" }, "devDependencies": { - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "c8": "^7.12.0", "chalk": "^2.4.2", "typescript": "^5.4.5" } }, - "developer/src/kmc-model-info/node_modules/@types/mocha": { - "version": "5.2.7", - "dev": true, - "license": "MIT" - }, "developer/src/kmc-model-info/node_modules/ansi-styles": { "version": "3.2.1", "dev": true, @@ -1027,11 +971,6 @@ "node": ">=4" } }, - "developer/src/kmc-model/node_modules/@types/mocha": { - "version": "5.2.7", - "dev": true, - "license": "MIT" - }, "developer/src/kmc-model/node_modules/ansi-styles": { "version": "3.2.1", "dev": true, @@ -1099,18 +1038,11 @@ }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", - "@types/mocha": "^5.2.7", - "@types/node": "^20.4.1", "c8": "^7.12.0", "chalk": "^2.4.2", "typescript": "^5.4.5" } }, - "developer/src/kmc-package/node_modules/@types/mocha": { - "version": "5.2.7", - "dev": true, - "license": "MIT" - }, "developer/src/kmc-package/node_modules/ansi-styles": { "version": "3.2.1", "dev": true, @@ -1167,11 +1099,6 @@ "node": ">=4" } }, - "developer/src/kmc/node_modules/@types/mocha": { - "version": "5.2.7", - "dev": true, - "license": "MIT" - }, "developer/src/kmc/node_modules/ansi-styles": { "version": "3.2.1", "license": "MIT", @@ -1241,11 +1168,11 @@ "license": "MIT", "dependencies": { "@keymanapp/developer-utils": "*", + "@ngrok/ngrok": "^1.7.0", "@sentry/node": "^7.57.0", "chalk": "^4.1.2", "express": "^4.20.0", - "multer": "^2.0.2", - "ngrok": "^5.0.0-beta.2", + "multer": "^2.1.1", "open": "^8.4.0", "restructure": "^3.0.1", "sax": ">=0.6.0", @@ -1257,9 +1184,7 @@ "@keymanapp/keyman-version": "*", "@keymanapp/resources-gosh": "*", "@types/express": "^4.17.13", - "@types/mocha": "^9.1.0", "@types/multer": "^1.4.12", - "@types/node": "^20.4.1", "@types/ws": "^8.2.2", "copyfiles": "^2.4.1", "node-gyp": "^10.2.0", @@ -1270,11 +1195,6 @@ "node-hide-console-window": "^2.2.0" } }, - "developer/src/server/node_modules/@types/mocha": { - "version": "9.1.1", - "dev": true, - "license": "MIT" - }, "developer/src/server/node_modules/@types/multer": { "version": "1.4.12", "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz", @@ -1285,6 +1205,25 @@ "@types/express": "*" } }, + "developer/src/server/node_modules/multer": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz", + "integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "type-is": "^1.6.18" + }, + "engines": { + "node": ">= 10.16.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/@75lb/deep-merge": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@75lb/deep-merge/-/deep-merge-1.1.2.tgz", @@ -1877,24 +1816,26 @@ } }, "node_modules/@microsoft/api-extractor": { - "version": "7.47.3", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.47.3.tgz", - "integrity": "sha512-WKPcmg0vcBnuAX0vArXMbCtCwyN0xuyH1zoRBhuU//X0gP35KA7QInRSeuN0nFV/AKI5NGXapr8dS2lwlCDmgQ==", + "version": "7.57.6", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.57.6.tgz", + "integrity": "sha512-0rFv/D8Grzw1Mjs2+8NGUR+o4h9LVm5zKRtMeWnpdB5IMJF4TeHCL1zR5LMCIudkOvyvjbhMG5Wjs0B5nqsrRQ==", "dev": true, + "license": "MIT", "dependencies": { - "@microsoft/api-extractor-model": "7.29.3", - "@microsoft/tsdoc": "~0.15.0", - "@microsoft/tsdoc-config": "~0.17.0", - "@rushstack/node-core-library": "5.5.0", - "@rushstack/rig-package": "0.5.2", - "@rushstack/terminal": "0.13.2", - "@rushstack/ts-command-line": "4.22.2", - "lodash": "~4.17.15", - "minimatch": "~3.0.3", + "@microsoft/api-extractor-model": "7.33.4", + "@microsoft/tsdoc": "~0.16.0", + "@microsoft/tsdoc-config": "~0.18.1", + "@rushstack/node-core-library": "5.20.3", + "@rushstack/rig-package": "0.7.2", + "@rushstack/terminal": "0.22.3", + "@rushstack/ts-command-line": "5.3.3", + "diff": "~8.0.2", + "lodash": "~4.17.23", + "minimatch": "10.2.1", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", - "typescript": "5.4.2" + "typescript": "5.8.2" }, "bin": { "api-extractor": "bin/api-extractor" @@ -1913,44 +1854,28 @@ } }, "node_modules/@microsoft/api-extractor/node_modules/@microsoft/api-extractor-model": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.29.3.tgz", - "integrity": "sha512-kEWjLr2ygL3ku9EGyjeTnL2S5IxyH9NaF1k1UoI0Nzwr4xEJBSWCVsWuF2+0lPUrRPA6mTY95fR264SJ5ETKQA==", + "version": "7.33.4", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.33.4.tgz", + "integrity": "sha512-u1LTaNTikZAQ9uK6KG1Ms7nvNedsnODnspq/gH2dcyETWvH4hVNGNDvRAEutH66kAmxA4/necElqGNs1FggC8w==", "dev": true, + "license": "MIT", "dependencies": { - "@microsoft/tsdoc": "~0.15.0", - "@microsoft/tsdoc-config": "~0.17.0", - "@rushstack/node-core-library": "5.5.0" - } - }, - "node_modules/@microsoft/api-extractor/node_modules/@microsoft/tsdoc": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.0.tgz", - "integrity": "sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==", - "dev": true - }, - "node_modules/@microsoft/api-extractor/node_modules/@microsoft/tsdoc-config": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.0.tgz", - "integrity": "sha512-v/EYRXnCAIHxOHW+Plb6OWuUoMotxTN0GLatnpOb1xq0KuTNw/WI3pamJx/UbsoJP5k9MCw1QxvvhPcF9pH3Zg==", - "dev": true, - "dependencies": { - "@microsoft/tsdoc": "0.15.0", - "ajv": "~8.12.0", - "jju": "~1.4.0", - "resolve": "~1.22.2" + "@microsoft/tsdoc": "~0.16.0", + "@microsoft/tsdoc-config": "~0.18.1", + "@rushstack/node-core-library": "5.20.3" } }, "node_modules/@microsoft/api-extractor/node_modules/@rushstack/node-core-library": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.5.0.tgz", - "integrity": "sha512-Cl3MYQ74Je5Y/EngMxcA3SpHjGZ/022nKbAO1aycGfQ+7eKyNCBu0oywj5B1f367GCzuHBgy+3BlVLKysHkXZw==", + "version": "5.20.3", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.20.3.tgz", + "integrity": "sha512-95JgEPq2k7tHxhF9/OJnnyHDXfC9cLhhta0An/6MlkDsX2A6dTzDrTUG18vx4vjc280V0fi0xDH9iQczpSuWsw==", "dev": true, + "license": "MIT", "dependencies": { - "ajv": "~8.13.0", + "ajv": "~8.18.0", "ajv-draft-04": "~1.0.0", "ajv-formats": "~3.0.1", - "fs-extra": "~7.0.1", + "fs-extra": "~11.3.0", "import-lazy": "~4.0.0", "jju": "~1.4.0", "resolve": "~1.22.1", @@ -1965,29 +1890,30 @@ } } }, - "node_modules/@microsoft/api-extractor/node_modules/@rushstack/node-core-library/node_modules/ajv": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", - "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "node_modules/@microsoft/api-extractor/node_modules/@rushstack/problem-matcher": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@rushstack/problem-matcher/-/problem-matcher-0.2.1.tgz", + "integrity": "sha512-gulfhBs6n+I5b7DvjKRfhMGyUejtSgOHTclF/eONr8hcgF1APEDjhxIsfdUYYMzC3rvLwGluqLjbwCFZ8nxrog==", "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" + "license": "MIT", + "peerDependencies": { + "@types/node": "*" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@microsoft/api-extractor/node_modules/@rushstack/terminal": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.13.2.tgz", - "integrity": "sha512-t8i0PsGvBHmFBY8pryO3badqFlxQsm2rw3KYrzjcmVkG/WGklKg1qVkr9beAS1Oe8XWDRgj6SkoHkpNjs7aaNw==", + "version": "0.22.3", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.22.3.tgz", + "integrity": "sha512-gHC9pIMrUPzAbBiI4VZMU7Q+rsCzb8hJl36lFIulIzoceKotyKL3Rd76AZ2CryCTKEg+0bnTj406HE5YY5OQvw==", "dev": true, + "license": "MIT", "dependencies": { - "@rushstack/node-core-library": "5.5.0", + "@rushstack/node-core-library": "5.20.3", + "@rushstack/problem-matcher": "0.2.1", "supports-color": "~8.1.1" }, "peerDependencies": { @@ -2000,12 +1926,13 @@ } }, "node_modules/@microsoft/api-extractor/node_modules/@rushstack/ts-command-line": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.22.2.tgz", - "integrity": "sha512-xkvrGd6D9dPlI3I401Thc640WNsEPB1sGEmy12a2VJaPQPwhE6Ik0gEVPZJ/2G1w213eaCAdxUY1xpiTulsmpA==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.3.3.tgz", + "integrity": "sha512-c+ltdcvC7ym+10lhwR/vWiOhsrm/bP3By2VsFcs5qTKv+6tTmxgbVrtJ5NdNjANiV5TcmOZgUN+5KYQ4llsvEw==", "dev": true, + "license": "MIT", "dependencies": { - "@rushstack/terminal": "0.13.2", + "@rushstack/terminal": "0.22.3", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" @@ -2033,6 +1960,7 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^8.0.0" }, @@ -2050,20 +1978,58 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } }, - "node_modules/@microsoft/api-extractor/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "node_modules/@microsoft/api-extractor/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "balanced-match": "^4.0.2" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/minimatch": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.1.tgz", + "integrity": "sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@microsoft/api-extractor/node_modules/string-argv": { @@ -2071,6 +2037,7 @@ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.6.19" } @@ -2080,6 +2047,7 @@ "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" }, @@ -2091,10 +2059,11 @@ } }, "node_modules/@microsoft/api-extractor/node_modules/typescript": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", - "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2111,33 +2080,245 @@ "license": "MIT" }, "node_modules/@microsoft/tsdoc-config": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.18.0.tgz", - "integrity": "sha512-8N/vClYyfOH+l4fLkkr9+myAoR6M7akc8ntBJ4DJdWH2b09uVfr71+LTMpNyG19fNqWDg8KEDZhx5wxuqHyGjw==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.18.1.tgz", + "integrity": "sha512-9brPoVdfN9k9g0dcWkFeA7IH9bbcttzDJlXvkf8b2OBzd5MueR1V2wkKBL0abn0otvmkHJC6aapBOTJDDeMCZg==", "dev": true, "license": "MIT", "dependencies": { "@microsoft/tsdoc": "0.16.0", - "ajv": "~8.12.0", + "ajv": "~8.18.0", "jju": "~1.4.0", "resolve": "~1.22.2" } }, - "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "node_modules/@ngrok/ngrok": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok/-/ngrok-1.7.0.tgz", + "integrity": "sha512-P06o9TpxrJbiRbHQkiwy/rUrlXRupc+Z8KT4MiJfmcdWxvIdzjCaJOdnNkcOTs6DMyzIOefG5tvk/HLdtjqr0g==", + "license": "(MIT OR Apache-2.0)", + "engines": { + "node": ">= 10" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "optionalDependencies": { + "@ngrok/ngrok-android-arm64": "1.7.0", + "@ngrok/ngrok-darwin-arm64": "1.7.0", + "@ngrok/ngrok-darwin-universal": "1.7.0", + "@ngrok/ngrok-darwin-x64": "1.7.0", + "@ngrok/ngrok-freebsd-x64": "1.7.0", + "@ngrok/ngrok-linux-arm-gnueabihf": "1.7.0", + "@ngrok/ngrok-linux-arm64-gnu": "1.7.0", + "@ngrok/ngrok-linux-arm64-musl": "1.7.0", + "@ngrok/ngrok-linux-x64-gnu": "1.7.0", + "@ngrok/ngrok-linux-x64-musl": "1.7.0", + "@ngrok/ngrok-win32-arm64-msvc": "1.7.0", + "@ngrok/ngrok-win32-ia32-msvc": "1.7.0", + "@ngrok/ngrok-win32-x64-msvc": "1.7.0" + } + }, + "node_modules/@ngrok/ngrok-android-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-android-arm64/-/ngrok-android-arm64-1.7.0.tgz", + "integrity": "sha512-8tco3ID6noSaNy+CMS7ewqPoIkIM6XO5COCzsUp3Wv3XEbMSyn65RN6cflX2JdqLfUCHcMyD0ahr9IEiHwqmbQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-arm64/-/ngrok-darwin-arm64-1.7.0.tgz", + "integrity": "sha512-+dmJSOzSO+MNDVrPOca2yYDP1W3KfP4qOlAkarIeFRIfqonQwq3QCBmcR7HAlZocLsSqEwyG6KP4RRvAuT0WGQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-universal": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-universal/-/ngrok-darwin-universal-1.7.0.tgz", + "integrity": "sha512-fDEfewyE2pWGFBhOSwQZObeHUkc65U1l+3HIgSOe094TMHsqmyJD0KTCgW9KSn0VP4OvDZbAISi1T3nvqgZYhQ==", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-x64/-/ngrok-darwin-x64-1.7.0.tgz", + "integrity": "sha512-+fwMi5uHd9G8BS42MMa9ye6exI5lwTcjUO6Ut497Vu0qgLONdVRenRqnEePV+Q3KtQR7NjqkMnomVfkr9MBjtw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-freebsd-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-freebsd-x64/-/ngrok-freebsd-x64-1.7.0.tgz", + "integrity": "sha512-2OGgbrjy3yLRrqAz5N6hlUKIWIXSpR5RjQa2chtZMsSbszQ6c9dI+uVQfOKAeo05tHMUgrYAZ7FocC+ig0dzdQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm-gnueabihf": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm-gnueabihf/-/ngrok-linux-arm-gnueabihf-1.7.0.tgz", + "integrity": "sha512-SN9YIfEQiR9xN90QVNvdgvAemqMLoFVSeTWZs779145hQMhvF9Qd9rnWi6J+2uNNK10OczdV1oc/nq1es7u/3g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm64-gnu/-/ngrok-linux-arm64-gnu-1.7.0.tgz", + "integrity": "sha512-KDMgzPKFU2kbpVSaA2RZBBia5IPdJEe063YlyVFnSMJmPYWCUnMwdybBsucXfV9u1Lw/ZjKTKotIlbTWGn3HGw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm64-musl/-/ngrok-linux-arm64-musl-1.7.0.tgz", + "integrity": "sha512-e66vUdVrBlQ0lT9ZdamB4U604zt5Gualt8/WVcUGzbu8s5LajWd6g/mzZCUjK4UepjvMpfgmCp1/+rX7Rk8d5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-x64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-x64-gnu/-/ngrok-linux-x64-gnu-1.7.0.tgz", + "integrity": "sha512-M6gF0DyOEFqXLfWxObfL3bxYZ4+PnKBHuyLVaqNfFN9Y5utY2mdPOn5422Ppbk4XoIK5/YkuhRqPJl/9FivKEw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-x64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-x64-musl/-/ngrok-linux-x64-musl-1.7.0.tgz", + "integrity": "sha512-4Ijm0dKeoyzZTMaYxR2EiNjtlK81ebflg/WYIO1XtleFrVy4UJEGnxtxEidYoT4BfCqi4uvXiK2Mx216xXKvog==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-arm64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-arm64-msvc/-/ngrok-win32-arm64-msvc-1.7.0.tgz", + "integrity": "sha512-u7qyWIJI2/YG1HTBnHwUR1+Z2tyGfAsUAItJK/+N1G0FeWJhIWQvSIFJHlaPy4oW1Dc8mSDBX9qvVsiQgLaRFg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-ia32-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-ia32-msvc/-/ngrok-win32-ia32-msvc-1.7.0.tgz", + "integrity": "sha512-/UdYUsLNv/Q8j9YJsyIfq/jLCoD8WP+NidouucTUzSoDtmOsXBBT3itLrmPiZTEdEgKiFYLuC1Zon8XQQvbVLA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-x64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-x64-msvc/-/ngrok-win32-x64-msvc-1.7.0.tgz", + "integrity": "sha512-UFJg/duEWzZlLkEs61Gz6/5nYhGaKI62I8dvUGdBR3NCtIMagehnFaFxmnXZldyHmCM8U0aCIFNpWRaKcrQkoA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" } }, "node_modules/@nodelib/fs.scandir": { @@ -2911,21 +3092,6 @@ } } }, - "node_modules/@rushstack/node-core-library/node_modules/fs-extra": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", - "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, "node_modules/@rushstack/problem-matcher": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@rushstack/problem-matcher/-/problem-matcher-0.1.1.tgz", @@ -2942,10 +3108,11 @@ } }, "node_modules/@rushstack/rig-package": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.2.tgz", - "integrity": "sha512-mUDecIJeH3yYGZs2a48k+pbhM6JYwWlgjs2Ca5f2n1G2/kgdgP9D/07oglEGf6mRyXEnazhEENeYTSNDRCwdqA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.7.2.tgz", + "integrity": "sha512-9XbFWuqMYcHUso4mnETfhGVUSaADBRj6HUAAEYk50nMPn8WRICmBuCphycQGNB3duIR6EEZX3Xj3SYc2XiP+9A==", "dev": true, + "license": "MIT", "dependencies": { "resolve": "~1.22.1", "strip-json-comments": "~3.1.1" @@ -3347,16 +3514,6 @@ "node": ">=8" } }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, "node_modules/@sinonjs/text-encoding": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", @@ -3364,16 +3521,6 @@ "dev": true, "license": "(Unlicense OR Apache-2.0)" }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "license": "MIT", - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", @@ -3411,16 +3558,6 @@ "@types/node": "*" } }, - "node_modules/@types/cacheable-request": { - "version": "6.0.2", - "license": "MIT", - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "*", - "@types/node": "*", - "@types/responselike": "*" - } - }, "node_modules/@types/chai": { "version": "4.3.14", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.14.tgz", @@ -3526,10 +3663,6 @@ "integrity": "sha512-4+tE/lwdAahgZT1g30Jkdm9PzFRde0xwxBNUyRsCitRvCQB90iuA2uJYdUnhnANRcqGXaWOGY4FEoxeElNAK2g==", "dev": true }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.1", - "license": "MIT" - }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -3559,10 +3692,6 @@ "@types/istanbul-lib-report": "*" } }, - "node_modules/@types/json-buffer": { - "version": "3.0.0", - "license": "MIT" - }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -3575,13 +3704,6 @@ "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==", "dev": true }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/koa": { "version": "2.15.0", "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz", @@ -3613,7 +3735,9 @@ "license": "MIT" }, "node_modules/@types/mocha": { - "version": "7.0.2", + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", "dev": true, "license": "MIT" }, @@ -3621,6 +3745,7 @@ "version": "20.12.11", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz", "integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==", + "dev": true, "dependencies": { "undici-types": "~5.26.4" } @@ -3654,13 +3779,6 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "dev": true }, - "node_modules/@types/responselike": { - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/semver": { "version": "7.3.13", "dev": true, @@ -3725,6 +3843,7 @@ }, "node_modules/@types/yauzl": { "version": "2.9.2", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -4658,9 +4777,11 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -4812,6 +4933,15 @@ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/argparse": { "version": "2.0.1", "dev": true, @@ -5190,6 +5320,7 @@ }, "node_modules/buffer-crc32": { "version": "0.2.13", + "dev": true, "license": "MIT", "engines": { "node": "*" @@ -5386,29 +5517,6 @@ "node": ">= 6.0.0" } }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "license": "MIT", - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "version": "7.0.2", - "license": "MIT", - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -5681,13 +5789,6 @@ "node": ">=0.8" } }, - "node_modules/clone-response": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "mimic-response": "^1.0.0" - } - }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -5829,17 +5930,6 @@ "node": ">=4.0.0" } }, - "node_modules/compress-brotli": { - "version": "1.3.6", - "license": "MIT", - "dependencies": { - "@types/json-buffer": "~3.0.0", - "json-buffer": "~3.0.1" - }, - "engines": { - "node": ">= 12" - } - }, "node_modules/concat-map": { "version": "0.0.1", "dev": true, @@ -5975,6 +6065,15 @@ "version": "1.0.3", "license": "MIT" }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -6066,6 +6165,7 @@ }, "node_modules/debug": { "version": "4.3.1", + "dev": true, "license": "MIT", "dependencies": { "ms": "2.1.2" @@ -6085,29 +6185,6 @@ "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", "dev": true }, - "node_modules/decompress-response": { - "version": "6.0.0", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", @@ -6205,13 +6282,6 @@ "node": ">=8" } }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -6413,6 +6483,7 @@ }, "node_modules/end-of-stream": { "version": "1.4.4", + "dev": true, "license": "MIT", "dependencies": { "once": "^1.4.0" @@ -7762,6 +7833,7 @@ }, "node_modules/extract-zip": { "version": "2.0.1", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "debug": "^4.1.1", @@ -7825,9 +7897,10 @@ "dev": true }, "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, "funding": [ { "type": "github", @@ -7841,9 +7914,9 @@ "license": "BSD-3-Clause" }, "node_modules/fast-xml-parser": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz", - "integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.8.tgz", + "integrity": "sha512-53jIF4N6u/pxvaL1eb/hEZts/cFLWZ92eCfLrNyCI0k38lettCG/Bs40W9pPwoPXyHQlKu2OUbQtiEIZK/J6Vw==", "funding": [ { "type": "github", @@ -7852,7 +7925,7 @@ ], "license": "MIT", "dependencies": { - "strnum": "^2.1.0" + "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" @@ -7869,6 +7942,7 @@ }, "node_modules/fd-slicer": { "version": "1.1.0", + "dev": true, "license": "MIT", "dependencies": { "pend": "~1.2.0" @@ -8061,35 +8135,18 @@ "license": "MIT" }, "node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", "dev": true, + "license": "MIT", "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs-extra/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/fs-extra/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" + "node": ">=14.14" } }, "node_modules/fs-minipass": { @@ -8211,6 +8268,7 @@ }, "node_modules/get-stream": { "version": "5.2.0", + "dev": true, "license": "MIT", "dependencies": { "pump": "^3.0.0" @@ -8462,29 +8520,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/got": { - "version": "11.8.5", - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, "node_modules/graceful-fs": { "version": "4.2.10", "dev": true, @@ -8604,11 +8639,6 @@ "node": ">=10.4" } }, - "node_modules/hpagent": { - "version": "0.1.2", - "license": "MIT", - "optional": true - }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -8658,7 +8688,8 @@ "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true }, "node_modules/http-errors": { "version": "2.0.0", @@ -8733,17 +8764,6 @@ } } }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "license": "MIT", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -9522,10 +9542,6 @@ "node": ">=18" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "license": "MIT" - }, "node_modules/json-schema-migrate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-2.0.0.tgz", @@ -9597,14 +9613,6 @@ "resolved": "web", "link": true }, - "node_modules/keyv": { - "version": "4.2.2", - "license": "MIT", - "dependencies": { - "compress-brotli": "^1.3.6", - "json-buffer": "3.0.1" - } - }, "node_modules/koa": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.1.tgz", @@ -9843,10 +9851,6 @@ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "license": "MIT" - }, "node_modules/lodash.get": { "version": "4.4.2", "dev": true, @@ -9961,13 +9965,6 @@ "get-func-name": "^2.0.1" } }, - "node_modules/lowercase-keys": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/lru-cache": { "version": "6.0.0", "license": "ISC", @@ -9993,6 +9990,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true + }, "node_modules/make-fetch-happen": { "version": "13.0.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", @@ -10135,13 +10141,6 @@ "node": ">=6" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/minimatch": { "version": "3.1.2", "dev": true, @@ -10155,6 +10154,7 @@ }, "node_modules/minimist": { "version": "1.2.6", + "dev": true, "license": "MIT" }, "node_modules/minipass": { @@ -10301,6 +10301,7 @@ }, "node_modules/mkdirp": { "version": "0.5.6", + "dev": true, "license": "MIT", "dependencies": { "minimist": "^1.2.6" @@ -10649,26 +10650,9 @@ }, "node_modules/ms": { "version": "2.1.2", + "dev": true, "license": "MIT" }, - "node_modules/multer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", - "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", - "license": "MIT", - "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.6.0", - "concat-stream": "^2.0.0", - "mkdirp": "^0.5.6", - "object-assign": "^4.1.1", - "type-is": "^1.6.18", - "xtend": "^4.0.2" - }, - "engines": { - "node": ">= 10.16.0" - } - }, "node_modules/nanocolors": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/nanocolors/-/nanocolors-0.2.13.tgz", @@ -10698,28 +10682,6 @@ "node": ">= 0.4.0" } }, - "node_modules/ngrok": { - "version": "5.0.0-beta.2", - "resolved": "https://registry.npmjs.org/ngrok/-/ngrok-5.0.0-beta.2.tgz", - "integrity": "sha512-UzsyGiJ4yTTQLCQD11k1DQaMwq2/SsztBg2b34zAqcyjS25qjDpogMKPaCKHwe/APRTHeel3iDXcVctk5CNaCQ==", - "hasInstallScript": true, - "dependencies": { - "extract-zip": "^2.0.1", - "got": "^11.8.5", - "lodash.clonedeep": "^4.5.0", - "uuid": "^7.0.0 || ^8.0.0", - "yaml": "^2.2.2" - }, - "bin": { - "ngrok": "bin/ngrok" - }, - "engines": { - "node": ">=14.2" - }, - "optionalDependencies": { - "hpagent": "^0.1.2" - } - }, "node_modules/nise": { "version": "5.1.9", "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", @@ -10963,31 +10925,12 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/normalize-url": { - "version": "6.1.0", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/nwsapi": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", "dev": true }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -11114,13 +11057,6 @@ "node": ">= 0.8.0" } }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/p-event": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", @@ -11449,6 +11385,7 @@ }, "node_modules/pend": { "version": "1.2.0", + "dev": true, "license": "MIT" }, "node_modules/picocolors": { @@ -11691,6 +11628,7 @@ }, "node_modules/pump": { "version": "3.0.0", + "dev": true, "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -11789,16 +11727,6 @@ } ] }, - "node_modules/quick-lru": { - "version": "5.1.1", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/randombytes": { "version": "2.1.0", "dev": true, @@ -11922,10 +11850,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "license": "MIT" - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -11984,13 +11908,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/responselike": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "lowercase-keys": "^2.0.0" - } - }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -12806,9 +12723,9 @@ "link": true }, "node_modules/strnum": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.0.tgz", - "integrity": "sha512-w0S//9BqZZGw0L0Y8uLSelFGnDJgTyyNQLmSlPnVz43zPAiqu3w4t8J8sDqqANOGeZIZ/9jWuPguYcEnsoHv4A==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", + "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==", "funding": [ { "type": "github", @@ -13085,6 +13002,47 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "typescript": ">=2.7" + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/tsc-watch": { "version": "4.6.2", "dev": true, @@ -13260,6 +13218,7 @@ "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13368,7 +13327,8 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true }, "node_modules/unique-filename": { "version": "3.0.0", @@ -13786,6 +13746,7 @@ }, "node_modules/xtend": { "version": "4.0.2", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4" @@ -13802,14 +13763,6 @@ "version": "4.0.0", "license": "ISC" }, - "node_modules/yaml": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", - "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", - "engines": { - "node": ">= 14" - } - }, "node_modules/yargs": { "version": "16.2.0", "dev": true, @@ -13910,6 +13863,7 @@ }, "node_modules/yauzl": { "version": "2.10.0", + "dev": true, "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", @@ -13925,6 +13879,18 @@ "node": ">= 4.0.0" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "dev": true, @@ -14243,7 +14209,6 @@ "devDependencies": { "@keymanapp/common-types": "*", "@keymanapp/web-utils": "*", - "@types/mocha": "^7.0.2", "c8": "^7.12.0", "typescript": "^5.4.5" } @@ -14261,7 +14226,6 @@ "devDependencies": { "@keymanapp/common-types": "*", "@keymanapp/resources-gosh": "*", - "@types/mocha": "^7.0.2", "c8": "^7.12.0", "typescript": "^5.4.5" } diff --git a/package.json b/package.json index 87b62dfe63..697bf312e1 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,9 @@ "private": true, "devDependencies": { "@microsoft/api-documenter": "^7.28.0", - "@microsoft/api-extractor": "^7.47.3", + "@microsoft/api-extractor": "^7.57.6", "@types/chai": "^4.3.14", + "@types/mocha": "^10.0.10", "@types/node": "^20.4.1", "@typescript-eslint/eslint-plugin": "^7.13.1", "@web/dev-server-esbuild": "^1.0.2", @@ -26,7 +27,7 @@ "sinon": "^17.0.1", "source-map-support": "^0.5.21", "tslib": "^2.5.2", - "typescript": "^5.4.5" + "typescript": "~5.4.5" }, "scripts": {}, "workspaces": [ diff --git a/resources/build/ci/trigger-definitions.inc.sh b/resources/build/ci/trigger-definitions.inc.sh index aeded92eb4..bac1cf99de 100644 --- a/resources/build/ci/trigger-definitions.inc.sh +++ b/resources/build/ci/trigger-definitions.inc.sh @@ -19,7 +19,7 @@ readonly build_level_release=release readonly valid_build_levels="$build_level_skip|$build_level_build|$build_level_release" # -# Target platforms +# Target platforms (note: 'common' matches all common* platforms in build bot commands) # available_platforms=(android common_web common_windows common_mac common_linux ios linux mac web windows developer) diff --git a/resources/build/pr-build-status/package-lock.json b/resources/build/pr-build-status/package-lock.json index e2980aed2c..0b48fb68b1 100644 --- a/resources/build/pr-build-status/package-lock.json +++ b/resources/build/pr-build-status/package-lock.json @@ -842,13 +842,13 @@ "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==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" diff --git a/resources/locate_emscripten.inc.sh b/resources/locate_emscripten.inc.sh index 67411b8391..50c8534ef4 100644 --- a/resources/locate_emscripten.inc.sh +++ b/resources/locate_emscripten.inc.sh @@ -4,6 +4,15 @@ # shellcheck disable=SC2154 . "${KEYMAN_ROOT}/resources/build/minimum-versions.inc.sh" +# On Windows/ARM64 machines, we build with the Emscripten x64 toolchain + +if builder_is_windows; then + if [[ "${MSYSTEM-x}" == CLANGARM64 ]]; then + builder_echo "Using x86_64 for emscripten" + export EMSDK_ARCH=x86_64 + fi +fi + # # We don't want to rely on emcc being on the path, because Emscripten puts far # too many things onto the path (in particular for us, node). diff --git a/resources/teamcity/linux/keyman-linux-release.sh b/resources/teamcity/linux/keyman-linux-release.sh index 83f85d7ab9..be29f77d97 100755 --- a/resources/teamcity/linux/keyman-linux-release.sh +++ b/resources/teamcity/linux/keyman-linux-release.sh @@ -117,8 +117,10 @@ function _publish_to_launchpad() { function _publish_linux_help() { builder_echo start "upload linux help" "Upload new Keyman Linux help to help.keyman.com" + cd "${HELP_KEYMAN_COM}" # shellcheck disable=SC2016 git config credential.helper '!f() { sleep 1; echo "username=${GITHUB_USER}"; echo "password=${GITHUB_TOKEN}"; }; f' + cd "${KEYMAN_ROOT}/resources/build" "${KEYMAN_ROOT}/resources/build/ci/help-keyman-com.sh" linux builder_echo end "upload linux help" success "Upload new Keyman Linux help to help.keyman.com" diff --git a/web/src/engine/osk/gesture-processor/src/engine/configuration/gestureRecognizerConfiguration.ts b/web/src/engine/osk/gesture-processor/src/engine/configuration/gestureRecognizerConfiguration.ts index c3d6de5974..26abbd695d 100644 --- a/web/src/engine/osk/gesture-processor/src/engine/configuration/gestureRecognizerConfiguration.ts +++ b/web/src/engine/osk/gesture-processor/src/engine/configuration/gestureRecognizerConfiguration.ts @@ -120,7 +120,7 @@ export function preprocessRecognizerConfig( processingConfig.maxRoamingBounds = processingConfig.maxRoamingBounds ?? processingConfig.targetRoot; processingConfig.safeBounds = processingConfig.safeBounds ?? new PaddedZoneSource([2]); - processingConfig.itemIdentifier = processingConfig.itemIdentifier ?? (() => null); + processingConfig.itemIdentifier = processingConfig.itemIdentifier ?? ((): HoveredItemType => null); processingConfig.recordingMode = !!processingConfig.recordingMode; processingConfig.historyLength = (processingConfig.historyLength ?? 0) > 0 ? processingConfig.historyLength : 0; diff --git a/web/src/engine/predictive-text/templates/package.json b/web/src/engine/predictive-text/templates/package.json index cbe3fb8747..fc1deedbfb 100644 --- a/web/src/engine/predictive-text/templates/package.json +++ b/web/src/engine/predictive-text/templates/package.json @@ -53,7 +53,6 @@ "devDependencies": { "@keymanapp/common-types": "*", "@keymanapp/web-utils": "*", - "@types/mocha": "^7.0.2", "c8": "^7.12.0", "typescript": "^5.4.5" }, diff --git a/web/src/engine/predictive-text/wordbreakers/package.json b/web/src/engine/predictive-text/wordbreakers/package.json index 0f73f19f69..5ebe40654b 100644 --- a/web/src/engine/predictive-text/wordbreakers/package.json +++ b/web/src/engine/predictive-text/wordbreakers/package.json @@ -56,7 +56,6 @@ "devDependencies": { "@keymanapp/common-types": "*", "@keymanapp/resources-gosh": "*", - "@types/mocha": "^7.0.2", "c8": "^7.12.0", "typescript": "^5.4.5" }, diff --git a/web/src/test/auto/headless/engine/main/headless/languageProcessor.tests.js b/web/src/test/auto/headless/engine/main/headless/languageProcessor.tests.js index 6d866ef8ed..e5b8856016 100644 --- a/web/src/test/auto/headless/engine/main/headless/languageProcessor.tests.js +++ b/web/src/test/auto/headless/engine/main/headless/languageProcessor.tests.js @@ -22,11 +22,10 @@ global.keyman = {}; // So that keyboard-based checks against the global `keyman` // Test the KeyboardProcessor interface. describe('LanguageProcessor', function() { let languageProcessor; - const callbacks = new TestCompilerCallbacks(); + const callbacks = new TestCompilerCallbacks(this); beforeEach(function() { languageProcessor = new LanguageProcessor(LMWorker, new TranscriptionCache()); - callbacks.clear(); }); afterEach(function() {