diff --git a/HISTORY.md b/HISTORY.md index dc0b6a7594..ea82962795 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,13 @@ # Keyman Version History +## 19.0.20 alpha 2025-04-01 + +* fix(android): move responsibility for setting keyboard height preference into `applyKeyboardHeight()` (#13578) + +## 19.0.19 alpha 2025-03-31 + +* chore: merge beta to master B18S4 (#13606) + ## 19.0.18 alpha 2025-03-25 * chore(developer): improve ldml code coverage (#13516) diff --git a/VERSION.md b/VERSION.md index 9dcade37d9..13ec463b7b 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.19 +19.0.21 \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustKeyboardHeightActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustKeyboardHeightActivity.java index de4530fe3a..45503b60b4 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustKeyboardHeightActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustKeyboardHeightActivity.java @@ -4,7 +4,6 @@ package com.tavultesoft.kmapro; import android.content.Context; -import android.content.SharedPreferences; import android.content.res.Configuration; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; @@ -34,7 +33,6 @@ public class AdjustKeyboardHeightActivity extends BaseActivity { private static ImageView sampleKeyboard = null; // Keeps track of the adjusted keyboard height for saving - private static SharedPreferences.Editor editor = null; private static int currentHeight = 0; private static ViewGroup.LayoutParams layoutParams; @@ -65,9 +63,6 @@ public class AdjustKeyboardHeightActivity extends BaseActivity { adjustKeyboardHeightActivityTitle.setTextColor(ContextCompat.getColor(this, R.color.ms_white)); adjustKeyboardHeightActivityTitle.setText(titleStr); - SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - editor = prefs.edit(); - sampleKeyboard = (ImageView) findViewById(R.id.sample_keyboard); layoutParams = sampleKeyboard.getLayoutParams(); currentHeight = KMManager.getKeyboardHeight(context); @@ -77,9 +72,7 @@ public class AdjustKeyboardHeightActivity extends BaseActivity { resetButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // Clear the keyboard height preferences to reset - editor.remove(KMManager.KMKey_KeyboardHeightPortrait); - editor.remove(KMManager.KMKey_KeyboardHeightLandscape); - editor.commit(); + KMManager.applyKeyboardHeight(context, KMManager.KeyboardHeight_Reset); // Restore default height currentHeight = KMManager.getKeyboardHeight(context); @@ -107,11 +100,7 @@ public class AdjustKeyboardHeightActivity extends BaseActivity { break; case MotionEvent.ACTION_UP: // Save the currentHeight when the user releases - int orientation = KMManager.getOrientation(context); - String keyboardHeightKey = (orientation == Configuration.ORIENTATION_LANDSCAPE) ? - KMManager.KMKey_KeyboardHeightLandscape : KMManager.KMKey_KeyboardHeightPortrait; - editor.putInt(keyboardHeightKey, currentHeight); - editor.commit(); + KMManager.applyKeyboardHeight(context, currentHeight); break; } return true; @@ -139,12 +128,5 @@ public class AdjustKeyboardHeightActivity extends BaseActivity { refreshSampleKeyboard(this); } - @Override - public void onBackPressed() { - // Apply the adjusted height on exit - KMManager.applyKeyboardHeight(this, currentHeight); - - super.onBackPressed(); - } } \ No newline at end of file diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 7080f0a1fb..95a99d635b 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -25,6 +25,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Point; import android.graphics.Typeface; import android.inputmethodservice.InputMethodService; @@ -273,6 +274,8 @@ public final class KMManager { // will be blocked. private static boolean maySendCrashReport = true; + private final static String KMEngine_PrefsKey = "KMAPreferences"; + // Keyman public keys public static final String KMKey_ID = "id"; public static final String KMKey_Name = "name"; @@ -295,8 +298,8 @@ public final class KMManager { public static final String KMKey_KMPInstall_Mode = "kmpInstallMode"; public static final String KMKey_KeyboardModified = "lastModified"; public static final String KMKey_KeyboardRTL = "rtl"; - public static final String KMKey_KeyboardHeightPortrait = "keyboardHeightPortrait"; - public static final String KMKey_KeyboardHeightLandscape = "keyboardHeightLandscape"; + private static final String KMKey_KeyboardHeightPortrait = "keyboardHeightPortrait"; + private static final String KMKey_KeyboardHeightLandscape = "keyboardHeightLandscape"; public static final String KMKey_LongpressDelay = "longpressDelay"; @@ -345,6 +348,14 @@ public final class KMManager { public static final int KMMinimum_LongpressDelay = 300; public static final int KMMaximum_LongpressDelay = 1500; + // Default keyboard heights + public static final int KeyboardHeight_Reset = 0; // To reset to default, applyKeyboardHeight + public static final int KeyboardHeight_Invalid = -1; // If invalid orientation passed to functions + public static int KeyboardHeight_Context_Portrait_Default = 0; // Default portrait height + public static int KeyboardHeight_Context_Landscape_Default = 0; // Default landscape height + public static int KeyboardHeight_Context_Portrait_Current = 0; // Current portrait height + public static int KeyboardHeight_Context_Landscape_Current = 0; // Current landscape height + // Default prediction/correction setting public static final int KMDefault_Suggestion = SuggestionType.PREDICTIONS_WITH_CORRECTIONS.toInt(); @@ -498,6 +509,12 @@ public final class KMManager { migrateCloudKeyboards(appContext); CloudDownloadMgr.getInstance().initialize(appContext); + + calculateDefaultKeyboardHeights(context); + SharedPreferences prefs = context.getSharedPreferences(KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE); + KeyboardHeight_Context_Portrait_Current = prefs.getInt(KMManager.KMKey_KeyboardHeightPortrait, KMManager.KeyboardHeight_Context_Portrait_Default); + KeyboardHeight_Context_Landscape_Current = prefs.getInt(KMManager.KMKey_KeyboardHeightLandscape, KMManager.KeyboardHeight_Context_Landscape_Default); + } public static void executeResourceUpdate(Context aContext) @@ -1463,7 +1480,7 @@ public final class KMManager { * @param suggestType SuggestionType */ public static void setMaySuggest(String languageID, SuggestionType suggestType) { - SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + SharedPreferences prefs = appContext.getSharedPreferences(KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putInt(KMManager.getLanguageAutoCorrectionPreferenceKey(languageID), suggestType.toInt()); editor.commit(); @@ -1582,7 +1599,7 @@ public final class KMManager { model = model.replaceAll("\"", "'"); // When entering password field, maySuggest should override to disabled - SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + SharedPreferences prefs = appContext.getSharedPreferences(KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE); int suggestionPreference = prefs.getInt(getLanguageAutoCorrectionPreferenceKey(languageID), KMDefault_Suggestion); RelativeLayout.LayoutParams params; @@ -2213,7 +2230,7 @@ public final class KMManager { */ public static int getLongpressDelay() { SharedPreferences prefs = appContext.getSharedPreferences( - appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE); return prefs.getInt(KMKey_LongpressDelay, KMDefault_LongpressDelay); } @@ -2230,7 +2247,7 @@ public final class KMManager { } SharedPreferences prefs = appContext.getSharedPreferences( - appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putInt(KMKey_LongpressDelay, longpressDelay); editor.commit(); @@ -2266,21 +2283,80 @@ public final class KMManager { return bannerHeight; } + /** + * Returns the current keyboard height in dp for the current device orientation + */ public static int getKeyboardHeight(Context context) { - int defaultHeight = (int) context.getResources().getDimension(R.dimen.keyboard_height); - SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); - int orientation = getOrientation(context); - if (orientation == Configuration.ORIENTATION_PORTRAIT) { - return prefs.getInt(KMManager.KMKey_KeyboardHeightPortrait, defaultHeight); - } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - return prefs.getInt(KMManager.KMKey_KeyboardHeightLandscape, defaultHeight); - } - - return defaultHeight; + return getKeyboardHeight(context, orientation); } + /** + * Returns the current keyboard height in dp for the specified device + * orientation + */ + public static int getKeyboardHeight(Context context, int orientation) { + SharedPreferences prefs = context.getSharedPreferences(KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE); + if (orientation == Configuration.ORIENTATION_PORTRAIT) { + return KMManager.KeyboardHeight_Context_Portrait_Current; + } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + return KMManager.KeyboardHeight_Context_Landscape_Current; + } else { + return KMManager.KeyboardHeight_Invalid; // Invalid orientation + } + } + + /** + * Apply the keyboard height for the current orientation and store it in SharedPreferences. + * If height is KMManager.KeyboardHeight_Reset, remove the stored height for that + * orientation, resetting to the default height for the device/orientation. + * @param context Context + * @param height int - keyboard height in dp + */ public static void applyKeyboardHeight(Context context, int height) { + int orientation = getOrientation(context); + applyKeyboardHeight(context, height, orientation); + } + + /** + * Apply the keyboard height for the specified orientation and store it in SharedPreferences. + * If height is KMManager.KeyboardHeight_Reset, remove the stored height for that orientation. + * @param context Context + * @param height int - keyboard height in dp + * @param orientation int - Configuration.ORIENTATION_PORTRAIT or Configuration.ORIENTATION_LANDSCAPE + */ + public static void applyKeyboardHeight(Context context, int height, int orientation) { + SharedPreferences prefs = context.getSharedPreferences(KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + int defaultHeightForContext = getDefaultKeyboardHeight(orientation); + if (orientation != Configuration.ORIENTATION_PORTRAIT && orientation != Configuration.ORIENTATION_LANDSCAPE) { + return; // Invalid orientation, do nothing + } + if (height == KMManager.KeyboardHeight_Reset) { + // Passing 0 will reset the stored height for this orientation. + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + editor.remove(KMManager.KMKey_KeyboardHeightLandscape); + } else /* (orientation == Configuration.ORIENTATION_PORTRAIT) */{ + editor.remove(KMManager.KMKey_KeyboardHeightPortrait); + } + height = defaultHeightForContext; + } else { + // Applying gating to 50%-200% of default height (following Keyman) + if (height < (defaultHeightForContext / 2)) { + height = (int) (defaultHeightForContext / 2); + } else if (height > (defaultHeightForContext * 2)) { + height = (int) (defaultHeightForContext * 2); + } + + // Store the new height based on the current orientation + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + editor.putInt(KMManager.KMKey_KeyboardHeightLandscape, height); + } else /* (orientation == Configuration.ORIENTATION_PORTRAIT) */ { + editor.putInt(KMManager.KMKey_KeyboardHeightPortrait, height); + } + } + editor.commit(); + // Confirm new LayoutParams for in-app or system keyboards if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP)) { InAppKeyboard.loadJavascript(KMString.format("setOskHeight('%s')", height)); RelativeLayout.LayoutParams params = getKeyboardLayoutParams(); @@ -2293,6 +2369,53 @@ public final class KMManager { } } + /** + * Returns the default keyboard height in dp for the current device + * and orientation + */ + public static int getDefaultKeyboardHeight(Context context) { + int orientation = getOrientation(context); + return getDefaultKeyboardHeight(orientation); + } + + /** + * Returns the current keyboard height in dp for the specified orientation + * for the current device + */ + public static int getDefaultKeyboardHeight(int orientation) { + if (orientation == Configuration.ORIENTATION_PORTRAIT) { + return KMManager.KeyboardHeight_Context_Portrait_Default; + } else if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + return KMManager.KeyboardHeight_Context_Landscape_Default; + } else { + return KMManager.KeyboardHeight_Invalid; // Invalid orientation + } + } + + private static void calculateDefaultKeyboardHeights(Context context) { + if (isTestMode()) { + // Keyboard height not needed for unit tests #13578 + KeyboardHeight_Context_Portrait_Default = 0; + KeyboardHeight_Context_Landscape_Default = 0; + return; + } + + Resources resources = context.getResources(); + Configuration originalConfig = resources.getConfiguration(); + + Configuration newConfig = new Configuration(originalConfig); + newConfig.orientation = Configuration.ORIENTATION_PORTRAIT; + Context newContext = context.createConfigurationContext(newConfig); + Resources newResources = newContext.getResources(); + KeyboardHeight_Context_Portrait_Default = (int) newResources.getDimension(R.dimen.keyboard_height); + + newConfig = new Configuration(originalConfig); + newConfig.orientation = Configuration.ORIENTATION_LANDSCAPE; + newContext = context.createConfigurationContext(newConfig); + newResources = newContext.getResources(); + KeyboardHeight_Context_Landscape_Default = (int) newResources.getDimension(R.dimen.keyboard_height); + } + /** * Get the size of the area the window would occupy. * API 30+ @@ -2322,7 +2445,7 @@ public final class KMManager { } protected static void setPersistentShouldShowHelpBubble(boolean flag) { - SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); + SharedPreferences prefs = appContext.getSharedPreferences(KMManager.KMEngine_PrefsKey, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putBoolean(KMManager.KMKey_ShouldShowHelpBubble, flag); editor.commit(); diff --git a/android/docs/engine/KMManager/applyKeyboardHeight.md b/android/docs/engine/KMManager/applyKeyboardHeight.md index 2830519c62..cd9bb3fbd0 100644 --- a/android/docs/engine/KMManager/applyKeyboardHeight.md +++ b/android/docs/engine/KMManager/applyKeyboardHeight.md @@ -5,12 +5,17 @@ title: KMManager.applyKeyboardHeight() ## Summary The **applyKeyboardHeight()** method sets the height of the keyboard frame for the device's current [screen orientation](https://developer.android.com/training/multiscreen/screensizes#TaskUseOriQuali) -(portrait vs landscape). +(portrait vs landscape) or a specified orientation. ## Syntax ```java KMManager.applyKeyboardHeight(Context context, int height) ``` +or + +```java +KMManager.applyKeyboardHeight(Context context, int height, int orientation) +``` ### Parameters @@ -18,7 +23,10 @@ KMManager.applyKeyboardHeight(Context context, int height) : The context `height` -: The height of the keyboard frame in *density-independent pixels (dp)* +: The height of the keyboard frame in *density-independent pixels (dp)*. Pass `KMManager.KeyboardHeight_Reset` to reset the keyboard height (for current orientation) to device-specific defaults. + +`orientation` _(Optional)_ +: Accepts a [screen orientation](https://developer.android.com/training/multiscreen/screensizes#TaskUseOriQuali) value. This is most useful if you want to change the size of the keyboard in the other (non-current) orientation. If `orientation` is not defined, keyboard height is set for the current device orientation. ## Description Use this method when you want to increase or decrease the keyboard height for @@ -30,11 +38,11 @@ For reference, here's a table of the default Keyman keyboard heights for various Device Type and Screen Orientation | Default height (dp) | |-----------------------------------|---------------------| | Default handset in portrait | 205 | -| Default handset in landscape | 150 | +| Default handset in landscape | 100 | | 7" tablet in portrait | 305 | -| 7" tablet in landscape | 200 | +| 7" tablet in landscape | 140 | | 10" tablet in portrait | 405 | -| 10" tablet in landscape | 300 | +| 10" tablet in landscape | 200 | **Note:** This new keyboard height would be applied for all platforms, so an adjusted keyboard height for a phone would appear too small for a tablet. @@ -50,5 +58,23 @@ The following script illustrates the use of `applyKeyboardHeight()`: KMManager.applyKeyboardHeight(this, newKeyboardHeight); ``` +or + +```java + import android.content.res.Configuration; + ... + // Increase the Keyman keyboard height for landscape mode to 250dp (default Keyman value for most phones is 100dp) + int newKeyboardHeight = 250; + KMManager.applyKeyboardHeight(this, newKeyboardHeight, Configuration.ORIENTATION_LANDSCAPE); +``` + + +The following script illustrates the use of `applyKeyboardHeight()` to reset keyboard height to default: + +```java + // Reset keyboard to default height + KMManager.applyKeyboardHeight(this, KMManager.KeyboardHeight_Reset); +``` + ## See also * [getKeyboardHeight()](getKeyboardHeight) diff --git a/android/docs/engine/KMManager/getDefaultKeyboardHeight.md b/android/docs/engine/KMManager/getDefaultKeyboardHeight.md new file mode 100644 index 0000000000..56705cae7b --- /dev/null +++ b/android/docs/engine/KMManager/getDefaultKeyboardHeight.md @@ -0,0 +1,55 @@ +--- +title: KMManager.getDefaultKeyboardHeight() +--- + +## Summary + +The **`getDefaultKeyboardHeight()`** method returns the default height of the keyboard frame for the current or specefied orientation. + +## Syntax + +``` javascript +KMManager.getDefaultKeyboardHeight(Context context) +``` +or +``` javascript +KMManager.getDefaultKeyboardHeight(int orientation) +``` + +### Parameters (Choose One) + +`context` _(Alternate)_ +: The context. + +`orientation` _(Alternate)_ +: Accepts a [screen orientation](https://developer.android.com/training/multiscreen/screensizes#TaskUseOriQuali) value. This is most useful if you want to get the size of the keyboard in the other (non-current) orientation. If `orientation` parameter is not passed in, keyboard height is returned for the current device orientation. + +### Returns + +Returns the default height of the keyboard frame in *density-independent pixels (dp)*. + +## Description + +Use this method to get the default height of the keyboard frame. + +## Examples + +### Example: Using `getDefaultKeyboardHeight()` + +The following script illustrate the use of `getDefaultKeyboardHeight()`: + +``` javascript + int keyboardHeight = KMManager.getDefaultKeyboardHeight(this); +``` +or +```java + import android.content.res.Configuration; + ... + // Get the current Keyman keyboard height for landscape mode. + + int keyboardHeightLandscape = KMManager.getDefaultKeyboardHeight( Configuration.ORIENTATION_LANDSCAPE); +``` + +## See also + +- [applyKeyboardHeight()](applyKeyboardHeight) diff --git a/android/docs/engine/KMManager/getKeyboardHeight.md b/android/docs/engine/KMManager/getKeyboardHeight.md index c459e45ddb..fb8efb2b96 100644 --- a/android/docs/engine/KMManager/getKeyboardHeight.md +++ b/android/docs/engine/KMManager/getKeyboardHeight.md @@ -12,12 +12,19 @@ frame. ``` javascript KMManager.getKeyboardHeight(Context context) ``` +or +``` javascript +KMManager.getKeyboardHeight(Context context, int orientation) +``` ### Parameters `context` : The context. +`orientation` _(Optional)_ +: Accepts a [screen orientation](https://developer.android.com/training/multiscreen/screensizes#TaskUseOriQuali) value. This is most useful if you want to get the size of the keyboard in the other (non-current) orientation. If `orientation` parameter is not passed in, keyboard height is returned for the current device orientation. + ### Returns Returns the height of the keyboard frame in *density-independent pixels @@ -36,7 +43,15 @@ The following script illustrate the use of `getKeyboardHeight()`: ``` javascript int keyboardHeight = KMManager.getKeyboardHeight(this); ``` +or +```java + import android.content.res.Configuration; + ... + // Get the current Keyman keyboard height for landscape mode. + + int keyboardHeightLandscape = KMManager.getKeyboardHeight(this, Configuration.ORIENTATION_LANDSCAPE); +``` ## See also -- [applyKeyboardHeight](applyKeyboardHeight) +- [applyKeyboardHeight()](applyKeyboardHeight) diff --git a/android/docs/help/basic/config/adjust-keyboard-height.md b/android/docs/help/basic/config/adjust-keyboard-height.md index fcc459a5e5..6b18c5dc2a 100644 --- a/android/docs/help/basic/config/adjust-keyboard-height.md +++ b/android/docs/help/basic/config/adjust-keyboard-height.md @@ -6,11 +6,11 @@ title: Adjust Keyboard Height - Keyman for Android Help This menu is used to adjust the height of the on-screen keyboard (OSK), which does not include the suggestion banner. -Drag the blank OSK at the bottom of the screen to increase or decrease the keyboard height for the device's current orientation (portrait vs landscape). The adjusted keyboard heights are automatically saved when the blank OSK is released. +Drag the blank OSK at the bottom of the screen to increase or decrease the keyboard height for the device's current orientation (portrait vs landscape). Turn the device 90 degrees to the alternate orientation and repeat the process. The adjusted keyboard heights are automatically saved when the blank OSK is released. ![](../../android_images/blank-osk.png) ### Reset to Default Height -Press the button to reset the OSK height to the default values. +Press the button to reset the OSK height (for the current orientation) to the default device-specific height. ![](../../android_images/reset-to-default.png) diff --git a/common/tools/hextobin/index.ts b/common/tools/hextobin/index.ts index b53b2d734a..168ccb0539 100644 --- a/common/tools/hextobin/index.ts +++ b/common/tools/hextobin/index.ts @@ -1,5 +1,5 @@ -import * as fs from 'fs'; -import * as rd from 'readline'; +import * as fs from 'node:fs'; +import * as rd from 'node:readline'; export default async function hextobin(inputFilename: string, outputFilename?: string, options?: {silent?: boolean}): Promise { diff --git a/common/tools/sourcemap-path-remapper/src/index.ts b/common/tools/sourcemap-path-remapper/src/index.ts index fca96c3b14..af1f184698 100644 --- a/common/tools/sourcemap-path-remapper/src/index.ts +++ b/common/tools/sourcemap-path-remapper/src/index.ts @@ -1,5 +1,5 @@ -import * as fs from 'fs'; -import path from 'path'; +import * as fs from 'node:fs'; +import path from 'node:path'; import convertSourceMap from 'convert-source-map'; // Transforms sourcemaps among various common formats. // Base64, stringified-JSON, end-of-file comment... diff --git a/core/tools/api-header-extractor/src/index.ts b/core/tools/api-header-extractor/src/index.ts index e0c6cab918..76498044df 100644 --- a/core/tools/api-header-extractor/src/index.ts +++ b/core/tools/api-header-extractor/src/index.ts @@ -1,5 +1,5 @@ -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; // TODO: support multiple infiles to generate from multiple headers diff --git a/developer/src/common/web/test-helpers/TestCompilerCallbacks.ts b/developer/src/common/web/test-helpers/TestCompilerCallbacks.ts index fad1dd646f..04e5734f18 100644 --- a/developer/src/common/web/test-helpers/TestCompilerCallbacks.ts +++ b/developer/src/common/web/test-helpers/TestCompilerCallbacks.ts @@ -1,5 +1,5 @@ -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; import { CompilerEvent, CompilerCallbacks, CompilerPathCallbacks, CompilerFileSystemCallbacks, CompilerError, CompilerNetAsyncCallbacks, DefaultCompilerFileSystemAsyncCallbacks, CompilerFileSystemAsyncCallbacks, diff --git a/developer/src/common/web/utils/package.json b/developer/src/common/web/utils/package.json index 05cba553bb..caff2e23a5 100644 --- a/developer/src/common/web/utils/package.json +++ b/developer/src/common/web/utils/package.json @@ -10,7 +10,6 @@ ], "dependencies": { "@keymanapp/common-types": "*", - "@sentry/node": "^7.57.0", "eventemitter3": "^5.0.0", "fast-xml-parser": "^5.0.9", "path-browserify": "^1.0.1", diff --git a/developer/src/common/web/utils/src/compiler-interfaces.ts b/developer/src/common/web/utils/src/compiler-interfaces.ts index 869e0165da..18b93d9c88 100644 --- a/developer/src/common/web/utils/src/compiler-interfaces.ts +++ b/developer/src/common/web/utils/src/compiler-interfaces.ts @@ -233,9 +233,10 @@ export enum CompilerErrorNamespace { */ LdmlKeyboardCompiler = 0x0000, /** - * common/web/types errors between 0x1000…0x1FFF + * common/web/utils errors between 0x1000…0x1FFF + * was previously called CommonTypes */ - CommonTypes = 0x1000, + DeveloperUtils = 0x1000, /** * kmc-kmn errors between 0x2000…0x2FFF; these map to * the base codes found in kmn_compiler_errors.h, exclusive severity flags diff --git a/developer/src/common/web/utils/src/common-messages.ts b/developer/src/common/web/utils/src/developer-utils-messages.ts similarity index 71% rename from developer/src/common/web/utils/src/common-messages.ts rename to developer/src/common/web/utils/src/developer-utils-messages.ts index 6eca08d37c..d5bf1cd64a 100644 --- a/developer/src/common/web/utils/src/common-messages.ts +++ b/developer/src/common/web/utils/src/developer-utils-messages.ts @@ -4,14 +4,14 @@ import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageDef as def, CompilerMessageSpec as m } from './compiler-interfaces.js'; import { constants } from '@keymanapp/ldml-keyboard-constants'; -const CommonTypesErrMask = CompilerErrorNamespace.CommonTypes; -// const SevInfo = CompilerErrorSeverity.Info | CommonTypesErrMask; -// const SevHint = CompilerErrorSeverity.Hint | CommonTypesErrMask; -// const SevWarn = CompilerErrorSeverity.Warn | CommonTypesErrMask; -const SevError = CompilerErrorSeverity.Error | CommonTypesErrMask; -// const SevFatal = CompilerErrorSeverity.Fatal | CommonTypesErrMask; +const DeveloperUtilsErrMask = CompilerErrorNamespace.DeveloperUtils; +// const SevInfo = CompilerErrorSeverity.Info | DeveloperUtilsErrMask; +// const SevHint = CompilerErrorSeverity.Hint | DeveloperUtilsErrMask; +// const SevWarn = CompilerErrorSeverity.Warn | DeveloperUtilsErrMask; +const SevError = CompilerErrorSeverity.Error | DeveloperUtilsErrMask; +// const SevFatal = CompilerErrorSeverity.Fatal | DeveloperUtilsErrMask; -export class CommonTypesMessages { +export class DeveloperUtilsMessages { // structured Ajv validation error static ERROR_SchemaValidationError = SevError | 0x0001; static Error_SchemaValidationError = (o:{instancePath:string, keyword:string, message: string, params: string}) => m(this.ERROR_SchemaValidationError, @@ -58,4 +58,22 @@ export class CommonTypesMessages { this.ERROR_InvalidPackageFile, `Package source file is invalid: ${(o.e ?? 'unknown error').toString()}` ); + + static ERROR_InvalidProjectFile = SevError | 0x000A; + static Error_InvalidProjectFile = (o:{message:string}) => m( + this.ERROR_InvalidProjectFile, + `Project file is not valid: ${def(o.message)}`, + ); + + static ERROR_UnsupportedProjectVersion = SevError | 0x0013; + static Error_UnsupportedProjectVersion = (o:{version:string}) => m( + this.ERROR_UnsupportedProjectVersion, + `Project version ${def(o.version)} is not supported by this version of Keyman Developer.`, + ); + + static ERROR_ProjectFileCouldNotBeRead = SevError | 0x0014; + static Error_ProjectFileCouldNotBeRead = () => m( + this.ERROR_ProjectFileCouldNotBeRead, + `Project file could not be read` + ); }; diff --git a/developer/src/common/web/utils/src/index.ts b/developer/src/common/web/utils/src/index.ts index f3e23e79f2..7ed873905f 100644 --- a/developer/src/common/web/utils/src/index.ts +++ b/developer/src/common/web/utils/src/index.ts @@ -3,8 +3,6 @@ */ export { validateMITLicense } from './utils/validate-mit-license.js'; -export { KeymanSentry, SentryNodeOptions } from './utils/KeymanSentry.js'; -export { getOption, loadOptions, clearOptions } from './utils/options.js'; export { escapeMarkdownChar } from './utils/markdown.js'; export { KeymanUrls } from './utils/keyman-urls.js'; @@ -63,7 +61,7 @@ export { defaultCompilerOptions, CompilerBaseOptions, CompilerOptions, CompilerE export { UrlSubpathCompilerCallback } from './utils/UrlSubpathCompilerCallback.js'; -export { CommonTypesMessages } from './common-messages.js'; +export { DeveloperUtilsMessages } from './developer-utils-messages.js'; export * as SourceFilenamePatterns from './source-filename-patterns.js'; export { KeymanXMLType, KeymanXMLWriter, KeymanXMLReader } from './xml-utils.js'; @@ -73,3 +71,5 @@ export * as CloudUrls from './cloud-urls.js'; export { getFontFamily, getFontFamilySync } from './font-family.js'; export * as ValidIds from './valid-ids.js'; + +export * as ProjectLoader from './project-loader.js'; \ No newline at end of file diff --git a/developer/src/common/web/utils/src/project-loader.ts b/developer/src/common/web/utils/src/project-loader.ts new file mode 100644 index 0000000000..a62545e877 --- /dev/null +++ b/developer/src/common/web/utils/src/project-loader.ts @@ -0,0 +1,34 @@ +import { KeymanDeveloperProject } from "./types/kpj/keyman-developer-project.js"; +import { KPJFileReader } from "./types/kpj/kpj-file-reader.js"; +import { CompilerAsyncCallbacks, CompilerCallbacks } from "./compiler-callbacks.js"; +import { DeveloperUtilsMessages } from "./developer-utils-messages.js"; + +// TODO-KMC-TEST: refactor asyncCallbacks, make it a first-class part of CompilerCallbacks +export async function loadProjectFromFile(infile: string, callbacks: CompilerCallbacks, asyncCallbacks: CompilerAsyncCallbacks = null): Promise { + const kpjData = await (asyncCallbacks ?? callbacks).fsAsync.readFile(infile); + if(!kpjData) { + callbacks.reportMessage(DeveloperUtilsMessages.Error_ProjectFileCouldNotBeRead()); + return null; + } + const reader = new KPJFileReader(callbacks); + let kpj = null; + try { + kpj = reader.read(kpjData); + if(!kpj) { + callbacks.reportMessage(DeveloperUtilsMessages.Error_ProjectFileCouldNotBeRead()); + return null; + } + + if(kpj.KeymanDeveloperProject?.Options?.Version && kpj.KeymanDeveloperProject.Options.Version != "1.0" && kpj.KeymanDeveloperProject.Options.Version != "2.0") { + callbacks.reportMessage(DeveloperUtilsMessages.Error_UnsupportedProjectVersion({version: kpj.KeymanDeveloperProject.Options.Version})); + return null; + } + reader.validate(kpj); + } catch(e) { + callbacks.reportMessage(DeveloperUtilsMessages.Error_InvalidProjectFile({message: (e??'').toString()})); + return null; + } + const project = await reader.transform(infile, kpj); + return project; +} + 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 73f2280ebc..c41a30ebb4 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 @@ -9,7 +9,7 @@ import { util } from '@keymanapp/common-types'; import boxXmlArray = util.boxXmlArray; -import { CommonTypesMessages } from "../../common-messages.js"; +import { DeveloperUtilsMessages } from "../../developer-utils-messages.js"; import { CompilerCallbacks } from "../../compiler-callbacks.js"; import { KeymanXMLReader } from "../../xml-utils.js"; import { KpsPackage } from "./kps-file.js"; @@ -29,7 +29,7 @@ export class KpsFileReader { a = new KeymanXMLReader('kps') .parse(data) as KpsPackage; } catch(e) { - this.callbacks.reportMessage(CommonTypesMessages.Error_InvalidPackageFile({e})); + this.callbacks.reportMessage(DeveloperUtilsMessages.Error_InvalidPackageFile({e})); } return a; })(); diff --git a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts index 0a5abf6355..c8bbbe40dd 100644 --- a/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/developer/src/common/web/utils/src/types/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -4,7 +4,7 @@ * Reads a LDML XML keyboard file into JS object tree and resolves imports */ import { SchemaValidators, util } from '@keymanapp/common-types'; -import { CommonTypesMessages } from '../../common-messages.js'; +import { DeveloperUtilsMessages } from '../../developer-utils-messages.js'; import { CompilerCallbacks } from "../../compiler-callbacks.js"; import { LDMLKeyboardXMLSourceFile, LKImport, ImportStatus } from './ldml-keyboard-xml.js'; import { constants } from '@keymanapp/ldml-keyboard-constants'; @@ -219,7 +219,7 @@ export class LDMLKeyboardXMLSourceFileReader { const { base, path } = asImport; // If base is not an empty string (or null/undefined), then it must be 'cldr' if (base && base !== constants.cldr_import_base) { - this.callbacks.reportMessage(CommonTypesMessages.Error_ImportInvalidBase({base, path, subtag})); + this.callbacks.reportMessage(DeveloperUtilsMessages.Error_ImportInvalidBase({base, path, subtag})); return false; } let importData: Uint8Array; @@ -228,7 +228,7 @@ export class LDMLKeyboardXMLSourceFileReader { // CLDR import const paths = path.split('/'); if (paths[0] == '' || paths[1] == '' || paths.length !== 2) { - this.callbacks.reportMessage(CommonTypesMessages.Error_ImportInvalidPath({base, path, subtag})); + this.callbacks.reportMessage(DeveloperUtilsMessages.Error_ImportInvalidPath({base, path, subtag})); return false; } if (constants.treatAsLatest(paths[0])) { @@ -241,7 +241,7 @@ export class LDMLKeyboardXMLSourceFileReader { importData = this.readLocalImportFile(path); } if (!importData || !importData.length) { - this.callbacks.reportMessage(CommonTypesMessages.Error_ImportReadFail({base, path, subtag})); + this.callbacks.reportMessage(DeveloperUtilsMessages.Error_ImportReadFail({base, path, subtag})); return false; } const importXml: any = this.loadUnboxed(importData); // TODO-LDML: have to load as any because it is an arbitrary part @@ -249,7 +249,7 @@ export class LDMLKeyboardXMLSourceFileReader { // importXml will have one property: the root element. if (!importRootNode) { - this.callbacks.reportMessage(CommonTypesMessages.Error_ImportWrongRoot({base, path, subtag})); + this.callbacks.reportMessage(DeveloperUtilsMessages.Error_ImportWrongRoot({base, path, subtag})); return false; } // pull all children of importXml[subtag] into obj @@ -259,7 +259,7 @@ export class LDMLKeyboardXMLSourceFileReader { if (!Array.isArray(subsubval)) { // This is somewhat of an internal error, indicating that a non-mergeable XML file was imported // Not exercisable with the standard LDML imports. - this.callbacks.reportMessage(CommonTypesMessages.Error_ImportMergeFail({base, path, subtag, subsubtag})); + this.callbacks.reportMessage(DeveloperUtilsMessages.Error_ImportMergeFail({base, path, subtag, subsubtag})); return false; } // Mark all children as an import @@ -286,7 +286,7 @@ export class LDMLKeyboardXMLSourceFileReader { public validate(source: LDMLKeyboardXMLSourceFile | LDMLKeyboardTestDataXMLSourceFile): boolean { if(!SchemaValidators.default.ldmlKeyboard3(source)) { for (const err of (SchemaValidators.default.ldmlKeyboard3).errors) { - this.callbacks.reportMessage(CommonTypesMessages.Error_SchemaValidationError({ + this.callbacks.reportMessage(DeveloperUtilsMessages.Error_SchemaValidationError({ instancePath: err.instancePath, keyword: err.keyword, message: err.message || 'Unknown AJV Error', // docs say 'message' is optional if 'messages:false' in options @@ -318,7 +318,7 @@ export class LDMLKeyboardXMLSourceFileReader { try { source = this.loadUnboxed(file); } catch(e) { - this.callbacks.reportMessage(CommonTypesMessages.Error_InvalidXml({e})); + this.callbacks.reportMessage(DeveloperUtilsMessages.Error_InvalidXml({e})); return null; } @@ -359,7 +359,7 @@ export class LDMLKeyboardXMLSourceFileReader { } else if (r.length === 1) { return r[0]; } else { - this.callbacks.reportMessage(CommonTypesMessages.Error_TestDataUnexpectedArray({subtag})); + this.callbacks.reportMessage(DeveloperUtilsMessages.Error_TestDataUnexpectedArray({subtag})); return null; // ERROR } } diff --git a/developer/src/common/web/utils/test/helpers/index.ts b/developer/src/common/web/utils/test/helpers/index.ts index e36d07429b..25fd6856c7 100644 --- a/developer/src/common/web/utils/test/helpers/index.ts +++ b/developer/src/common/web/utils/test/helpers/index.ts @@ -1,9 +1,9 @@ /** * Helpers and utilities for the Mocha tests. */ -import * as path from 'path'; -import * as fs from "fs"; -import { fileURLToPath } from 'url'; +import * as path from 'node:path'; +import * as fs from "node:fs"; +import { fileURLToPath } from 'node:url'; /** * Builds a path to the fixture with the given path components. 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 1b809f39d0..ddf6ec48a9 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 @@ -14,7 +14,7 @@ import { makePathToFixture } from '../helpers/index.js'; import { KpsFileReader } from "../../src/types/kps/kps-file-reader.js"; import { KpsFileWriter } from '../../src/types/kps/kps-file-writer.js'; -import { CommonTypesMessages } from '../../src/common-messages.js'; +import { DeveloperUtilsMessages } from '../../src/developer-utils-messages.js'; const callbacks = new TestCompilerCallbacks(); @@ -80,6 +80,6 @@ describe('kps-file-reader', function () { assert.isNull(kps); assert.lengthOf(callbacks.messages, 1); - assert.isTrue(callbacks.hasMessage(CommonTypesMessages.ERROR_InvalidPackageFile)); + assert.isTrue(callbacks.hasMessage(DeveloperUtilsMessages.ERROR_InvalidPackageFile)); }); }); diff --git a/developer/src/common/web/utils/test/ldml/ldml-keyboard-xml-reader.tests.ts b/developer/src/common/web/utils/test/ldml/ldml-keyboard-xml-reader.tests.ts index dc6f3021ae..d2dfb86370 100644 --- a/developer/src/common/web/utils/test/ldml/ldml-keyboard-xml-reader.tests.ts +++ b/developer/src/common/web/utils/test/ldml/ldml-keyboard-xml-reader.tests.ts @@ -1,7 +1,7 @@ import 'mocha'; import {assert} from 'chai'; +import { DeveloperUtilsMessages } from '../../src/developer-utils-messages.js'; import { Constants } from '@keymanapp/common-types'; -import { CommonTypesMessages } from '../../src/common-messages.js'; import { LKKey, ImportStatus } from '../../src/types/ldml-keyboard/ldml-keyboard-xml.js'; import { testReaderCases } from '../helpers/reader-callback-test.js'; @@ -19,7 +19,7 @@ describe('ldml keyboard xml reader tests', function () { testReaderCases([ { subpath: 'invalid-structure-per-dtd.xml', - errors: [CommonTypesMessages.Error_SchemaValidationError({ + errors: [DeveloperUtilsMessages.Error_SchemaValidationError({ instancePath: '/keyboard3', keyword: 'required', message: `must have required property 'info'`, @@ -28,7 +28,7 @@ describe('ldml keyboard xml reader tests', function () { }, { subpath: 'invalid-conforms-to.xml', - errors: [CommonTypesMessages.Error_SchemaValidationError({ + errors: [DeveloperUtilsMessages.Error_SchemaValidationError({ instancePath: '/keyboard3/conformsTo', keyword: 'enum', message: `must be equal to one of the allowed values`, @@ -150,7 +150,7 @@ describe('ldml keyboard xml reader tests', function () { subpath: 'invalid-import-base.xml', loadfail: true, errors: [ - CommonTypesMessages.Error_ImportInvalidBase({ + DeveloperUtilsMessages.Error_ImportInvalidBase({ base: 'SOME_INVALID_BASE', path: 'B', subtag: 'C' @@ -161,7 +161,7 @@ describe('ldml keyboard xml reader tests', function () { subpath: 'invalid-import-local.xml', loadfail: true, errors: [ - CommonTypesMessages.Error_ImportReadFail({ + DeveloperUtilsMessages.Error_ImportReadFail({ base: undefined, path: 'keys-Zyyy-DOESNOTEXIST.xml', subtag: 'keys' @@ -172,7 +172,7 @@ describe('ldml keyboard xml reader tests', function () { subpath: 'invalid-import-path.xml', loadfail: true, errors: [ - CommonTypesMessages.Error_ImportInvalidPath({ + DeveloperUtilsMessages.Error_ImportInvalidPath({ base: 'cldr', path: '45/too/many/slashes/leading/to/nothing-Zxxx-does-not-exist.xml', subtag: null, @@ -183,7 +183,7 @@ describe('ldml keyboard xml reader tests', function () { subpath: 'invalid-import-readfail.xml', loadfail: true, errors: [ - CommonTypesMessages.Error_ImportReadFail({ + DeveloperUtilsMessages.Error_ImportReadFail({ base: 'cldr', path: '45/none-Zxxx-does-not-exist.xml', subtag: null, @@ -194,7 +194,7 @@ describe('ldml keyboard xml reader tests', function () { subpath: 'invalid-import-wrongroot.xml', loadfail: true, errors: [ - CommonTypesMessages.Error_ImportWrongRoot({ + DeveloperUtilsMessages.Error_ImportWrongRoot({ base: null, path: '45/keys-Zyyy-punctuation.xml', subtag: 'flicks', diff --git a/developer/src/kmc-copy/src/KeymanProjectCopier.ts b/developer/src/kmc-copy/src/KeymanProjectCopier.ts index a751e46731..545163aded 100644 --- a/developer/src/kmc-copy/src/KeymanProjectCopier.ts +++ b/developer/src/kmc-copy/src/KeymanProjectCopier.ts @@ -4,7 +4,23 @@ * Copy a keyboard or lexical model project */ -import { CloudUrls, GitHubUrls, CompilerCallbacks, KeymanCompiler, KeymanCompilerArtifact, KeymanCompilerArtifacts, KeymanCompilerResult, KeymanDeveloperProject, KeymanDeveloperProjectOptions, KPJFileReader, KPJFileWriter, KpsFileReader, KpsFileWriter, ValidIds, CompilerBaseOptions } from "@keymanapp/developer-utils"; +import { + CloudUrls, + GitHubUrls, + CompilerCallbacks, + KeymanCompiler, + KeymanCompilerArtifact, + KeymanCompilerArtifacts, + KeymanCompilerResult, + KeymanDeveloperProject, + KeymanDeveloperProjectOptions, + ProjectLoader, + KPJFileWriter, + KpsFileReader, + KpsFileWriter, + ValidIds, + CompilerBaseOptions, +} from "@keymanapp/developer-utils"; import { KeymanFileTypes } from "@keymanapp/common-types"; import { CopierMessages } from "./copier-messages.js"; @@ -124,7 +140,7 @@ export class KeymanProjectCopier implements KeymanCompiler { this.sourceId = this.callbacks.path.basename(projectSource, KeymanFileTypes.Source.Project); // copy project file - const project = await this.loadProjectFromFile(projectSource); + const project = await ProjectLoader.loadProjectFromFile(projectSource, this.callbacks, this.asyncCallbacks); if(!project) { // loadProjectFromFile already reported errors return null; @@ -637,31 +653,6 @@ export class KeymanProjectCopier implements KeymanCompiler { return this.callbacks.path.join(this.outPath, 'source'); } - private async loadProjectFromFile(filename: string): Promise { - const kpjData = await this.asyncCallbacks.fsAsync.readFile(filename); - const reader = new KPJFileReader(this.asyncCallbacks); - let kpj = null; - try { - kpj = reader.read(kpjData); - if(!kpj) { - this.callbacks.reportMessage(CopierMessages.Error_ProjectFileCouldNotBeRead({filename})); - return null; - } - - if(kpj.KeymanDeveloperProject?.Options?.Version && kpj.KeymanDeveloperProject.Options.Version != "1.0" && kpj.KeymanDeveloperProject.Options.Version != "2.0") { - this.callbacks.reportMessage(CopierMessages.Error_UnsupportedProjectVersion({filename, version: kpj.KeymanDeveloperProject.Options.Version})); - return null; - } - reader.validate(kpj); - } catch(e) { - this.callbacks.reportMessage(CopierMessages.Error_InvalidProjectFile({filename, message: (e ?? '').toString()})); - return null; - } - const project = await reader.transform(filename, kpj); - - return project; - } - /** * Write artifacts from a successful compile to disk, via callbacks methods. * The artifacts written will include all files from the project, across diff --git a/developer/src/kmc-copy/src/copier-messages.ts b/developer/src/kmc-copy/src/copier-messages.ts index 976002a35f..cb8d0c83de 100644 --- a/developer/src/kmc-copy/src/copier-messages.ts +++ b/developer/src/kmc-copy/src/copier-messages.ts @@ -92,22 +92,9 @@ export class CopierMessages { `The package source file '${def(o.filename)}' could not be loaded. The file may have an invalid format` ); - static ERROR_UnsupportedProjectVersion = SevError | 0x000D; - static Error_UnsupportedProjectVersion = (o:{filename: string, version: string}) => m(this.ERROR_UnsupportedProjectVersion, - `Project version ${def(o.version)} for '${def(o.filename)}' is not supported by this version of Keyman Developer` - ); - - static ERROR_InvalidProjectFile = SevError | 0x000E; - static Error_InvalidProjectFile = (o:{filename: string, message: string}) => m( - this.ERROR_InvalidProjectFile, - `Project file '${def(o.filename)}' is not valid: ${def(o.message)}` - ); - - static ERROR_ProjectFileCouldNotBeRead = SevError | 0x000F; - static Error_ProjectFileCouldNotBeRead = (o:{filename: string}) => m( - this.ERROR_ProjectFileCouldNotBeRead, - `Project file '${def(o.filename)}' could not be read` - ); + // 0x000D ERROR_UnsupportedProjectVersion: moved to developer-utils-messages + // 0x000E ERROR_InvalidProjectFile: moved to developer-utils-messages + // 0x000F ERROR_ProjectFileCouldNotBeRead: moved to developer-utils-messages static INFO_DryRun = SevInfo | 0x0010; static Info_DryRun = (o:{outPath: string}) => m( diff --git a/developer/src/kmc-copy/tsconfig.json b/developer/src/kmc-copy/tsconfig.json index 1fc0a249be..e54ad9adaa 100644 --- a/developer/src/kmc-copy/tsconfig.json +++ b/developer/src/kmc-copy/tsconfig.json @@ -9,6 +9,7 @@ "paths": { "@keymanapp/keyman-version": ["../../../common/web/keyman-version/keyman-version.mts"], "@keymanapp/common-types": ["../../../common/web/types/src/main"], + "@keymanapp/developer-utils": ["../common/web/utils"], }, }, @@ -21,5 +22,6 @@ "references": [ { "path": "../../../common/web/keyman-version" }, { "path": "../../../common/web/types/" }, + { "path": "../common/web/utils/" }, ] } diff --git a/developer/src/kmc-model/tools/create-override-script-regexp.ts b/developer/src/kmc-model/tools/create-override-script-regexp.ts index 38f6ade0a6..63c39aeea5 100644 --- a/developer/src/kmc-model/tools/create-override-script-regexp.ts +++ b/developer/src/kmc-model/tools/create-override-script-regexp.ts @@ -9,8 +9,8 @@ * If you need to add more Unicode blocks, customize SPACELESS_SCRIPT_BLOCKS. */ -import {readFileSync} from "fs"; -import * as path from "path"; +import {readFileSync} from "node:fs"; +import * as path from "node:path"; // Where to find UnicodeData.txt and Blocks.txt const UCD_DIR = path.join("..", "..", "..", "..", "resources", "standards-data", "unicode-character-database"); diff --git a/developer/src/kmc-package/src/compiler/package-compiler-messages.ts b/developer/src/kmc-package/src/compiler/package-compiler-messages.ts index 5a088be128..d98ff747e0 100644 --- a/developer/src/kmc-package/src/compiler/package-compiler-messages.ts +++ b/developer/src/kmc-package/src/compiler/package-compiler-messages.ts @@ -144,7 +144,7 @@ export class PackageCompilerMessages { `The source file '${def(o.filename)}' should not be included in the package; instead include the compiled result.` ); - // 0x001E was ERROR_InvalidPackageFile, now CommonTypesMessages.Error_InvalidPackageFile + // 0x001E was ERROR_InvalidPackageFile, now DeveloperUtilsMessages.Error_InvalidPackageFile static ERROR_FileRecordIsMissingName = SevError | 0x001F; static Error_FileRecordIsMissingName = (o:{description:string}) => m(this.ERROR_FileRecordIsMissingName, @@ -191,7 +191,7 @@ export class PackageCompilerMessages { `The format for version numbers should be number[.number[.number]]. Each number component should be an integer, without leading zeroes.` ); - + static ERROR_PackageMustNotContainItself = SevError | 0x0028; static Error_PackageMustNotContainItself = (o:{outputFilename: string}) => m( this.ERROR_PackageMustNotContainItself, `The package may not include a .kmp file of the same name '${def(o.outputFilename)}'.`, ` diff --git a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts index 4ae9f8d9ce..249261cd0d 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts @@ -8,7 +8,7 @@ import { buildActivities, buildKeyboardInfoActivity, buildModelInfoActivity } fr import { InfrastructureMessages } from '../../messages/infrastructureMessages.js'; import { loadProject } from '../../util/projectLoader.js'; import { ExtendedCompilerOptions } from '../../util/extendedCompilerOptions.js'; -import { getOption } from '@keymanapp/developer-utils'; +import { getOption } from '../../util/options.js'; export class BuildProject extends BuildActivity { public get name(): string { return 'Project'; } diff --git a/developer/src/kmc/src/kmc.ts b/developer/src/kmc/src/kmc.ts index f4c28aa68c..d569621163 100644 --- a/developer/src/kmc/src/kmc.ts +++ b/developer/src/kmc/src/kmc.ts @@ -6,7 +6,8 @@ import { Command, Option } from 'commander'; import { declareBuild } from './commands/build.js'; import { declareAnalyze } from './commands/analyze.js'; -import { KeymanSentry, loadOptions } from '@keymanapp/developer-utils'; +import { loadOptions } from './util/options.js'; +import { KeymanSentry } from './util/KeymanSentry.js'; import KEYMAN_VERSION from "@keymanapp/keyman-version"; import { TestKeymanSentry } from './util/TestKeymanSentry.js'; import { exitProcess } from './util/sysexits.js'; diff --git a/developer/src/kmc/src/messages/infrastructureMessages.ts b/developer/src/kmc/src/messages/infrastructureMessages.ts index 3e1f4f30c6..d384aa7d86 100644 --- a/developer/src/kmc/src/messages/infrastructureMessages.ts +++ b/developer/src/kmc/src/messages/infrastructureMessages.ts @@ -38,9 +38,7 @@ export class InfrastructureMessages { static Info_FileNotBuiltSuccessfully = (o:{filename:string,relativeFilename:string}) => ({filename:o.filename, ...m(this.INFO_FileNotBuiltSuccessfully, `${def(o.relativeFilename)} failed to build.`)}); - static ERROR_InvalidProjectFile = SevError | 0x0008; - static Error_InvalidProjectFile = (o:{message:string}) => m(this.ERROR_InvalidProjectFile, - `Project file is not valid: ${def(o.message)}`); + // 0x0008: ERROR_InvalidProjectFile moved to DeveloperUtilsMessages static HINT_FilenameHasDifferingCase = SevHint | 0x0009; static Hint_FilenameHasDifferingCase = (o:{reference:string, filename:string}) => m(this.HINT_FilenameHasDifferingCase, @@ -84,9 +82,7 @@ export class InfrastructureMessages { static Error_InvalidProjectFolder = (o:{folderName:string}) => m(this.ERROR_InvalidProjectFolder, `The folder ${def(o.folderName)} does not appear to be a Keyman Developer project.`); - static ERROR_UnsupportedProjectVersion = SevError | 0x0013; - static Error_UnsupportedProjectVersion = (o:{version:string}) => m(this.ERROR_UnsupportedProjectVersion, - `Project version ${def(o.version)} is not supported by this version of Keyman Developer.`); + // 0x0013: ERROR_UnsupportedProjectVersion moved to developer-utils-messages static HINT_ProjectIsVersion10 = SevHint | 0x0014; static Hint_ProjectIsVersion10 = () => m(this.HINT_ProjectIsVersion10, diff --git a/developer/src/kmc/src/messages/messageNamespaces.ts b/developer/src/kmc/src/messages/messageNamespaces.ts index 3918d7183d..792acffd19 100644 --- a/developer/src/kmc/src/messages/messageNamespaces.ts +++ b/developer/src/kmc/src/messages/messageNamespaces.ts @@ -1,4 +1,4 @@ -import { CommonTypesMessages, CompilerErrorNamespace } from '@keymanapp/developer-utils'; +import { DeveloperUtilsMessages, CompilerErrorNamespace } from '@keymanapp/developer-utils'; import { AnalyzerMessages } from '@keymanapp/kmc-analyze'; import { CopierMessages } from '@keymanapp/kmc-copy'; import { GeneratorMessages } from '@keymanapp/kmc-generate'; @@ -13,7 +13,7 @@ import { InfrastructureMessages } from './infrastructureMessages.js'; // Maps every compiler error namespace to the corresponding implementation const messageNamespaces: Record = { [CompilerErrorNamespace.LdmlKeyboardCompiler]: LdmlCompilerMessages, - [CompilerErrorNamespace.CommonTypes]: CommonTypesMessages, + [CompilerErrorNamespace.DeveloperUtils]: DeveloperUtilsMessages, [CompilerErrorNamespace.KmnCompiler]: KmnCompilerMessages, [CompilerErrorNamespace.ModelCompiler]: ModelCompilerMessages, [CompilerErrorNamespace.PackageCompiler]: PackageCompilerMessages, @@ -38,7 +38,7 @@ export type CompilerMessageSource = { // TODO: consolidate with messageNamespaces above export const messageSources: Record = { [CompilerErrorNamespace.LdmlKeyboardCompiler]: { module: 'kmc-ldml', class: LdmlCompilerMessages }, - [CompilerErrorNamespace.CommonTypes]: { module: 'common-types', class: CommonTypesMessages }, + [CompilerErrorNamespace.DeveloperUtils]: { module: 'developer-utils', class: DeveloperUtilsMessages }, [CompilerErrorNamespace.KmnCompiler]: { module: 'kmc-kmn', class: KmnCompilerMessages }, [CompilerErrorNamespace.ModelCompiler]: { module: 'kmc-model', class: ModelCompilerMessages }, [CompilerErrorNamespace.PackageCompiler]: { module: 'kmc-package', class: PackageCompilerMessages }, diff --git a/developer/src/kmc/src/util/KeymanSentry.ts b/developer/src/kmc/src/util/KeymanSentry.ts new file mode 100644 index 0000000000..9ee05ecb7f --- /dev/null +++ b/developer/src/kmc/src/util/KeymanSentry.ts @@ -0,0 +1,104 @@ +// TODO: this is duplicated in server.js +import Sentry from "@sentry/node"; +import KEYMAN_VERSION from "@keymanapp/keyman-version"; +import { getOption } from "./options.js"; + +export type SentryNodeOptions = Sentry.NodeOptions; + +/** + * Maximum delay on shutdown of process to send pending events + * to Sentry, in msec + */ +const CLOSE_TIMEOUT = 2000; + +let isInit = false; + +export class KeymanSentry { + + static isEnabled() { + if(process.argv.includes('--no-error-reporting')) { + return false; + } + if(process.argv.includes('--error-reporting')) { + return true; + } + + return getOption('automatically report errors', true); + } + + static init(options?: SentryNodeOptions) { + options = options ?? {}; + Sentry.init({ + ...options, + dsn: 'https://39b25a09410349a58fe12aaf721565af@o1005580.ingest.sentry.io/5983519', // Keyman Developer + environment: KEYMAN_VERSION.VERSION_ENVIRONMENT, + release: KEYMAN_VERSION.VERSION_GIT_TAG, + }); + isInit = true; + } + + private static writeSentryMessage(eventId: string) { + process.stderr.write(` + This error has been automatically reported to the Keyman team. + Identifier: ${eventId} + Application: Keyman Developer + Reported at: https://sentry.io/organizations/keyman/projects/keyman-developer/events/${eventId}/ + `); + } + + static async reportException(e: any, silent: boolean = true) { + if(isInit) { + const eventId = await Sentry.captureException(e); + if(!silent) { + this.writeSentryMessage(eventId); + } + return eventId; + } + return null; + } + + static captureMessage(message: string) { + if(isInit) { + return Sentry.captureMessage(message); + } else { + return null; + } + } + + /** + * capture an exception for Sentry; note that in local environments, this will normally + * throw the exception rather than sending to Sentry + * @param e + * @param force if true, reports to Sentry even in local environments + */ + static async captureException(e: any, force?: boolean): Promise { + if(isInit) { + // For local development, we don't want to bury the trace; we need the cast to avoid + // TS2367 (comparison appears to be unintentional) + if(!force && (KEYMAN_VERSION.VERSION_ENVIRONMENT as string) == 'local') { + throw e; + } + + const eventId = Sentry.captureException(e); + process.stderr.write(` + Fatal error: ${(e??'').toString()} + `); + this.writeSentryMessage(eventId); + await this.close(); + + process.exit(1); + } else { + throw e; + } + } + + static async close() { + if(isInit) { + try { + await Sentry.close(CLOSE_TIMEOUT); + } catch(e) { + // ignore any errors here + } + } + } +} \ No newline at end of file diff --git a/developer/src/kmc/src/util/NodeCompilerCallbacks.ts b/developer/src/kmc/src/util/NodeCompilerCallbacks.ts index 94465d4413..98b62a8bad 100644 --- a/developer/src/kmc/src/util/NodeCompilerCallbacks.ts +++ b/developer/src/kmc/src/util/NodeCompilerCallbacks.ts @@ -13,7 +13,7 @@ import { CompilerCallbacks, CompilerEvent, import { InfrastructureMessages } from '../messages/infrastructureMessages.js'; import chalk from 'chalk'; import supportsColor from 'supports-color'; -import { KeymanSentry } from '@keymanapp/developer-utils'; +import { KeymanSentry } from './KeymanSentry.js'; import { fileURLToPath } from 'url'; const color = chalk.default; diff --git a/developer/src/kmc/src/util/TestKeymanSentry.ts b/developer/src/kmc/src/util/TestKeymanSentry.ts index b8572516bd..34e41e64d3 100644 --- a/developer/src/kmc/src/util/TestKeymanSentry.ts +++ b/developer/src/kmc/src/util/TestKeymanSentry.ts @@ -1,6 +1,6 @@ import { KmnCompiler } from "@keymanapp/kmc-kmn"; import { NodeCompilerCallbacks } from "./NodeCompilerCallbacks.js"; -import { KeymanSentry, SentryNodeOptions } from '@keymanapp/developer-utils'; +import { KeymanSentry, SentryNodeOptions } from './KeymanSentry.js'; const cli = process.argv.join(' '); diff --git a/developer/src/kmc/src/util/kmcSentryOptions.ts b/developer/src/kmc/src/util/kmcSentryOptions.ts index c6c47f732f..b0def8be66 100644 --- a/developer/src/kmc/src/util/kmcSentryOptions.ts +++ b/developer/src/kmc/src/util/kmcSentryOptions.ts @@ -1,6 +1,6 @@ import Sentry from "@sentry/node"; import * as process from "node:process"; -import { SentryNodeOptions } from "@keymanapp/developer-utils"; +import { SentryNodeOptions } from "./KeymanSentry.js"; Sentry.setContext('Command Line', { // obfusate parameters with longer paths e.g. from 'c:/users/name/a/b' to diff --git a/developer/src/common/web/utils/src/utils/options.ts b/developer/src/kmc/src/util/options.ts similarity index 100% rename from developer/src/common/web/utils/src/utils/options.ts rename to developer/src/kmc/src/util/options.ts diff --git a/developer/src/kmc/src/util/projectLoader.ts b/developer/src/kmc/src/util/projectLoader.ts index 1cd944397f..b870a9fd68 100644 --- a/developer/src/kmc/src/util/projectLoader.ts +++ b/developer/src/kmc/src/util/projectLoader.ts @@ -2,7 +2,7 @@ import * as path from 'path'; import * as fs from 'fs'; import { KeymanFileTypes } from "@keymanapp/common-types"; -import { CompilerCallbacks, KeymanDeveloperProject, KPJFileReader } from "@keymanapp/developer-utils"; +import { CompilerCallbacks, KeymanDeveloperProject, ProjectLoader } from "@keymanapp/developer-utils"; import { InfrastructureMessages } from "../messages/infrastructureMessages.js"; export const isProject = (filename: string): boolean => @@ -26,7 +26,7 @@ export async function loadProject(infile: string, callbacks: CompilerCallbacks) } const project = callbacks.fs.existsSync(infile) ? - await loadProjectFromFile(infile, callbacks) : + await ProjectLoader.loadProjectFromFile(infile, callbacks) : await loadDefaultProjectFromFolder(infile, callbacks); return project; @@ -42,21 +42,3 @@ async function loadDefaultProjectFromFolder(infile: string, callbacks: CompilerC return project; } -async function loadProjectFromFile(infile: string, callbacks: CompilerCallbacks): Promise { - const kpjData = await callbacks.fsAsync.readFile(infile); - const reader = new KPJFileReader(callbacks); - let kpj = null; - try { - kpj = reader.read(kpjData); - if(kpj.KeymanDeveloperProject?.Options?.Version && kpj.KeymanDeveloperProject.Options.Version != "1.0" && kpj.KeymanDeveloperProject.Options.Version != "2.0") { - callbacks.reportMessage(InfrastructureMessages.Error_UnsupportedProjectVersion({version: kpj.KeymanDeveloperProject.Options.Version})); - return null; - } - reader.validate(kpj); - } catch(e) { - callbacks.reportMessage(InfrastructureMessages.Error_InvalidProjectFile({message: (e??'').toString()})); - return null; - } - const project = await reader.transform(infile, kpj); - return project; -} diff --git a/developer/src/kmc/src/util/sysexits.ts b/developer/src/kmc/src/util/sysexits.ts index b656d538e4..7256dd4e77 100644 --- a/developer/src/kmc/src/util/sysexits.ts +++ b/developer/src/kmc/src/util/sysexits.ts @@ -1,4 +1,4 @@ -import { KeymanSentry } from "@keymanapp/developer-utils"; +import { KeymanSentry } from './KeymanSentry.js'; /** * Exit codes defined in : diff --git a/developer/src/kmc/test/build.tests.ts b/developer/src/kmc/test/build.tests.ts index 5bb382efc3..f8356db54b 100644 --- a/developer/src/kmc/test/build.tests.ts +++ b/developer/src/kmc/test/build.tests.ts @@ -1,5 +1,5 @@ import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; -import { clearOptions } from '@keymanapp/developer-utils'; +import { clearOptions } from '../src/util/options.js'; import { assert } from 'chai'; import 'mocha'; import { BuildProject } from '../src/commands/buildClasses/BuildProject.js'; diff --git a/developer/src/kmc/test/infrastructureMessages.tests.ts b/developer/src/kmc/test/infrastructureMessages.tests.ts index 225742bd92..68edaa7a10 100644 --- a/developer/src/kmc/test/infrastructureMessages.tests.ts +++ b/developer/src/kmc/test/infrastructureMessages.tests.ts @@ -8,9 +8,9 @@ import { NodeCompilerCallbacks } from '../src/util/NodeCompilerCallbacks.js'; import { KeymanFileTypes } from '@keymanapp/common-types'; import { unitTestEndpoints } from '../src/commands/build.js'; import { KmnCompilerMessages } from '@keymanapp/kmc-kmn'; -import { clearOptions } from '@keymanapp/developer-utils'; +import { clearOptions } from '../src/util/options.js'; import { loadProject } from '../src/util/projectLoader.js'; -import { CompilerErrorNamespace, CompilerEvent, defaultCompilerOptions, CompilerOptions} from '@keymanapp/developer-utils'; +import { CompilerErrorNamespace, CompilerEvent, defaultCompilerOptions, CompilerOptions, DeveloperUtilsMessages} from '@keymanapp/developer-utils'; import { analyzeUnitTestEndpoints } from '../src/commands/analyze.js'; import { BuildKeyboardInfo } from '../src/commands/buildClasses/BuildKeyboardInfo.js'; import { BuildModelInfo } from '../src/commands/buildClasses/BuildModelInfo.js'; @@ -71,12 +71,12 @@ describe('InfrastructureMessages', function () { 'ERROR_OutFileNotValidForProjects not generated, instead got: '+JSON.stringify(ncb.messages,null,2)); }); - // ERROR_InvalidProjectFile + // ERROR_InvalidProjectFile - test stays here for now though referencing developer-messages it('should generate ERROR_InvalidProjectFile if a project file is invalid', async function() { const ncb = new NodeCompilerCallbacks({logLevel: 'silent'}); await unitTestEndpoints.build(makePathToFixture('invalid-projects', 'error_invalid_project_file.kpj'), null, ncb, {}); - assert.isTrue(ncb.hasMessage(InfrastructureMessages.ERROR_InvalidProjectFile), + assert.isTrue(ncb.hasMessage(DeveloperUtilsMessages.ERROR_InvalidProjectFile), 'ERROR_InvalidProjectFile not generated, instead got: '+JSON.stringify(ncb.messages,null,2)); }); @@ -202,14 +202,14 @@ describe('InfrastructureMessages', function () { assertMessagesEqual(ncb.messages, expectedMessages); }); - // ERROR_UnsupportedProjectVersion + // ERROR_UnsupportedProjectVersion (test stays here for now though referencing developer-utils) it('should generate ERROR_UnsupportedProjectVersion if a project file with an unsupported version is loaded', async function() { const ncb = new NodeCompilerCallbacks({logLevel: 'silent'}); const filename = makePathToFixture('invalid-projects', 'error_unsupported_project_version.kpj'); const expectedMessages = [ InfrastructureMessages.INFO_BuildingFile, - InfrastructureMessages.ERROR_UnsupportedProjectVersion, + DeveloperUtilsMessages.ERROR_UnsupportedProjectVersion, InfrastructureMessages.INFO_ProjectNotBuiltSuccessfully ]; await unitTestEndpoints.build(filename, null, ncb, {compilerWarningsAsErrors: true}); diff --git a/developer/src/kmc/test/project-build.tests.ts b/developer/src/kmc/test/project-build.tests.ts index 3b10d8857c..f094afb79d 100644 --- a/developer/src/kmc/test/project-build.tests.ts +++ b/developer/src/kmc/test/project-build.tests.ts @@ -4,7 +4,7 @@ import 'mocha'; import { BuildProject } from '../src/commands/buildClasses/BuildProject.js'; import { makePathToFixture } from './helpers/index.js'; import { InfrastructureMessages } from '../src/messages/infrastructureMessages.js'; -import { clearOptions } from '@keymanapp/developer-utils'; +import { clearOptions } from '../src/util/options.js'; const callbacks = new TestCompilerCallbacks(); diff --git a/developer/src/common/web/utils/src/utils/KeymanSentry.ts b/developer/src/server/src/KeymanSentry.ts similarity index 98% rename from developer/src/common/web/utils/src/utils/KeymanSentry.ts rename to developer/src/server/src/KeymanSentry.ts index dfe62bfa3d..9dba30a0d4 100644 --- a/developer/src/common/web/utils/src/utils/KeymanSentry.ts +++ b/developer/src/server/src/KeymanSentry.ts @@ -1,3 +1,4 @@ +// TODO: this is duplicated in kmc! import Sentry from "@sentry/node"; import KEYMAN_VERSION from "@keymanapp/keyman-version"; import { getOption } from "./options.js"; diff --git a/developer/src/server/src/index.ts b/developer/src/server/src/index.ts index 677c5a7cd5..85de2fcce6 100644 --- a/developer/src/server/src/index.ts +++ b/developer/src/server/src/index.ts @@ -5,12 +5,13 @@ import chalk from 'chalk'; import express from 'express'; import multer from 'multer'; import * as ws from 'ws'; -import { KeymanSentry, loadOptions } from '@keymanapp/developer-utils'; +import { KeymanSentry } from './KeymanSentry.js'; import { configuration } from './config.js'; import { environment } from './environment.js'; import setupRoutes from './routes.js'; import { shutdown } from './shutdown.js'; import { initTray } from './tray.js'; +import { loadOptions } from './options.js'; const options = { ngrokLog: false, // Set this to true if you need to see ngrok logs in the console diff --git a/developer/src/server/src/load-json-file.ts b/developer/src/server/src/load-json-file.ts index 28916cd83b..174a22a650 100644 --- a/developer/src/server/src/load-json-file.ts +++ b/developer/src/server/src/load-json-file.ts @@ -1,4 +1,4 @@ -import { KeymanSentry } from '@keymanapp/developer-utils'; +import { KeymanSentry } from './KeymanSentry.js'; import { readFileSync, existsSync } from 'fs'; export function loadJsonFile(filename: string): any { diff --git a/developer/src/server/src/options.ts b/developer/src/server/src/options.ts new file mode 100644 index 0000000000..45799ba5f2 --- /dev/null +++ b/developer/src/server/src/options.ts @@ -0,0 +1,96 @@ +// TODO: this is duplicated in kmc +import * as os from 'node:os'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +export interface KeymanDeveloperOptions { + "use tab char"?: boolean; + "link font sizes"?: boolean; + "indent size"?: number; + "use old debugger"?: boolean; + "editor theme"?: string; + "debugger break when exiting line"?: boolean; + "debugger single step after break"?: boolean; + "debugger show store offset"?: boolean; + "debugger recompile with debug info"?: boolean; + "debugger auto reset before compilng"?: boolean; + "auto save before compiling"?: boolean; + "osk auto save before importing"?: boolean; + "web host port"?: number; + "server keep alive"?: boolean; + "server use local addresses"?: boolean; + "server ngrok token"?: string; + "server ngrok region"?: string; + "server use ngrok"?: boolean; + "server show console window"?: boolean; + "char map disable database lookups"?: boolean; + "char map auto lookup"?: boolean; + "open keyboard files in source view"?: boolean; + "display theme"?: string; + "external editor path"?: string; + "smtp server"?: string; + "test email addresses"?: string; + "web ladder length"?: number; + "default project path"?: string; + "automatically report errors"?: boolean; + "automatically report usage"?: boolean; + "toolbar visible"?: boolean; + "active project"?: string; + "prompt to upgrade projects"?: boolean; +}; + +type KeymanDeveloperOption = keyof KeymanDeveloperOptions; + +// Default has no options set, and unit tests will use the defaults (won't call +// `loadOptions()`) +let options: KeymanDeveloperOptions = {}; + +// We only load the options from disk once on first use +let optionsLoaded = false; + +export async function loadOptions(): Promise { + if(optionsLoaded) { + return options; + } + + options = {}; + try { + const optionsFile = path.join(os.homedir(), '.keymandeveloper', 'options.json'); + if(fs.existsSync(optionsFile)) { + for(let i = 0; i < 5; i++) { + try { + const data = JSON.parse(fs.readFileSync(optionsFile, 'utf-8')); + if(typeof data == 'object') { + options = data; + } + break; + } catch(e) { + if(e?.code == 'EBUSY') { + await new Promise(resolve => setTimeout(resolve, 500)); + } else { + // give up, nothing to report here + break; + } + } + } + } + } catch(e) { + // Nothing to report here, sadly -- because we cannot rely on Sentry at this + // low level. + options = {}; + } + optionsLoaded = true; + return options; +} + +export function getOption(valueName: T, defaultValue: KeymanDeveloperOptions[T]): KeymanDeveloperOptions[T] { + return options[valueName] ?? defaultValue; +} + +/** + * unit tests will clear options before running, for consistency + */ +export function clearOptions() { + options = {}; + optionsLoaded = true; +} \ No newline at end of file diff --git a/developer/src/server/src/shutdown.ts b/developer/src/server/src/shutdown.ts index d33b035bfc..13f0c15349 100644 --- a/developer/src/server/src/shutdown.ts +++ b/developer/src/server/src/shutdown.ts @@ -1,4 +1,4 @@ -import { KeymanSentry } from '@keymanapp/developer-utils'; +import { KeymanSentry } from './KeymanSentry.js'; export async function shutdown(code?: number): Promise { await KeymanSentry.close(); diff --git a/linux/mcompile/keymap/mc_kmxfile.cpp b/linux/mcompile/keymap/mc_kmxfile.cpp index 674da61fa1..039ae075f1 100644 --- a/linux/mcompile/keymap/mc_kmxfile.cpp +++ b/linux/mcompile/keymap/mc_kmxfile.cpp @@ -346,7 +346,7 @@ LPKMX_KEYBOARD CopyKeyboard(PKMX_BYTE bufp, PKMX_BYTE base) { * @return pointer to the keyboard */ LPKMX_KEYBOARD KMX_FixupKeyboard(PKMX_BYTE bufp, PKMX_BYTE base, KMX_DWORD dwFileSize) { - UNREFERENCED_PARAMETER(dwFileSize); + // UNREFERENCED_PARAMETER(dwFileSize); KMX_DWORD i, j; PCOMP_KEYBOARD ckbp = (PCOMP_KEYBOARD)base; @@ -369,9 +369,9 @@ LPKMX_KEYBOARD KMX_FixupKeyboard(PKMX_BYTE bufp, PKMX_BYTE base, KMX_DWORD dwFil for (gp = kbp->dpGroupArray, cgp = (PCOMP_GROUP)gp, i = 0; i < kbp->cxGroupArray; i++, gp++, cgp++) { gp->dpName = KMX_StringOffset(base, cgp->dpName); gp->dpKeyArray = (LPKMX_KEY)(base + cgp->dpKeyArray); - if (cgp->dpMatch != NULL) + if (cgp->dpMatch != 0) gp->dpMatch = (PKMX_WCHAR)(base + cgp->dpMatch); - if (cgp->dpNoMatch != NULL) + if (cgp->dpNoMatch != 0) gp->dpNoMatch = (PKMX_WCHAR)(base + cgp->dpNoMatch); for (kp = gp->dpKeyArray, ckp = (PCOMP_KEY)kp, j = 0; j < gp->cxKeyArray; j++, kp++, ckp++) { diff --git a/resources/build/version/src/fixupHistory.ts b/resources/build/version/src/fixupHistory.ts index 1d6a290957..884e9c5557 100644 --- a/resources/build/version/src/fixupHistory.ts +++ b/resources/build/version/src/fixupHistory.ts @@ -3,7 +3,7 @@ import { } from '@actions/core'; import { GitHub } from '@actions/github'; -import { readFileSync, writeFileSync } from 'fs'; +import { readFileSync, writeFileSync } from 'node:fs'; import { gt } from 'semver'; import { reportHistory } from './reportHistory.js'; import { spawnChild } from './util/spawnAwait.js'; diff --git a/resources/build/version/src/incrementVersion.ts b/resources/build/version/src/incrementVersion.ts index 6183b7f9c4..2d7ea67ab6 100644 --- a/resources/build/version/src/incrementVersion.ts +++ b/resources/build/version/src/incrementVersion.ts @@ -1,11 +1,11 @@ -import { readFileSync, writeFileSync } from 'fs'; +import { readFileSync, writeFileSync } from 'node:fs'; // ------------------------------------------------------------------------------------ // incrementVersion // ------------------------------------------------------------------------------------ export const incrementVersion = () => { - // 1. + // 1. const version = readFileSync('./VERSION.md', 'utf8'); const triplet = version.split('.'); const patch = parseInt(triplet[2], 10) + 1; diff --git a/resources/build/version/src/index.ts b/resources/build/version/src/index.ts index ea0b12e7e7..f1fb2cc6a0 100644 --- a/resources/build/version/src/index.ts +++ b/resources/build/version/src/index.ts @@ -5,7 +5,7 @@ import { sendCommentToPullRequestAndRelatedIssues, fixupHistory } from './fixupH import { incrementVersion } from './incrementVersion.js'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; -import { readFileSync } from 'fs'; +import { readFileSync } from 'node:fs'; import { reportHistory } from './reportHistory.js'; const argv = await yargs(hideBin(process.argv)) diff --git a/web/src/engine/keyboard-storage/src/nodeCloudRequester.ts b/web/src/engine/keyboard-storage/src/nodeCloudRequester.ts index e8461ff20d..334cc570cf 100644 --- a/web/src/engine/keyboard-storage/src/nodeCloudRequester.ts +++ b/web/src/engine/keyboard-storage/src/nodeCloudRequester.ts @@ -7,9 +7,9 @@ import { default as CloudQueryEngine } from './cloud/queryEngine.js'; -import fs from 'fs'; -import https from 'https'; -import vm from 'vm'; +import fs from 'node:fs'; +import https from 'node:https'; +import vm from 'node:vm'; export default class NodeCloudRequester implements CloudRequesterInterface { private static QUERY_SEED = 1; diff --git a/web/src/engine/predictive-text/wordbreakers/tools/data-compiler/index.ts b/web/src/engine/predictive-text/wordbreakers/tools/data-compiler/index.ts index 7feb16e5b1..15ca018fd4 100644 --- a/web/src/engine/predictive-text/wordbreakers/tools/data-compiler/index.ts +++ b/web/src/engine/predictive-text/wordbreakers/tools/data-compiler/index.ts @@ -2,8 +2,8 @@ // Original version found at: https://github.com/eddieantonio/unicode-default-word-boundary/blob/master/libexec/compile-word-break.js -import fs from 'fs'; -import path from 'path'; +import fs from 'node:fs'; +import path from 'node:path'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); diff --git a/web/src/engine/predictive-text/worker-main/src/node/mappedWorker.ts b/web/src/engine/predictive-text/worker-main/src/node/mappedWorker.ts index aafbba5232..d588a19562 100644 --- a/web/src/engine/predictive-text/worker-main/src/node/mappedWorker.ts +++ b/web/src/engine/predictive-text/worker-main/src/node/mappedWorker.ts @@ -3,17 +3,17 @@ /// // Defines types related to Node workers. -import * as worker from 'worker_threads'; -import { Buffer } from 'buffer'; -import { URL } from 'url'; +import * as worker from 'node:worker_threads'; +import { Buffer } from 'node:buffer'; +import { URL } from 'node:url'; /** * Defines mappings from Node Worker signatures to WebWorker signatures */ const nodeWorkerToWebWorkerMappingSource = ` -import { parentPort } from 'worker_threads'; -import fs from 'fs'; -import vm from 'vm'; +import { parentPort } from 'node:worker_threads'; +import fs from 'node:fs'; +import vm from 'node:vm'; function postMessage(...args) { parentPort.postMessage.call(parentPort, args); diff --git a/web/src/tools/building/sourcemap-root/index.ts b/web/src/tools/building/sourcemap-root/index.ts index c99c0c4e5c..9f87f2cae5 100644 --- a/web/src/tools/building/sourcemap-root/index.ts +++ b/web/src/tools/building/sourcemap-root/index.ts @@ -1,7 +1,7 @@ import SourcemapRemapper from "@keymanapp/sourcemap-path-remapper" import convertSourceMap from 'convert-source-map'; // Transforms sourcemaps among various common formats. // Base64, stringified-JSON, end-of-file comment... -import fs from 'fs'; +import fs from 'node:fs'; function displayHelp() { console.log("KeymanWeb's sourcemap-cleansing tool. This tool is designed to produce clean filepaths");