From 225877f57a5bfc0f310a32fe5e7cc34f26ec97cc Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 5 Oct 2023 13:54:40 +0700 Subject: [PATCH 1/2] feat(common): precompile json schemas to improve performance Fixes #9640. This ended up being somewhat more fragile than originally anticipated. For the `ajv` cli call to get the right versions, we needed to install ajv as a dev dependency in the top-level package.json as well as at the common/web/types level. Ajv does not do all that well with ESM yet, either, so we use esbuild to transform the compiled validators before building in typescript. --- common/web/types/.eslintrc.cjs | 2 + common/web/types/.gitignore | 3 +- common/web/types/build.sh | 21 +- common/web/types/package.json | 6 +- .../keyman-touch-layout-file-reader.ts | 9 +- common/web/types/src/kpj/kpj-file-reader.ts | 12 +- common/web/types/src/kvk/kvks-file-reader.ts | 9 +- .../ldml-keyboard/ldml-keyboard-xml-reader.ts | 9 +- common/web/types/src/main.ts | 3 +- common/web/types/src/osk/osk.ts | 9 +- common/web/types/src/schema-validators.ts | 23 ++ common/web/types/test/helpers/index.ts | 4 +- common/web/types/test/tsconfig.json | 2 +- common/web/types/tools/formats.cjs | 10 + common/web/types/tools/schema-bundler.js | 27 ++ common/web/types/tsconfig.json | 6 +- common/web/utils/package.json | 4 +- developer/src/kmc-keyboard-info/build.sh | 1 + developer/src/kmc-keyboard-info/package.json | 4 +- developer/src/kmc-keyboard-info/src/index.ts | 18 +- developer/src/kmc-ldml/package.json | 1 - developer/src/kmc-ldml/tsconfig.json | 1 - package-lock.json | 264 ++++++++++++------ package.json | 3 + tsconfig.esm-base.json | 1 + 25 files changed, 296 insertions(+), 156 deletions(-) create mode 100644 common/web/types/src/schema-validators.ts create mode 100644 common/web/types/tools/formats.cjs create mode 100644 common/web/types/tools/schema-bundler.js diff --git a/common/web/types/.eslintrc.cjs b/common/web/types/.eslintrc.cjs index 21618ea66ff..98db7f812e5 100644 --- a/common/web/types/.eslintrc.cjs +++ b/common/web/types/.eslintrc.cjs @@ -8,6 +8,8 @@ module.exports = { "coverage/*", "node_modules/*", "test/fixtures/*", + "tools/*", + "src/schemas/*" ], overrides: [ { diff --git a/common/web/types/.gitignore b/common/web/types/.gitignore index 35512aa7852..2f943a2f4e9 100644 --- a/common/web/types/.gitignore +++ b/common/web/types/.gitignore @@ -1 +1,2 @@ -src/schemas/ \ No newline at end of file +src/schemas/ +obj/ \ No newline at end of file diff --git a/common/web/types/build.sh b/common/web/types/build.sh index 560ade06ed2..85c448024ed 100755 --- a/common/web/types/build.sh +++ b/common/web/types/build.sh @@ -40,16 +40,35 @@ function compile_schemas() { "$KEYMAN_ROOT/common/schemas/keyboard_info/keyboard_info.schema.json" ) + rm -rf "$THIS_SCRIPT_PATH/obj/schemas" + mkdir -p "$THIS_SCRIPT_PATH/obj/schemas" rm -rf "$THIS_SCRIPT_PATH/src/schemas" mkdir -p "$THIS_SCRIPT_PATH/src/schemas" cp "${schemas[@]}" "$THIS_SCRIPT_PATH/src/schemas/" # TODO: use https://github.com/tc39/proposal-json-modules instead of this once it stablises for schema in "${schemas[@]}"; do - local fn="$THIS_SCRIPT_PATH/src/schemas/$(basename "$schema" .json)" + local schema_base="$(basename "$schema" .json)" + local fn="$THIS_SCRIPT_PATH/src/schemas/$schema_base" + local out="$THIS_SCRIPT_PATH/obj/schemas/$schema_base.validator.cjs" + + # emit a .ts wrapper for the schema file + + builder_echo "Compiling schema $schema_base.json" echo 'export default ' > "$fn.ts" cat "$fn.json" >> "$fn.ts" + + # emit a compiled validator for the schema file + + # While would seem obvious to just run 'ajv' directly here, somewhere node + # is picking up the wrong path for the build and breaking the formats + # imports. So it is essential to use `npm run` at this point, even though it + # is painfully slower, at least until we figure out the path discrepancy. + npm run build:schema -- -c ./tools/formats.cjs -s "$fn.json" --strict-types false -o "$out" done + + # the validators now need to be compiled to esm + node tools/schema-bundler.js } function copy_cldr_imports() { diff --git a/common/web/types/package.json b/common/web/types/package.json index 83a403aeedc..fd59cffa97d 100644 --- a/common/web/types/package.json +++ b/common/web/types/package.json @@ -16,6 +16,7 @@ ], "scripts": { "build": "tsc -b", + "build:schema": "ajv compile", "lint": "eslint .", "test": "npm run lint && cd test && tsc -b && cd .. && c8 --skip-full --reporter=lcov --reporter=text mocha", "prepublishOnly": "npm run build" @@ -27,7 +28,6 @@ }, "dependencies": { "@keymanapp/keyman-version": "*", - "ajv": "^8.11.0", "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#7a188a1e26f8f36a175d95b67ffece8702363dfc", "semver": "^7.5.2", "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" @@ -39,6 +39,9 @@ "@types/node": "^20.4.1", "@types/semver": "^7.3.12", "@types/xml2js": "^0.4.5", + "ajv": "^8.12.0", + "ajv-cli": "^5.0.0", + "ajv-formats": "^2.1.1", "c8": "^7.12.0", "chai": "^4.3.4", "chalk": "^2.4.2", @@ -74,6 +77,7 @@ "src/ldml-keyboard/unicodeset-parser-api.ts", "src/keyman-touch-layout/keyman-touch-layout-file-writer.ts", "src/osk/osk.ts", + "src/schemas/*", "test/" ] } diff --git a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-reader.ts b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-reader.ts index f2dc0a5c7d9..bb5d8c2df17 100644 --- a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-reader.ts +++ b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-reader.ts @@ -1,7 +1,5 @@ -import { default as AjvModule } from 'ajv'; -const Ajv = AjvModule.default; // The actual expected Ajv type. import { TouchLayoutFile } from "./keyman-touch-layout-file.js"; -import Schemas from '../../src/schemas.js'; +import SchemaValidators from '../schema-validators.js'; export class TouchLayoutFileReader { public read(source: Uint8Array): TouchLayoutFile { @@ -69,11 +67,10 @@ export class TouchLayoutFileReader { } public validate(source: TouchLayoutFile): void { - const ajv = new Ajv(); - if(!ajv.validate(Schemas.touchLayoutClean, source)) + if(!SchemaValidators.touchLayoutClean(source)) /* c8 ignore next 3 */ { - throw new Error(ajv.errorsText()); + throw new Error((SchemaValidators.touchLayoutClean).errors); } } diff --git a/common/web/types/src/kpj/kpj-file-reader.ts b/common/web/types/src/kpj/kpj-file-reader.ts index 5472d5c8d24..cffa2069c64 100644 --- a/common/web/types/src/kpj/kpj-file-reader.ts +++ b/common/web/types/src/kpj/kpj-file-reader.ts @@ -1,11 +1,9 @@ import * as xml2js from 'xml2js'; import { KPJFile, KPJFileProject } from './kpj-file.js'; -import { default as AjvModule } from 'ajv'; -const Ajv = AjvModule.default; // The actual expected Ajv type. import { boxXmlArray } from '../util/util.js'; import { KeymanDeveloperProject, KeymanDeveloperProjectFile10, KeymanDeveloperProjectType } from './keyman-developer-project.js'; import { CompilerCallbacks } from '../util/compiler-interfaces.js'; -import Schemas from '../schemas.js'; +import SchemaValidators from '../schema-validators.js'; export class KPJFileReader { constructor(private callbacks: CompilerCallbacks) { @@ -35,13 +33,11 @@ export class KPJFileReader { } public validate(source: KPJFile): void { - const ajv = new Ajv(); - if(!ajv.validate(Schemas.kpj, source)) { - const ajvLegacy = new Ajv(); - if(!ajvLegacy.validate(Schemas.kpj90, source)) { + if(!SchemaValidators.kpj(source)) { + if(!SchemaValidators.kpj90(source)) { // If the legacy schema also does not validate, then we will only report // the errors against the modern schema - throw new Error(ajv.errorsText()); + throw new Error((SchemaValidators.kpj).errors); } } } diff --git a/common/web/types/src/kvk/kvks-file-reader.ts b/common/web/types/src/kvk/kvks-file-reader.ts index c9808d0b934..70967df0538 100644 --- a/common/web/types/src/kvk/kvks-file-reader.ts +++ b/common/web/types/src/kvk/kvks-file-reader.ts @@ -1,12 +1,10 @@ import * as xml2js from 'xml2js'; import KVKSourceFile from './kvks-file.js'; -import { default as AjvModule } from 'ajv'; -const Ajv = AjvModule.default; // The actual expected Ajv type. import { boxXmlArray } from '../util/util.js'; import { DEFAULT_KVK_FONT, VisualKeyboard, VisualKeyboardHeaderFlags, VisualKeyboardKey, VisualKeyboardKeyFlags, VisualKeyboardLegalShiftStates, VisualKeyboardShiftState } from './visual-keyboard.js'; import { USVirtualKeyCodes } from '../consts/virtual-key-constants.js'; import { BUILDER_KVK_HEADER_VERSION, KVK_HEADER_IDENTIFIER_BYTES } from './kvk-file.js'; -import Schemas from '../schemas.js'; +import SchemaValidators from '../schema-validators.js'; export default class KVKSFileReader { @@ -85,9 +83,8 @@ export default class KVKSFileReader { } public validate(source: KVKSourceFile): void { - const ajv = new Ajv(); - if(!ajv.validate(Schemas.kvks, source)) { - throw new Error(ajv.errorsText()); + if(!SchemaValidators.kvks(source)) { + throw new Error((SchemaValidators.kvks).errorsText()); } } diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index 3fdd9a74025..aaf46262c5d 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -1,13 +1,11 @@ import * as xml2js from 'xml2js'; import { LDMLKeyboardXMLSourceFile, LKImport } from './ldml-keyboard-xml.js'; -import { default as AjvModule } from 'ajv'; -const Ajv = AjvModule.default; // The actual expected Ajv type. import { boxXmlArray } from '../util/util.js'; import { CompilerCallbacks } from '../util/compiler-interfaces.js'; import { constants } from '@keymanapp/ldml-keyboard-constants'; import { CommonTypesMessages } from '../util/common-events.js'; import { LDMLKeyboardTestDataXMLSourceFile, LKTTest, LKTTests } from './ldml-keyboard-testdata-xml.js'; -import Schemas from '../schemas.js'; +import SchemaValidators from '../schema-validators.js'; interface NameAndProps { '$'?: any; // content @@ -207,9 +205,8 @@ export class LDMLKeyboardXMLSourceFileReader { * @returns true if valid, false if invalid */ public validate(source: LDMLKeyboardXMLSourceFile | LDMLKeyboardTestDataXMLSourceFile): boolean { - const ajv = new Ajv(); - if(!ajv.validate(Schemas.ldmlKeyboard3, source)) { - for (let err of ajv.errors) { + if(!SchemaValidators.ldmlKeyboard3(source)) { + for (let err of (SchemaValidators.ldmlKeyboard3).errors) { this.callbacks.reportMessage(CommonTypesMessages.Error_SchemaValidationError({ instancePath: err.instancePath, keyword: err.keyword, diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index a814903ab75..108a2d57c29 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -47,4 +47,5 @@ export * as KeymanFileTypes from './util/file-types.js'; export * as Osk from './osk/osk.js'; -export * as Schemas from './schemas.js'; \ No newline at end of file +export * as Schemas from './schemas.js'; +export * as SchemaValidators from './schema-validators.js'; \ No newline at end of file diff --git a/common/web/types/src/osk/osk.ts b/common/web/types/src/osk/osk.ts index 620712baeb2..9c7d1bf04c4 100644 --- a/common/web/types/src/osk/osk.ts +++ b/common/web/types/src/osk/osk.ts @@ -1,8 +1,6 @@ import { TouchLayoutFile, TouchLayoutFlick, TouchLayoutKey, TouchLayoutPlatform, TouchLayoutSubKey } from "src/keyman-touch-layout/keyman-touch-layout-file.js"; import { VisualKeyboard } from "../kvk/visual-keyboard.js"; -import { default as AjvModule } from 'ajv'; -import Schemas from "../schemas.js"; -const Ajv = AjvModule.default; // The actual expected Ajv type. +import SchemaValidators from "../schema-validators.js"; export interface StringRefUsage { filename: string; @@ -24,11 +22,10 @@ export interface StringResult { export type PuaMap = {[index:string]: string}; export function parseMapping(mapping: any) { - const ajv = new Ajv(); - if(!ajv.validate(Schemas.displayMap, mapping)) + if(!SchemaValidators.displayMap(mapping)) /* c8 ignore next 3 */ { - throw new Error(ajv.errorsText()); + throw new Error((SchemaValidators.displayMap).errorsText()); } let map: PuaMap = {}; diff --git a/common/web/types/src/schema-validators.ts b/common/web/types/src/schema-validators.ts new file mode 100644 index 00000000000..340517b188d --- /dev/null +++ b/common/web/types/src/schema-validators.ts @@ -0,0 +1,23 @@ +import kpj from './schemas/kpj.schema.validator.mjs'; +import kpj90 from './schemas/kpj-9.0.schema.validator.mjs'; +import kvks from './schemas/kvks.schema.validator.mjs'; +import ldmlKeyboard3 from './schemas/ldml-keyboard3.schema.validator.mjs'; +import ldmlKeyboardTest3 from './schemas/ldml-keyboardtest3.schema.validator.mjs'; +import displayMap from './schemas/displaymap.schema.validator.mjs'; +import touchLayoutClean from './schemas/keyman-touch-layout.clean.spec.validator.mjs'; +import touchLayout from './schemas/keyman-touch-layout.spec.validator.mjs'; +import keyboard_info from './schemas/keyboard_info.schema.validator.mjs'; + +const SchemaValidators = { + kpj, + kpj90, + kvks, + ldmlKeyboard3, + ldmlKeyboardTest3, + displayMap, + touchLayoutClean, + touchLayout, + keyboard_info, +}; + +export default SchemaValidators; diff --git a/common/web/types/test/helpers/index.ts b/common/web/types/test/helpers/index.ts index e92b9656bfa..9b2be1d6056 100644 --- a/common/web/types/test/helpers/index.ts +++ b/common/web/types/test/helpers/index.ts @@ -1,5 +1,5 @@ -import path from "path"; -import fs from "fs"; +import * as path from "path"; +import * as fs from "fs"; import { fileURLToPath } from "url"; /** diff --git a/common/web/types/test/tsconfig.json b/common/web/types/test/tsconfig.json index c522129c99a..a59923bf9a6 100644 --- a/common/web/types/test/tsconfig.json +++ b/common/web/types/test/tsconfig.json @@ -7,7 +7,7 @@ "outDir": "../build/test", "baseUrl": ".", "strictNullChecks": false, // TODO: get rid of this as some point - "allowSyntheticDefaultImports": true // for ajv + "allowSyntheticDefaultImports": true }, "include": [ "**/test-*.ts", diff --git a/common/web/types/tools/formats.cjs b/common/web/types/tools/formats.cjs new file mode 100644 index 00000000000..82eca97ab2f --- /dev/null +++ b/common/web/types/tools/formats.cjs @@ -0,0 +1,10 @@ +/* + * This somewhat peculiar function is used in `build.sh configure` when + * precompiling the validators and makes it possible to use the extended formats + * in ajv-formats. + */ +function formats(ajv) { + require("ajv-formats")(ajv); +} + +module.exports = formats; diff --git a/common/web/types/tools/schema-bundler.js b/common/web/types/tools/schema-bundler.js new file mode 100644 index 00000000000..995befaec1f --- /dev/null +++ b/common/web/types/tools/schema-bundler.js @@ -0,0 +1,27 @@ +/* + * Bundle schema validation files (from .cjs) and make them available as ES modules + */ + +import esbuild from 'esbuild'; + +await esbuild.build({ + entryPoints: [ + 'obj/schemas/kpj.schema.validator.cjs', + 'obj/schemas/kpj-9.0.schema.validator.cjs', + 'obj/schemas/kvks.schema.validator.cjs', + 'obj/schemas/ldml-keyboard3.schema.validator.cjs', + 'obj/schemas/ldml-keyboardtest3.schema.validator.cjs', + 'obj/schemas/displaymap.schema.validator.cjs', + 'obj/schemas/keyman-touch-layout.clean.spec.validator.cjs', + 'obj/schemas/keyman-touch-layout.spec.validator.cjs', + 'obj/schemas/keyboard_info.schema.validator.cjs', + ], + bundle: true, + format: 'esm', + target: 'es2022', + outdir: 'src/schemas/', + sourcemap: false, + + // We want a .mjs extension to force node into ESM module mode + outExtension: { '.js': '.mjs' }, +}); diff --git a/common/web/types/tsconfig.json b/common/web/types/tsconfig.json index 5059030c0f8..ec2873ed5e0 100644 --- a/common/web/types/tsconfig.json +++ b/common/web/types/tsconfig.json @@ -5,11 +5,11 @@ "outDir": "build/src/", "rootDir": "src/", "baseUrl": ".", - "allowSyntheticDefaultImports": true, // for ajv - "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, }, "include": [ - "src/**/*.ts" + "src/**/*.ts", + "src/schemas/*.mjs", // Import the validators ], "references": [ { "path": "../keyman-version" }, diff --git a/common/web/utils/package.json b/common/web/utils/package.json index 210ac4b6be5..3f5729ec6a8 100644 --- a/common/web/utils/package.json +++ b/common/web/utils/package.json @@ -23,13 +23,13 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/resources-gosh": "*", "@keymanapp/keyman-version": "*", + "@keymanapp/resources-gosh": "*", + "@types/node": "^14.0.5", "c8": "^7.12.0", "chai": "^4.3.4", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", - "@types/node": "^14.0.5", "typescript": "^4.9.5" }, "type": "module", diff --git a/developer/src/kmc-keyboard-info/build.sh b/developer/src/kmc-keyboard-info/build.sh index 094221c2e43..fb35c450197 100755 --- a/developer/src/kmc-keyboard-info/build.sh +++ b/developer/src/kmc-keyboard-info/build.sh @@ -11,6 +11,7 @@ cd "$THIS_SCRIPT_PATH" builder_describe "Build Keyman kmc keyboard-info Compiler module" \ "@/common/web/types" \ + "@/developer/src/common/web/utils" \ "clean" \ "configure" \ "build" \ diff --git a/developer/src/kmc-keyboard-info/package.json b/developer/src/kmc-keyboard-info/package.json index a354c942e53..7ca8db1f626 100644 --- a/developer/src/kmc-keyboard-info/package.json +++ b/developer/src/kmc-keyboard-info/package.json @@ -25,10 +25,8 @@ }, "dependencies": { "@keymanapp/common-types": "*", - "@keymanapp/kmc-package": "*", "@keymanapp/developer-utils": "*", - "ajv": "^8.11.0", - "ajv-formats": "^2.1.1" + "@keymanapp/kmc-package": "*" }, "bundleDependencies": [ "@keymanapp/developer-utils" diff --git a/developer/src/kmc-keyboard-info/src/index.ts b/developer/src/kmc-keyboard-info/src/index.ts index c36e9b82fe5..a933e8771ae 100644 --- a/developer/src/kmc-keyboard-info/src/index.ts +++ b/developer/src/kmc-keyboard-info/src/index.ts @@ -11,12 +11,7 @@ import langtags from "./imports/langtags.js"; import { validateMITLicense } from "@keymanapp/developer-utils"; import { KmpCompiler } from "@keymanapp/kmc-package"; -import AjvModule from 'ajv'; -import AjvFormatsModule from 'ajv-formats'; -const Ajv = AjvModule.default; // The actual expected Ajv type. -const ajvFormats = AjvFormatsModule.default; - -import { Schemas } from "@keymanapp/common-types"; +import { SchemaValidators } from "@keymanapp/common-types"; import { packageKeysExamplesToKeyboardInfo } from "./example-keys.js"; const regionNames = new Intl.DisplayNames(['en'], { type: "region" }); @@ -290,17 +285,10 @@ export class KeyboardInfoCompiler { const jsonOutput = JSON.stringify(keyboard_info, null, 2); - // TODO: look at performance improvements by precompiling Ajv schemas on first use - const ajv = new Ajv({ logger: { - log: (message) => this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Hint_OutputValidation({message})), - warn: (message) => this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Warn_OutputValidation({message})), - error: (message) => this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_OutputValidation({message})), - }}); - ajvFormats.default(ajv); - if(!ajv.validate(Schemas.default.keyboard_info, keyboard_info)) { + if(!SchemaValidators.default.keyboard_info(keyboard_info)) { // This is an internal fatal error; we should not be capable of producing // invalid output, so it is best to throw and die - throw new Error(ajv.errorsText()); + throw new Error((SchemaValidators.default.keyboard_info).errorsText()); } return new TextEncoder().encode(jsonOutput); diff --git a/developer/src/kmc-ldml/package.json b/developer/src/kmc-ldml/package.json index b140a5c552e..53542664c8b 100644 --- a/developer/src/kmc-ldml/package.json +++ b/developer/src/kmc-ldml/package.json @@ -29,7 +29,6 @@ "@keymanapp/keyman-version": "*", "@keymanapp/kmc-kmn": "*", "@keymanapp/ldml-keyboard-constants": "*", - "ajv": "^8.11.0", "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#7a188a1e26f8f36a175d95b67ffece8702363dfc", "semver": "^7.5.2", "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" diff --git a/developer/src/kmc-ldml/tsconfig.json b/developer/src/kmc-ldml/tsconfig.json index c9ec649b40f..0306158ebd6 100644 --- a/developer/src/kmc-ldml/tsconfig.json +++ b/developer/src/kmc-ldml/tsconfig.json @@ -5,7 +5,6 @@ "outDir": "build/src/", "rootDir": "src/", "baseUrl": ".", - "allowSyntheticDefaultImports": true, // for ajv "paths": { // "@keymanapp/keyman-version": ["../../../common/web/keyman-version/keyman-version.mts"], diff --git a/package-lock.json b/package-lock.json index fbcf72c79f6..644d3991870 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,9 @@ "devDependencies": { "@types/chai": "^4.3.5", "@typescript-eslint/eslint-plugin": "^5.59.1", + "ajv": "^8.12.0", + "ajv-cli": "^5.0.0", + "ajv-formats": "^2.1.1", "chai": "^4.3.4", "esbuild": "^0.15.16", "eslint": "^8.39.0", @@ -378,7 +381,6 @@ "license": "MIT", "dependencies": { "@keymanapp/keyman-version": "*", - "ajv": "^8.11.0", "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#7a188a1e26f8f36a175d95b67ffece8702363dfc", "semver": "^7.5.2", "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" @@ -390,6 +392,9 @@ "@types/node": "^20.4.1", "@types/semver": "^7.3.12", "@types/xml2js": "^0.4.5", + "ajv": "^8.12.0", + "ajv-cli": "^5.0.0", + "ajv-formats": "^2.1.1", "c8": "^7.12.0", "chai": "^4.3.4", "chalk": "^2.4.2", @@ -411,20 +416,6 @@ "integrity": "sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg==", "dev": true }, - "common/web/types/node_modules/ajv": { - "version": "8.11.2", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "common/web/types/node_modules/ansi-styles": { "version": "3.2.1", "dev": true, @@ -473,10 +464,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "common/web/types/node_modules/json-schema-traverse": { - "version": "1.0.0", - "license": "MIT" - }, "common/web/types/node_modules/minimatch": { "version": "3.0.4", "dev": true, @@ -1160,9 +1147,7 @@ "dependencies": { "@keymanapp/common-types": "*", "@keymanapp/developer-utils": "*", - "@keymanapp/kmc-package": "*", - "ajv": "^8.11.0", - "ajv-formats": "^2.1.1" + "@keymanapp/kmc-package": "*" }, "devDependencies": { "@types/chai": "^4.3.5", @@ -1188,21 +1173,6 @@ "integrity": "sha512-CukZhumInROvLq3+b5gLev+vgpsIqC2D0deQr/yS1WnxvmYLlJXZpaQrQiseMY+6xusl79E04UjWoqyr+t1/Ew==", "dev": true }, - "developer/src/kmc-keyboard-info/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "developer/src/kmc-keyboard-info/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -1256,11 +1226,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "developer/src/kmc-keyboard-info/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "developer/src/kmc-keyboard-info/node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1754,7 +1719,6 @@ "@keymanapp/keyman-version": "*", "@keymanapp/kmc-kmn": "*", "@keymanapp/ldml-keyboard-constants": "*", - "ajv": "^8.11.0", "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#7a188a1e26f8f36a175d95b67ffece8702363dfc", "semver": "^7.5.2", "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" @@ -1786,21 +1750,6 @@ "integrity": "sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg==", "dev": true }, - "developer/src/kmc-ldml/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "developer/src/kmc-ldml/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -1854,11 +1803,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "developer/src/kmc-ldml/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "developer/src/kmc-ldml/node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -3033,6 +2977,22 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@eslint/eslintrc/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3050,6 +3010,12 @@ } } }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "node_modules/@eslint/js": { "version": "8.39.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.39.0.tgz", @@ -4377,14 +4343,14 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" }, "funding": { @@ -4392,10 +4358,71 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-cli": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ajv-cli/-/ajv-cli-5.0.0.tgz", + "integrity": "sha512-LY4m6dUv44HTyhV+u2z5uX4EhPYTM38Iv1jdgDJJJCyOOuqB8KtZEGjPZ2T+sh5ZIJrXUfgErYx/j3gLd3+PlQ==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0", + "fast-json-patch": "^2.0.0", + "glob": "^7.1.0", + "js-yaml": "^3.14.0", + "json-schema-migrate": "^2.0.0", + "json5": "^2.1.3", + "minimist": "^1.2.0" + }, + "bin": { + "ajv": "dist/index.js" + }, + "peerDependencies": { + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } + } + }, + "node_modules/ajv-cli/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/ajv-cli/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/ajv-cli/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/ajv-formats": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, "dependencies": { "ajv": "^8.0.0" }, @@ -4408,26 +4435,6 @@ } } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/ansi-colors": { "version": "4.1.1", "dev": true, @@ -6469,6 +6476,22 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/eslint/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -6544,6 +6567,12 @@ "node": ">=10.13.0" } }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "node_modules/eslint/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -6573,6 +6602,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", @@ -6813,6 +6855,7 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", + "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -6831,6 +6874,24 @@ "node": ">=8.6.0" } }, + "node_modules/fast-json-patch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-2.2.1.tgz", + "integrity": "sha512-4j5uBaTnsYAV5ebkidvxiLUYOwjQ+JSFljeqfTxCrH9bDmlCQaOJFS84oDJ2rAXZq2yskmk3ORfoP9DCwqFNig==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^2.0.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fast-json-patch/node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", + "dev": true + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -8162,10 +8223,19 @@ "version": "3.0.1", "license": "MIT" }, + "node_modules/json-schema-migrate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/json-schema-migrate/-/json-schema-migrate-2.0.0.tgz", + "integrity": "sha512-r38SVTtojDRp4eD6WsCqiE0eNDt4v1WalBXb9cyZYw9ai5cGtBwzRNWjHzJl38w6TxFkXAIA7h+fyX3tnrAFhQ==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + } + }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { @@ -10001,6 +10071,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, "engines": { "node": ">=6" } @@ -10166,7 +10237,9 @@ }, "node_modules/require-from-string": { "version": "2.0.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -10681,6 +10754,12 @@ "node": "*" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "node_modules/ssri": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", @@ -11306,6 +11385,7 @@ }, "node_modules/uri-js": { "version": "4.4.1", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" diff --git a/package.json b/package.json index ac8c23dec5d..01f3f387f0b 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "devDependencies": { "@types/chai": "^4.3.5", "@typescript-eslint/eslint-plugin": "^5.59.1", + "ajv": "^8.12.0", + "ajv-cli": "^5.0.0", + "ajv-formats": "^2.1.1", "chai": "^4.3.4", "esbuild": "^0.15.16", "eslint": "^8.39.0", diff --git a/tsconfig.esm-base.json b/tsconfig.esm-base.json index d17d505e5c2..170983b8a25 100644 --- a/tsconfig.esm-base.json +++ b/tsconfig.esm-base.json @@ -14,6 +14,7 @@ "strictBindCallApply": true, "strictFunctionTypes": true, "noUnusedLocals": true, + "allowJs": true, "paths": { "@keymanapp/keyman-version": ["./common/web/keyman-version/keyman-version.mts"], From b8b0e561e8063e4b8e5cb1b593290cb83931820e Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 6 Oct 2023 12:29:02 +1100 Subject: [PATCH 2/2] chore: Add comment to common/web/types/src/schema-validators.ts --- common/web/types/src/schema-validators.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/web/types/src/schema-validators.ts b/common/web/types/src/schema-validators.ts index 340517b188d..07bcc032f16 100644 --- a/common/web/types/src/schema-validators.ts +++ b/common/web/types/src/schema-validators.ts @@ -8,6 +8,8 @@ import touchLayoutClean from './schemas/keyman-touch-layout.clean.spec.validator import touchLayout from './schemas/keyman-touch-layout.spec.validator.mjs'; import keyboard_info from './schemas/keyboard_info.schema.validator.mjs'; +// How to use: https://ajv.js.org/standalone.html#using-the-validation-function-s +// See also existing uses (search for `SchemaValidators`) for examples. const SchemaValidators = { kpj, kpj90,