From e24c48c9b7699d60b1ffa7b6874c85762d767d51 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 12 Dec 2022 09:26:14 +0700 Subject: [PATCH 01/10] chore(common): removes at-this-point-unused npm package --- .../sourcemap-path-remapper/package.json | 1 - package-lock.json | 90 ------------------- 2 files changed, 91 deletions(-) diff --git a/common/tools/sourcemap-path-remapper/package.json b/common/tools/sourcemap-path-remapper/package.json index 9823fb22fe..87697810a7 100644 --- a/common/tools/sourcemap-path-remapper/package.json +++ b/common/tools/sourcemap-path-remapper/package.json @@ -27,7 +27,6 @@ "typescript": "^4.5.4" }, "dependencies": { - "combine-source-map": "^0.8.0", "convert-source-map": "^2.0.0" } } diff --git a/package-lock.json b/package-lock.json index 5006ce153c..069ce484a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -210,7 +210,6 @@ "name": "@keymanapp/sourcemap-path-remapper", "license": "MIT", "dependencies": { - "combine-source-map": "^0.8.0", "convert-source-map": "^2.0.0" }, "devDependencies": { @@ -2248,30 +2247,6 @@ "color-support": "bin.js" } }, - "node_modules/combine-source-map": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", - "integrity": "sha512-UlxQ9Vw0b/Bt/KYwCFqdEwsQ1eL8d1gibiFb7lxQJFdvTgc2hIZi6ugsg+kyhzhPV+QEpUiEIwInIAIrgoEkrg==", - "dependencies": { - "convert-source-map": "~1.1.0", - "inline-source-map": "~0.6.0", - "lodash.memoize": "~3.0.3", - "source-map": "~0.5.3" - } - }, - "node_modules/combine-source-map/node_modules/convert-source-map": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "integrity": "sha512-Y8L5rp6jo+g9VEPgvqNfEopjTR4OTYct8lXlS8iVQdmnjDvbdbzYe9rjtFCB9egC86JoNCU61WRY+ScjkZpnIg==" - }, - "node_modules/combine-source-map/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -4005,22 +3980,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/inline-source-map": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", - "integrity": "sha512-0mVWSSbNDvedDWIN4wxLsdPM4a7cIPcpyMxj3QZ406QRwQ6ePGB1YIHxVPjqpcUGbWQ5C+nHTwGNWAGvt7ggVA==", - "dependencies": { - "source-map": "~0.5.3" - } - }, - "node_modules/inline-source-map/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -4621,11 +4580,6 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, - "node_modules/lodash.memoize": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", - "integrity": "sha512-eDn9kqrAmVUC1wmZvlQ6Uhde44n+tXpqPrN8olQJbttgh0oKclk+SF54P47VEGE9CEiMeRwAP8BaM7UHvBkz2A==" - }, "node_modules/lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", @@ -8135,7 +8089,6 @@ "@keymanapp/resources-gosh": "*", "@types/node": "^10.17.21", "chai": "^4.3.4", - "combine-source-map": "^0.8.0", "convert-source-map": "^2.0.0", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", @@ -9356,29 +9309,6 @@ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true }, - "combine-source-map": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", - "integrity": "sha512-UlxQ9Vw0b/Bt/KYwCFqdEwsQ1eL8d1gibiFb7lxQJFdvTgc2hIZi6ugsg+kyhzhPV+QEpUiEIwInIAIrgoEkrg==", - "requires": { - "convert-source-map": "~1.1.0", - "inline-source-map": "~0.6.0", - "lodash.memoize": "~3.0.3", - "source-map": "~0.5.3" - }, - "dependencies": { - "convert-source-map": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "integrity": "sha512-Y8L5rp6jo+g9VEPgvqNfEopjTR4OTYct8lXlS8iVQdmnjDvbdbzYe9rjtFCB9egC86JoNCU61WRY+ScjkZpnIg==" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" - } - } - }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -10626,21 +10556,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "inline-source-map": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", - "integrity": "sha512-0mVWSSbNDvedDWIN4wxLsdPM4a7cIPcpyMxj3QZ406QRwQ6ePGB1YIHxVPjqpcUGbWQ5C+nHTwGNWAGvt7ggVA==", - "requires": { - "source-map": "~0.5.3" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" - } - } - }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -11158,11 +11073,6 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, - "lodash.memoize": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", - "integrity": "sha512-eDn9kqrAmVUC1wmZvlQ6Uhde44n+tXpqPrN8olQJbttgh0oKclk+SF54P47VEGE9CEiMeRwAP8BaM7UHvBkz2A==" - }, "lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", From 587d2c88db1609b14e16c58fe8314da25f4efdad Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 9 Jan 2023 11:56:08 +0700 Subject: [PATCH 02/10] feat(common): basic build script for the sourcemap-path-remapper tool --- common/tools/sourcemap-path-remapper/build.sh | 53 +++++++++++++++++++ .../sourcemap-path-remapper/src/index.ts | 2 - 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100755 common/tools/sourcemap-path-remapper/build.sh diff --git a/common/tools/sourcemap-path-remapper/build.sh b/common/tools/sourcemap-path-remapper/build.sh new file mode 100755 index 0000000000..b5076225a7 --- /dev/null +++ b/common/tools/sourcemap-path-remapper/build.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# +# Compile our sourcemap-path remapping module for use by Web builds, releases, etc. +# +set -eu + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(greadlink -f "${BASH_SOURCE[0]}" 2>/dev/null || readlink -f "${BASH_SOURCE[0]}")" +. "$(dirname "$THIS_SCRIPT")/../../../resources/build/build-utils.sh" +. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +# This script runs from its own folder +cd "$(dirname "$THIS_SCRIPT")" + +################################ Main script ################################ + +# TODO: for predictive-text, we only need :headless, perhaps we should be splitting modules? +# TODO: remove :tools once kmlmc is a dependency for test:module + +builder_describe "Builds a sourcemap manipulation ES module for use in Web-related builds" \ + "clean" \ + "configure" \ + "build" + +builder_describe_outputs \ + configure /node_modules \ + build build/index.js + +builder_parse "$@" + +### CONFIGURE ACTIONS + +if builder_start_action configure; then + verify_npm_setup + builder_finish_action success configure +fi + +### CLEAN ACTIONS + +if builder_start_action clean; then + rm -rf build/ + builder_finish_action success clean +fi + +### BUILD ACTIONS + +if builder_start_action build; then + npm run tsc + + builder_finish_action success build +fi \ No newline at end of file diff --git a/common/tools/sourcemap-path-remapper/src/index.ts b/common/tools/sourcemap-path-remapper/src/index.ts index bfae8a5f19..de54a3499b 100644 --- a/common/tools/sourcemap-path-remapper/src/index.ts +++ b/common/tools/sourcemap-path-remapper/src/index.ts @@ -1,5 +1,3 @@ -// Sourcemap types! - import * as fs from 'fs'; import convertSourcemap from 'convert-source-map'; // Transforms sourcemaps among various common formats. // Base64, stringified-JSON, end-of-file comment... From 2ce3191e79eb5883af1c52432ea7019791236bb1 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 9 Jan 2023 13:45:32 +0700 Subject: [PATCH 03/10] change(web): main web's source cleaner tool now based on new common tool --- web/build.sh | 8 +- .../tools/building/sourcemap-root/build.sh | 7 +- .../tools/building/sourcemap-root/index.ts | 86 +++++++------------ .../building/sourcemap-root/tsconfig.json | 4 +- 4 files changed, 42 insertions(+), 63 deletions(-) diff --git a/web/build.sh b/web/build.sh index ce49bc866d..49aa23c3dd 100755 --- a/web/build.sh +++ b/web/build.sh @@ -166,7 +166,7 @@ minifycmd="$JAVA -jar $minifier --compilation_level WHITESPACE_ONLY $minifier_wa readonly minifier readonly minifycmd -minified_sourcemap_cleaner="build/tools/building/sourcemap-root" +minified_sourcemap_cleaner="build/tools/building/sourcemap-root/index.mjs" # Fails the build if a specified file does not exist. assert_exists ( ) { @@ -183,9 +183,9 @@ assert_exists ( ) { # $5 - additional output wrapper minify ( ) { if [ $# -ge 4 ]; then - cleanerOptions="--suffix $4" + cleanerOptions="--clean --suffix $4" else - cleanerOptions= + cleanerOptions="--clean" fi if [ $# -ge 5 ]; then @@ -209,7 +209,7 @@ minify ( ) { --source_map_location_mapping "$INPUT_DIR|../../.." \ --js "$INPUT" --compilation_level $3 \ --js_output_file "$OUTPUT" --warning_level VERBOSE --output_wrapper "$wrapper -//# sourceMappingURL=$1.map" +//# sourceMappingURL=$INPUT_FILE.map" # Now to clean the source map. assert_exists "$OUTPUT" diff --git a/web/src/tools/building/sourcemap-root/build.sh b/web/src/tools/building/sourcemap-root/build.sh index a3004db5d3..453a1888f8 100755 --- a/web/src/tools/building/sourcemap-root/build.sh +++ b/web/src/tools/building/sourcemap-root/build.sh @@ -18,13 +18,14 @@ cd "$THIS_SCRIPT_PATH" ################################ Main script ################################ builder_describe "Builds the sourcemap-sanitizing script used for Keyman Engine for Web builds" \ + "@../../../../../common/tools/sourcemap-path-remapper" \ "clean" \ "configure" \ "build" \ builder_describe_outputs \ configure /node_modules \ - build index.js + build ../../../../build/tools/sourcemap-root/index.js builder_parse "$@" @@ -43,5 +44,9 @@ fi if builder_start_action build; then npm run tsc -- -b "$THIS_SCRIPT_PATH/tsconfig.json" + # Necessary until the Web project is converted over to ES modules. + # Node defaults to CommonJS format otherwise. + cp ../../../../build/tools/building/sourcemap-root/index.js ../../../../build/tools/building/sourcemap-root/index.mjs + builder_finish_action success build fi diff --git a/web/src/tools/building/sourcemap-root/index.ts b/web/src/tools/building/sourcemap-root/index.ts index ba17a2c4f9..5c362e3b80 100644 --- a/web/src/tools/building/sourcemap-root/index.ts +++ b/web/src/tools/building/sourcemap-root/index.ts @@ -1,12 +1,13 @@ -var fs = require("fs"); +import SourcemapRemapper from "@keymanapp/sourcemap-path-remapper" function displayHelp() { console.log("KeymanWeb's sourcemap-cleansing tool. This tool is designed to produce clean filepaths"); console.log("in the sourcemaps for ease of reference in browser and Sentry, copying the clean paths TS"); console.log("is able to provide, whereas Closure's minification is not."); console.log(""); - console.log("Usage: node sourcemap-root [-s|--suffix ]"); - console.log(" -s|--suffix Appends to the input sourcemap's 'sourceRoot'.") + console.log("Usage: node sourcemap-root [-c|--clean] [-s|--sourceRoot ]"); + console.log(" -c|--clean Runs source-filepath cleaning operations."); + console.log(" -s|--suffix Sets the input sourcemap's 'sourceRoot' to ."); } // By default, any node-based process has two command-line args: @@ -38,85 +39,56 @@ function assert_is_map(filename: string) { } } // Verify the first parameter is a sourcemap filepath. -let sourceFile = process.argv[2]; +// let sourceFile = process.argv[2]; // Used to be necessary, but no longer is. let destFile = process.argv[3]; -assert_is_map(sourceFile); assert_is_map(destFile); // Now to process any additional flags. -var sourceRootSuffix = ""; +var sourceRoot = ""; +let shouldClean = false; // Starting at [5] - optional command-line arguments for(let procArgIndex = 4; procArgIndex < process.argv.length; procArgIndex++) { let flag = process.argv[procArgIndex]; - + if(flag.indexOf('-') != 0) { console.error("Error: Unexpected command-line argument provided: \"" + flag + "\" is not an argument flag") process.exit(1); } switch(flag) { + case "-c": + case "--clean": + shouldClean = true; + break; case "-s": case "--suffix": procArgIndex++; - sourceRootSuffix = process.argv[procArgIndex]; + sourceRoot = process.argv[procArgIndex]; break; default: console.warn("Unrecognized flag found in argument list: \"" + flag + "\""); } } -let sourceContent: string; -let destContent: string; -try { - sourceContent = fs.readFileSync(sourceFile) as string; - destContent = fs.readFileSync(destFile) as string; -} catch(err) { - console.error("Could not read specified input files"); - console.error(""); - console.error(err); - process.exit(1); +let srcMap = SourcemapRemapper.fromFile(destFile); + +if(shouldClean) { + // First pass: remove any closure prepended ../../../ pathing. + srcMap.remapPaths([ + { from: "../../../", to: "" } + ]); + + // Second pass: fix up any mangled source paths + srcMap.remapPaths([ + { from: "common/web/input-processor/build/keyman/", to: "" }, + { from: "common/web/keyboard-processor/build/keyman/", to: "" } + ]); } -let sourceJSON = JSON.parse(sourceContent) as {[key: string]: any}; -let destJSON = JSON.parse(destContent) as {[key: string]: any}; - -// We now have the root JSON contents. Time to go to work! First, the easy parts. -destJSON["file"] = sourceJSON["file"]; -destJSON["sourceRoot"] = sourceJSON["sourceRoot"] + sourceRootSuffix; - -// The hard part: mapping the source file arrays correctly. -// A raw copy of the arrays will not work properly. -let sourceSources = sourceJSON["sources"] as string[]; -let destSources = destJSON["sources"] as string[]; -let finalDestSources: string[] = []; - -/** - * Closure's minification always uses the TS paths as a suffix, prepending relative minification-path info to reuse them. - * So, if we can find the TS path complete within a path in the minified sourcemap, it's a match. - * - * It's key that we preserve the original ordering of the source files from Closure's output sourcemaps, as is done here. - * Otherwise, browsers will incorrectly match the inlined source to any given file, causing a fair bit of confusion. - * This is why we iterate over the destination's defined array; this way ensures identical ordering for the finalized version. - */ -for(let destPath of destSources) { - let matched = false; - - for(let sourcePath of sourceSources) { - if(destPath.indexOf(sourcePath) != -1) { - matched = true; - finalDestSources.push(sourcePath); - break; - } - } - - if(!matched) { - finalDestSources.push(destPath); - } +if(sourceRoot) { + srcMap.sourceRoot = srcMap.sourceRoot ?? "" + sourceRoot; } -destJSON["sources"] = finalDestSources; - -let destOutput = JSON.stringify(destJSON); -fs.writeFileSync(destFile, destOutput); \ No newline at end of file +srcMap.toFile(destFile); \ No newline at end of file diff --git a/web/src/tools/building/sourcemap-root/tsconfig.json b/web/src/tools/building/sourcemap-root/tsconfig.json index e8e4d5e4c6..6324bca910 100644 --- a/web/src/tools/building/sourcemap-root/tsconfig.json +++ b/web/src/tools/building/sourcemap-root/tsconfig.json @@ -1,7 +1,9 @@ { "compilerOptions": { - "allowJs": true, + "allowJs": false, "outDir": "../../../../build/tools/building/sourcemap-root", + "module": "es6", + "moduleResolution": "node", "types": ["node"], "lib": ["es6", "dom"] } From 4b79f6da7603c8f829ee9a4a5847c04c2d26a1a3 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 9 Jan 2023 13:53:55 +0700 Subject: [PATCH 04/10] change(web): param name tweak --- web/build.sh | 2 +- web/src/tools/building/sourcemap-root/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/build.sh b/web/build.sh index 49aa23c3dd..c5dcc4df88 100755 --- a/web/build.sh +++ b/web/build.sh @@ -183,7 +183,7 @@ assert_exists ( ) { # $5 - additional output wrapper minify ( ) { if [ $# -ge 4 ]; then - cleanerOptions="--clean --suffix $4" + cleanerOptions="--clean --sourceRoot $4" else cleanerOptions="--clean" fi diff --git a/web/src/tools/building/sourcemap-root/index.ts b/web/src/tools/building/sourcemap-root/index.ts index 5c362e3b80..efbd267520 100644 --- a/web/src/tools/building/sourcemap-root/index.ts +++ b/web/src/tools/building/sourcemap-root/index.ts @@ -63,7 +63,7 @@ for(let procArgIndex = 4; procArgIndex < process.argv.length; procArgIndex++) { shouldClean = true; break; case "-s": - case "--suffix": + case "--sourceRoot": procArgIndex++; sourceRoot = process.argv[procArgIndex]; break; From 4dc2d68a345790a9fcdf2bcc2888fa98cf2a50bf Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 9 Jan 2023 11:52:04 +0700 Subject: [PATCH 05/10] feat(web): starts implementation of CI publish-s.keyman action --- web/build.sh | 2 +- web/ci.sh | 55 ++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/web/build.sh b/web/build.sh index c5dcc4df88..558fee2799 100755 --- a/web/build.sh +++ b/web/build.sh @@ -306,7 +306,7 @@ copy_sources ( ) { do echo "- $SOURCE/$SOURCE_FOLDER/ => $CONFIG_OUT_PATH/src/$SOURCE_FOLDER/" mkdir -p "$CONFIG_OUT_PATH/src/$SOURCE_FOLDER" - cp -Rf "$SOURCE/$SOURCE_FOLDER/" "$CONFIG_OUT_PATH/src/$SOURCE_FOLDER" + cp -Rf "$SOURCE/$SOURCE_FOLDER/"* "$CONFIG_OUT_PATH/src/$SOURCE_FOLDER/" done echo diff --git a/web/ci.sh b/web/ci.sh index 539d47f79f..05fb8294d0 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -24,10 +24,13 @@ cd "$THIS_SCRIPT_PATH" builder_describe "Defines and implements the CI build steps for Keyman Engine for Web (KMW)." \ "build" \ - "test Runs all unit tests." \ - "post-test Runs post-test cleanup. Should be run even if a prior step fails." \ - "validate-size Runs the build-size comparison check" \ - "--debug Runs this script in local-development mode; reports and tests will be locally logged" + "test Runs all unit tests." \ + "post-test Runs post-test cleanup. Should be run even if a prior step fails." \ + "validate-size Runs the build-size comparison check" \ + "publish-s.keyman Prepares an s.keyman.com PR (intended for release builds)" \ + "publish-downloads Prepares the upload to downloads.keyman.com (intended for release builds)" \ + "--debug Runs this script in local-development mode; reports and tests will be locally logged" \ + "--password=PASSWORD Used to supply passwords needed by certain actions" builder_parse "$@" @@ -83,4 +86,48 @@ if builder_start_action validate-size; then ./src/tools/building/check-build-size.sh $FLAGS builder_finish_action success validate-size +fi + +if builder_start_action publish-s.keyman; then + TIER=`cat ../TIER.md` + BUILD_NUMBER=`cat ../VERSION.md` + S_KEYMAN_COM=../../s.keyman.com + + # First phase: make sure the s.keyman.com repo is locally-available and up to date. + pushd "$S_KEYMAN_COM" + if builder_has_option --password; then + git pull https://keyman-server:$PASSWORD@github.com/keymanapp/s.keyman.com.git master + else + # For testing on a local development machine / a machine with the repo already loaded. + git checkout master + git pull + fi + popd + + # Second phase: copy the artifacts over + + # The main build products are expected to reside at the root of this folder. + BASE_PUBLISH_FOLDER="$S_KEYMAN_COM/kmw/engine/$BUILD_NUMBER" + mkdir "$BASE_PUBLISH_FOLDER" + + cp -Rf build/app/web/release/* "$BASE_PUBLISH_FOLDER" + cp -Rf build/app/ui/release/* "$BASE_PUBLISH_FOLDER" + + # Third phase: tweak the sourcemaps + # TODO: actual sourcemap tweaking. + + # Final phase: build the PR and push it. + cd "$S_KEYMAN_COM" + # git config user.name "Keyman Build Server" + # git config user.email "keyman-server@users.noreply.github.com" + git add "kmw/engine/$BUILD_NUMBER" + # git commit -m "KeymanWeb release $BUILD_NUMBER (automatic)" + # git push https://keyman-server:$PASSWORD@github.com/keymanapp/s.keyman.com.git master + + builder_finish_action success publish-s.keyman +fi + +if builder_start_action publish-downloads; then +# + builder_finish_action success publish-downloads fi \ No newline at end of file From a883ab85f9fabe8c3458d5e1cd8bbd5b279c9bff Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 9 Jan 2023 13:58:21 +0700 Subject: [PATCH 06/10] feat(web): finishes implementation of ci.sh publish-s.keyman action --- web/ci.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/web/ci.sh b/web/ci.sh index 05fb8294d0..2eb0da47a5 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -114,15 +114,18 @@ if builder_start_action publish-s.keyman; then cp -Rf build/app/ui/release/* "$BASE_PUBLISH_FOLDER" # Third phase: tweak the sourcemaps - # TODO: actual sourcemap tweaking. + # We can use an alt-mode of Web's sourcemap-root tool for this. + for sourcemap in "$BASE_PUBLISH_FOLDER/"*.map; do + node build/tools/building/sourcemap-root/index.mjs null "$sourcemap" --sourceRoot "https://s.keyman.com/kmw/engine/$BUILD_NUMBER/src" + done # Final phase: build the PR and push it. cd "$S_KEYMAN_COM" - # git config user.name "Keyman Build Server" - # git config user.email "keyman-server@users.noreply.github.com" + git config user.name "Keyman Build Server" + git config user.email "keyman-server@users.noreply.github.com" git add "kmw/engine/$BUILD_NUMBER" - # git commit -m "KeymanWeb release $BUILD_NUMBER (automatic)" - # git push https://keyman-server:$PASSWORD@github.com/keymanapp/s.keyman.com.git master + git commit -m "KeymanWeb release $BUILD_NUMBER (automatic)" + git push https://keyman-server:$PASSWORD@github.com/keymanapp/s.keyman.com.git master builder_finish_action success publish-s.keyman fi From 1753951da33fdb8caccd929758ad9a05e0028bea Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 10 Jan 2023 09:17:03 +0700 Subject: [PATCH 07/10] feat(web): implements publish-downloads action --- web/ci.sh | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 5 deletions(-) diff --git a/web/ci.sh b/web/ci.sh index 2eb0da47a5..7bb39f164e 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -36,6 +36,10 @@ builder_parse "$@" #### +TIER=`cat ../TIER.md` +BUILD_NUMBER=`cat ../VERSION.md` +S_KEYMAN_COM=../../s.keyman.com + if builder_start_action build; then # Build step: since CI builds start (and should start) from scratch, run the following # three actions: @@ -89,10 +93,6 @@ if builder_start_action validate-size; then fi if builder_start_action publish-s.keyman; then - TIER=`cat ../TIER.md` - BUILD_NUMBER=`cat ../VERSION.md` - S_KEYMAN_COM=../../s.keyman.com - # First phase: make sure the s.keyman.com repo is locally-available and up to date. pushd "$S_KEYMAN_COM" if builder_has_option --password; then @@ -130,7 +130,86 @@ if builder_start_action publish-s.keyman; then builder_finish_action success publish-s.keyman fi +# Note: for now, this command is used to prepare the artifacts used by the download site, but +# NOT to actually UPLOAD them via rsync or to produce related .download_info files. if builder_start_action publish-downloads; then -# + VERSION_BUILD_REGEX="^[0-9]+\.[0-9]+\.([0-9]+)$" + + if [[ $BUILD_NUMBER =~ $VERSION_BUILD_REGEX ]]; then + BUILD_COUNTER="${BASH_REMATCH[1]}" + else + builder_finish_action failure publish-downloads + exit 0 + fi + + UPLOAD_PATH="build/upload/$BUILD_NUMBER" + + # --- First action artifact - the KMW zip file --- + ZIP="$UPLOAD_PATH/keymanweb-$BUILD_NUMBER.zip" + + # RSYNC_HOME should be pre-set environment variables. + # (7Z_HOME is illegal as a variable name in BASH b/c leading digit.) + mkdir -p "$UPLOAD_PATH" + + # Nifty tidbit: https://stackoverflow.com/questions/592620/how-can-i-check-if-a-program-exists-from-a-bash-script + # If we're fine with ensuring that the program is available via path, we can just use that on + # Win machines. The decision was made to continue relying on an environment variable for 7-zip, though. + + COMPRESS_CMD= + COMPRESS_ADD= + + # Marc's preference; use $SEVEN_Z_HOME and have the BAs set up with THAT as an env var. + if [ -n "${SEVEN_Z_HOME+x}" ] &> /dev/null; then + echo "7z command available" + COMPRESS_CMD="$SEVEN_Z_HOME/7z" + COMPRESS_ADD="a -bd -bb0 -r" # add, hide progress, log level 0, recursive + COMPRESS_RENAME="rn" + fi + + if [[ -z "${COMPRESS_CMD}" ]] ; then + if command -v zip &> /dev/null; then + echo "zip command available" + # Note: does not support within-archive renames! + COMPRESS_CMD=zip + COMPRESS_ADD="-r" + else + echo "${COLOR_RED}Fallback approach failed: zip command unavailable${COLOR_RESET}" >&2 + builder_finish_action failure publish-downloads + exit 1 + fi + fi + + pushd build/app/web/release + "${COMPRESS_CMD}" $COMPRESS_ADD ../../../../$ZIP * + cd .. + "${COMPRESS_CMD}" $COMPRESS_ADD ../../../$ZIP debug + popd + + pushd build/app/ui/release + "${COMPRESS_CMD}" $COMPRESS_ADD ../../../../$ZIP * + cd .. + "${COMPRESS_CMD}" $COMPRESS_ADD ../../../$ZIP debug + popd + + # --- Second action artifact - the 'static' folder (hosted user testing on downloads.keyman.com) --- + + echo "" + echo "Building \`static/\` folder for long-term hosting of testing resources..." + STATIC="$UPLOAD_PATH/static" + mkdir -p "$STATIC" + + mkdir -p "$STATIC/build" + cp -rf build/app "$STATIC/build/app" + cp -rf build/engine "$STATIC/build/engine" + cp -rf build/tools "$STATIC/build/tools" + # avoid build/upload, since that's the folder we're building! + + cp -f index.html "$STATIC/index.html" + + mkdir -p "$STATIC/src/tools" + cp -rf src/tools/testing "$STATIC/src/tools/testing" + cp -rf src/test "$STATIC/src/test" + cp -rf src/samples "$STATIC/src/samples" + builder_finish_action success publish-downloads fi \ No newline at end of file From 128f7a5b474c020b8bda6f0175caf0eb22b48a96 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 10 Jan 2023 09:33:26 +0700 Subject: [PATCH 08/10] change(web): build counter unused in current implementation --- web/ci.sh | 9 --------- 1 file changed, 9 deletions(-) diff --git a/web/ci.sh b/web/ci.sh index 7bb39f164e..2d1a8c5fd4 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -133,15 +133,6 @@ fi # Note: for now, this command is used to prepare the artifacts used by the download site, but # NOT to actually UPLOAD them via rsync or to produce related .download_info files. if builder_start_action publish-downloads; then - VERSION_BUILD_REGEX="^[0-9]+\.[0-9]+\.([0-9]+)$" - - if [[ $BUILD_NUMBER =~ $VERSION_BUILD_REGEX ]]; then - BUILD_COUNTER="${BASH_REMATCH[1]}" - else - builder_finish_action failure publish-downloads - exit 0 - fi - UPLOAD_PATH="build/upload/$BUILD_NUMBER" # --- First action artifact - the KMW zip file --- From d46d0c8c247b9438433a2b0642c6f79d4dcf0bac Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 10 Jan 2023 09:45:43 +0700 Subject: [PATCH 09/10] change(web): improved debug path for publish-s.keyman --- web/ci.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/web/ci.sh b/web/ci.sh index 2d1a8c5fd4..3a4e5fcdbb 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -121,11 +121,15 @@ if builder_start_action publish-s.keyman; then # Final phase: build the PR and push it. cd "$S_KEYMAN_COM" - git config user.name "Keyman Build Server" - git config user.email "keyman-server@users.noreply.github.com" + if builder_has_option --password; then + git config user.name "Keyman Build Server" + git config user.email "keyman-server@users.noreply.github.com" + fi git add "kmw/engine/$BUILD_NUMBER" - git commit -m "KeymanWeb release $BUILD_NUMBER (automatic)" - git push https://keyman-server:$PASSWORD@github.com/keymanapp/s.keyman.com.git master + if builder_has_option --password; then + git commit -m "KeymanWeb release $BUILD_NUMBER (automatic)" + git push https://keyman-server:$PASSWORD@github.com/keymanapp/s.keyman.com.git master + fi builder_finish_action success publish-s.keyman fi From 7a7f48bf0c762d044e44ba2525aab57a849ebbf8 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 11 Jan 2023 08:52:41 +0700 Subject: [PATCH 10/10] change(web): modularizes kmwdevice.ts --- web/src/engine/device-detect/kmwdevice.ts | 346 +++++++++--------- .../device-detect/tsconfig.bundled.json | 12 - web/src/engine/device-detect/tsconfig.json | 17 +- .../device-detect/utils/styleConstants.ts | 49 ++- 4 files changed, 204 insertions(+), 220 deletions(-) delete mode 100644 web/src/engine/device-detect/tsconfig.bundled.json diff --git a/web/src/engine/device-detect/kmwdevice.ts b/web/src/engine/device-detect/kmwdevice.ts index 1d55068160..7a596cd25b 100644 --- a/web/src/engine/device-detect/kmwdevice.ts +++ b/web/src/engine/device-detect/kmwdevice.ts @@ -1,203 +1,201 @@ -// Includes version-related functionality -/// +import StyleConstants from 'utils/styleConstants.js'; +import { DeviceSpec, Version } from "@keymanapp/web-utils/build/obj/index.js"; // The Device object definition ------------------------------------------------- -namespace com.keyman { - export class Device { - touchable: boolean; - OS: string; - formFactor: string; - dyPortrait: number; - dyLandscape: number; - version: string; - orientation: string|number; - browser: string; - colorScheme: 'light' | 'dark'; +export class Device { + touchable: boolean; + OS: string; + formFactor: string; + dyPortrait: number; + dyLandscape: number; + version: string; + orientation: string|number; + browser: string; + colorScheme: 'light' | 'dark'; - private detected: boolean = false; - private _styles: utils.StyleConstants; + private detected: boolean = false; + private _styles: StyleConstants; - // Generates a default Device value. - constructor() { - this.touchable = !!('ontouchstart' in window); - this.OS = ''; - this.formFactor='desktop'; - this.dyPortrait=0; - this.dyLandscape=0; - this.version='0'; - this.orientation=window.orientation; - this.browser=''; - } + // Generates a default Device value. + constructor() { + this.touchable = !!('ontouchstart' in window); + this.OS = ''; + this.formFactor='desktop'; + this.dyPortrait=0; + this.dyLandscape=0; + this.version='0'; + this.orientation=window.orientation; + this.browser=''; + } - /** - * Get device horizontal DPI for touch devices, to set actual size of active regions - * Note that the actual physical DPI may be somewhat different. - * - * @return {number} - */ - getDPI(): number { - var t=document.createElement('DIV') ,s=t.style,dpi=96; - if(document.readyState !== 'complete') { - return dpi; - } - - t.id='calculateDPI'; - s.position='absolute'; s.display='block';s.visibility='hidden'; - s.left='10px'; s.top='10px'; s.width='1in'; s.height='10px'; - document.body.appendChild(t); - dpi=(typeof window.devicePixelRatio == 'undefined') ? t.offsetWidth : t.offsetWidth * window.devicePixelRatio; - document.body.removeChild(t); + /** + * Get device horizontal DPI for touch devices, to set actual size of active regions + * Note that the actual physical DPI may be somewhat different. + * + * @return {number} + */ + getDPI(): number { + var t=document.createElement('DIV') ,s=t.style,dpi=96; + if(document.readyState !== 'complete') { return dpi; } - detect() : void { - var possMacSpoof = false; + t.id='calculateDPI'; + s.position='absolute'; s.display='block';s.visibility='hidden'; + s.left='10px'; s.top='10px'; s.width='1in'; s.height='10px'; + document.body.appendChild(t); + dpi=(typeof window.devicePixelRatio == 'undefined') ? t.offsetWidth : t.offsetWidth * window.devicePixelRatio; + document.body.removeChild(t); + return dpi; + } - if(navigator && navigator.userAgent) { - var agent=navigator.userAgent; + detect() : void { + var possMacSpoof = false; - if(agent.indexOf('iPad') >= 0) { - this.OS='iOS'; - this.formFactor='tablet'; - this.dyPortrait=this.dyLandscape=0; - } else if(agent.indexOf('iPhone') >= 0) { - this.OS='iOS'; - this.formFactor='phone'; - this.dyPortrait=this.dyLandscape=25; - } else if(agent.indexOf('Android') >= 0) { - this.OS='Android'; - this.formFactor='phone'; // form factor may be redefined on initialization - this.dyPortrait=75; - this.dyLandscape=25; - try { - var rx=new RegExp("(?:Android\\s+)(\\d+\\.\\d+\\.\\d+)"); - this.version=agent.match(rx)[1]; - } catch(ex) {} - } else if(agent.indexOf('Linux') >= 0) { - this.OS='Linux'; - } else if(agent.indexOf('Macintosh') >= 0) { - // Starting with 13.1, "Macintosh" can reflect iPads (by default) or iPhones - // (by user setting); a new "Request Desktop Website" setting for Safari will - // change the user agent string to match a desktop Mac. - // - // Firefox uses '.' between version components, while Chrome and Safari use - // '_' instead. So, we have to check for both. Yay. - let regex = /Intel Mac OS X (\d+(?:[_\.]\d+)+)/i; - let results = regex.exec(agent); + if(navigator && navigator.userAgent) { + var agent=navigator.userAgent; - // Match result: a version string with components separated by underscores. - if(!results) { - console.warn("KMW could not properly parse the user agent string." - + "A suboptimal keyboard layout may result."); - this.OS='MacOSX'; - } else if(results.length > 1 && results[1]) { - // Convert version string into a usable form. - let versionString = results[1].replace('_', '.'); - let version = new utils.Version(versionString); + if(agent.indexOf('iPad') >= 0) { + this.OS='iOS'; + this.formFactor='tablet'; + this.dyPortrait=this.dyLandscape=0; + } else if(agent.indexOf('iPhone') >= 0) { + this.OS='iOS'; + this.formFactor='phone'; + this.dyPortrait=this.dyLandscape=25; + } else if(agent.indexOf('Android') >= 0) { + this.OS='Android'; + this.formFactor='phone'; // form factor may be redefined on initialization + this.dyPortrait=75; + this.dyLandscape=25; + try { + var rx=new RegExp("(?:Android\\s+)(\\d+\\.\\d+\\.\\d+)"); + this.version=agent.match(rx)[1]; + } catch(ex) {} + } else if(agent.indexOf('Linux') >= 0) { + this.OS='Linux'; + } else if(agent.indexOf('Macintosh') >= 0) { + // Starting with 13.1, "Macintosh" can reflect iPads (by default) or iPhones + // (by user setting); a new "Request Desktop Website" setting for Safari will + // change the user agent string to match a desktop Mac. + // + // Firefox uses '.' between version components, while Chrome and Safari use + // '_' instead. So, we have to check for both. Yay. + let regex = /Intel Mac OS X (\d+(?:[_\.]\d+)+)/i; + let results = regex.exec(agent); - possMacSpoof = utils.Version.MAC_POSSIBLE_IPAD_ALIAS.compareTo(version) <= 0; - this.OS='MacOSX'; - } - } else if(agent.indexOf('Windows NT') >= 0) { - this.OS='Windows'; - if(agent.indexOf('Touch') >= 0) { - this.formFactor='phone'; // will be redefined as tablet if resolution high enough - } + // Match result: a version string with components separated by underscores. + if(!results) { + console.warn("KMW could not properly parse the user agent string." + + "A suboptimal keyboard layout may result."); + this.OS='MacOSX'; + } else if(results.length > 1 && results[1]) { + // Convert version string into a usable form. + let versionString = results[1].replace('_', '.'); + let version = new Version(versionString); - // Windows Phone and Tablet PC - if(typeof (navigator).msMaxTouchPoints == 'number' && (navigator).msMaxTouchPoints > 0) { - this.touchable=true; - } + possMacSpoof = Version.MAC_POSSIBLE_IPAD_ALIAS.compareTo(version) <= 0; + this.OS='MacOSX'; + } + } else if(agent.indexOf('Windows NT') >= 0) { + this.OS='Windows'; + if(agent.indexOf('Touch') >= 0) { + this.formFactor='phone'; // will be redefined as tablet if resolution high enough + } + + // Windows Phone and Tablet PC + if(typeof (navigator).msMaxTouchPoints == 'number' && (navigator).msMaxTouchPoints > 0) { + this.touchable=true; } } + } - // We look at the screen resolution for Android, because we can't tell from - // the user agent string whether or not this is supposed to be a tablet. - // It seems that there are a handful of older phones out there that report a - // higher resolution than 700px*___px, but it is proving hard to test these, - // and the majority have an aspect ratio <= 0.5625 anyway. - // But we trust what iOS tells us for phone vs tablet. + // We look at the screen resolution for Android, because we can't tell from + // the user agent string whether or not this is supposed to be a tablet. + // It seems that there are a handful of older phones out there that report a + // higher resolution than 700px*___px, but it is proving hard to test these, + // and the majority have an aspect ratio <= 0.5625 anyway. + // But we trust what iOS tells us for phone vs tablet. - const dimMin = Math.min(screen.width,screen.height), dimMax = Math.max(screen.width,screen.height); - const aspect = dimMin / dimMax; + const dimMin = Math.min(screen.width,screen.height), dimMax = Math.max(screen.width,screen.height); + const aspect = dimMin / dimMax; - if(this.OS != 'iOS' && - this.formFactor == 'phone' && - ((dimMin >= 600 && aspect > 0.5625) || // 0.5625 -> 1920x1080 is common phone res - (aspect >= 0.625)) // all reported devices with aspect >= 0.625 are tablets per https://screensiz.es/ - ) { - this.formFactor='tablet'; - } + if(this.OS != 'iOS' && + this.formFactor == 'phone' && + ((dimMin >= 600 && aspect > 0.5625) || // 0.5625 -> 1920x1080 is common phone res + (aspect >= 0.625)) // all reported devices with aspect >= 0.625 are tablets per https://screensiz.es/ + ) { + this.formFactor='tablet'; + } - // Test for potential Chrome emulation on Windows or macOS X (used only in next if-check) - let possibleChromeEmulation = navigator.platform == 'Win32' || navigator.platform == 'MacIntel' + // Test for potential Chrome emulation on Windows or macOS X (used only in next if-check) + let possibleChromeEmulation = navigator.platform == 'Win32' || navigator.platform == 'MacIntel' - // alert(sxx+'->'+device.formFactor); - // Check for phony iOS devices (but don't undo for Chrome emulation used during development) - if(this.OS == 'iOS' && !('ongesturestart' in window) && !possibleChromeEmulation) { - this.OS='Android'; - } + // alert(sxx+'->'+device.formFactor); + // Check for phony iOS devices (but don't undo for Chrome emulation used during development) + if(this.OS == 'iOS' && !('ongesturestart' in window) && !possibleChromeEmulation) { + this.OS='Android'; + } - // Determine application or browser - this.browser='web'; - if(this.OS == 'iOS' || this.OS.toLowerCase() == 'macosx') { + // Determine application or browser + this.browser='web'; + if(this.OS == 'iOS' || this.OS.toLowerCase() == 'macosx') { + this.browser='safari'; + } + + var bMatch=/Firefox|Chrome|OPR|Safari|Edge/; + if(bMatch.test(navigator.userAgent)) { + if((navigator.userAgent.indexOf('Firefox') >= 0) && ('onmozorientationchange' in screen)) { + this.browser='firefox'; + } else if(navigator.userAgent.indexOf('OPR') >= 0) { + this.browser='opera'; + } else if(navigator.userAgent.indexOf(' Edge/') >= 0) { + // Edge is too common a word, so test for Edge/ :) + // Must come before Chrome and Safari test because + // Edge pretends to be both + this.browser='edge'; + } else if(navigator.userAgent.indexOf('Chrome') >= 0) { + // This test must come before Safari test because on macOS, + // Chrome also reports "Safari" + this.browser='chrome'; + } else if(navigator.userAgent.indexOf('Safari') >= 0) { this.browser='safari'; } - - var bMatch=/Firefox|Chrome|OPR|Safari|Edge/; - if(bMatch.test(navigator.userAgent)) { - if((navigator.userAgent.indexOf('Firefox') >= 0) && ('onmozorientationchange' in screen)) { - this.browser='firefox'; - } else if(navigator.userAgent.indexOf('OPR') >= 0) { - this.browser='opera'; - } else if(navigator.userAgent.indexOf(' Edge/') >= 0) { - // Edge is too common a word, so test for Edge/ :) - // Must come before Chrome and Safari test because - // Edge pretends to be both - this.browser='edge'; - } else if(navigator.userAgent.indexOf('Chrome') >= 0) { - // This test must come before Safari test because on macOS, - // Chrome also reports "Safari" - this.browser='chrome'; - } else if(navigator.userAgent.indexOf('Safari') >= 0) { - this.browser='safari'; - } - } - - if(possMacSpoof && this.browser == 'safari') { - // Indistinguishable user agent string! We need a different test; fortunately, true macOS - // Safari doesn't support TouchEvents. (Chrome does, though! Hence the filter above.) - if(window['TouchEvent']) { - this.OS='iOS'; - this.formFactor='tablet'; - this.dyPortrait=this.dyLandscape=0; - - // It's currently impossible to differentiate between iPhone and iPad here - // except for by screen dimensions. - let aspectRatio = screen.height / screen.width; - if(aspectRatio < 1) { - aspectRatio = 1 / aspectRatio; - } - - // iPhones usually have a ratio of 16:9 (or 1.778) or higher, while iPads use 4:3 (or 1.333) - if(aspectRatio > 1.6) { - // Override - we'll treat this device as an iPhone. - this.formFactor = 'phone'; - this.dyPortrait=this.dyLandscape=25; - } - } - } - - this.colorScheme = utils.StyleConstants.prefersDarkMode() ? 'dark' : 'light'; - this.detected = true; } - /** - * Returns a slimmer, web-core compatible version of this object. - */ - public get coreSpec(): utils.DeviceSpec { - return new utils.DeviceSpec(this.browser, this.formFactor, this.OS, this.touchable); + if(possMacSpoof && this.browser == 'safari') { + // Indistinguishable user agent string! We need a different test; fortunately, true macOS + // Safari doesn't support TouchEvents. (Chrome does, though! Hence the filter above.) + if(window['TouchEvent']) { + this.OS='iOS'; + this.formFactor='tablet'; + this.dyPortrait=this.dyLandscape=0; + + // It's currently impossible to differentiate between iPhone and iPad here + // except for by screen dimensions. + let aspectRatio = screen.height / screen.width; + if(aspectRatio < 1) { + aspectRatio = 1 / aspectRatio; + } + + // iPhones usually have a ratio of 16:9 (or 1.778) or higher, while iPads use 4:3 (or 1.333) + if(aspectRatio > 1.6) { + // Override - we'll treat this device as an iPhone. + this.formFactor = 'phone'; + this.dyPortrait=this.dyLandscape=25; + } + } } + + this.colorScheme = StyleConstants.prefersDarkMode() ? 'dark' : 'light'; + this.detected = true; + } + + /** + * Returns a slimmer, web-core compatible version of this object. + */ + public get coreSpec(): DeviceSpec { + return new DeviceSpec(this.browser, this.formFactor, this.OS, this.touchable); } } \ No newline at end of file diff --git a/web/src/engine/device-detect/tsconfig.bundled.json b/web/src/engine/device-detect/tsconfig.bundled.json deleted file mode 100644 index 61ac455342..0000000000 --- a/web/src/engine/device-detect/tsconfig.bundled.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "./tsconfig.json", - - "compilerOptions": { - "outFile": "../../../build/engine/device-detect/obj/index.bundled.js", - }, - - "references": [ - { "path": "../../../../common/web/keyman-version", "prepend": true }, - { "path": "../../../../common/web/utils", "prepend": true } - ] -} diff --git a/web/src/engine/device-detect/tsconfig.json b/web/src/engine/device-detect/tsconfig.json index 471f6f9b90..b63fa2cc79 100644 --- a/web/src/engine/device-detect/tsconfig.json +++ b/web/src/engine/device-detect/tsconfig.json @@ -2,18 +2,19 @@ "extends": "../../../../tsconfig-base.json", "compilerOptions": { - "allowJs": true, + "allowJs": false, "inlineSources": true, - "module": "none", - "outFile": "../../../build/engine/device-detect/obj/index.js", + "module": "es6", + "moduleResolution": "Node", "sourceMap": true, - "target": "es5" + "target": "es5", + "baseUrl": "./", + "outDir": "../../../build/engine/device-detect/obj/", + "tsBuildInfoFile": "../../../build/engine/device-detect/obj/tsconfig.tsbuildinfo", + "rootDir": "." }, - "files": [ - "kmwdevice.ts", - "utils/styleConstants.ts", // goes with kmwdevice.ts - ], + "include": [ "**/*.ts" ], "references": [ { "path": "../../../../common/web/keyman-version" }, diff --git a/web/src/engine/device-detect/utils/styleConstants.ts b/web/src/engine/device-detect/utils/styleConstants.ts index 0ea2e3be22..90aa2fe307 100644 --- a/web/src/engine/device-detect/utils/styleConstants.ts +++ b/web/src/engine/device-detect/utils/styleConstants.ts @@ -1,34 +1,31 @@ -// Includes Device definitions, which may play a role in constant logic. -/// +import { DeviceSpec } from "@keymanapp/web-utils/build/obj/index.js"; /* * This file is intended for CSS-styling constants that see use with the OSK. */ -namespace com.keyman.utils { - /** - * Defines device-level constants used for CSS styling. - */ - export class StyleConstants { - constructor(device: com.keyman.utils.DeviceSpec) { - // popupCanvasBackgroundColor - if(device.OS == utils.OperatingSystem.Android) { - this.popupCanvasBackgroundColor = '#999'; - } else { - this.popupCanvasBackgroundColor = StyleConstants.prefersDarkMode() ? '#0f1319' : '#ffffff'; - } +/** + * Defines device-level constants used for CSS styling. + */ +export default class StyleConstants { + constructor(device: DeviceSpec) { + // popupCanvasBackgroundColor + if(device.OS == DeviceSpec.OperatingSystem.Android) { + this.popupCanvasBackgroundColor = '#999'; + } else { + this.popupCanvasBackgroundColor = StyleConstants.prefersDarkMode() ? '#0f1319' : '#ffffff'; } - - /** - * Checks is a user's browser is in dark mode, if the feature is supported. Returns false otherwise. - * - * Thanks to https://stackoverflow.com/a/57795518 for this code. - */ - static prefersDarkMode(): boolean { - // Ensure the detector exists (otherwise, returns false) - return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; - } - - public readonly popupCanvasBackgroundColor: string; } + + /** + * Checks is a user's browser is in dark mode, if the feature is supported. Returns false otherwise. + * + * Thanks to https://stackoverflow.com/a/57795518 for this code. + */ + static prefersDarkMode(): boolean { + // Ensure the detector exists (otherwise, returns false) + return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + public readonly popupCanvasBackgroundColor: string; } \ No newline at end of file