mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-12 03:45:34 +00:00
Merge branch 'epic/kmc-convert' into chore/merge-master-into-kmc-convert
This commit is contained in:
commit
b9f8e8eb85
31 changed files with 819 additions and 1 deletions
|
|
@ -90,6 +90,10 @@ node-based next generation compiler, hosts kmc, (and legacy kmlmc, kmlmp)
|
|||
|
||||
File analysis tools for Keyman files.
|
||||
|
||||
### kmc-convert - Keyboard conversion tools
|
||||
|
||||
Tools for converting keyboard source files between various formats.
|
||||
|
||||
### kmc-copy - Project copying and renaming tools
|
||||
|
||||
Tools to copy and rename Keyman keyboard files and projects
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ builder_describe \
|
|||
":kmcmplib Compiler - .kmn compiler" \
|
||||
":kmc-analyze Compiler - Analysis Tools" \
|
||||
":kmc-copy Compiler - Project Copying and Renaming Tools" \
|
||||
":kmc-convert Compiler - Keyboard Conversion Tools" \
|
||||
":kmc-generate Compiler - Generation Tools" \
|
||||
":kmc-keyboard-info Compiler - .keyboard_info Module" \
|
||||
":kmc-kmn Compiler - .kmn to .kmx and .js Keyboard Module" \
|
||||
|
|
|
|||
|
|
@ -290,6 +290,10 @@ export enum CompilerErrorNamespace {
|
|||
* kmc-copy 0xB000…0xBFFF
|
||||
*/
|
||||
Copier = 0xB000,
|
||||
/**
|
||||
* kmc-convert 0xC000…0xCFFF
|
||||
*/
|
||||
Converter = 0xC000,
|
||||
};
|
||||
|
||||
type CompilerErrorSeverityOverride = CompilerErrorSeverity | 'disable';
|
||||
|
|
|
|||
11
developer/src/kmc-convert/.eslintrc.cjs
Normal file
11
developer/src/kmc-convert/.eslintrc.cjs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
module.exports = {
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.json", "./test/tsconfig.json"],
|
||||
},
|
||||
ignorePatterns: ["test/fixtures/**/*"],
|
||||
overrides: [
|
||||
{
|
||||
files:"src/**/*.ts",
|
||||
}
|
||||
],
|
||||
};
|
||||
6
developer/src/kmc-convert/README.md
Normal file
6
developer/src/kmc-convert/README.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Keyman Developer - kmc-convert
|
||||
|
||||
This package provides keyboard conversion tools. It can be used from the
|
||||
command line with [@keymanapp/kmc](https://npmjs.com/package/@keymanapp/kmc).
|
||||
|
||||
* [API Reference](https://help.keyman.com/developer/current-version/reference/api/kmc-convert)
|
||||
49
developer/src/kmc-convert/build.sh
Executable file
49
developer/src/kmc-convert/build.sh
Executable file
|
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env bash
|
||||
## START STANDARD BUILD SCRIPT INCLUDE
|
||||
# adjust relative paths as necessary
|
||||
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
||||
. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
|
||||
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
|
||||
|
||||
builder_describe "Keyman kmc-convert keyboard conversion tools module" \
|
||||
"@/developer/src/common/web/utils" \
|
||||
"@/developer/src/common/web/test-helpers" \
|
||||
"configure" \
|
||||
"build" \
|
||||
"api analyze API and prepare API documentation" \
|
||||
"clean" \
|
||||
"test" \
|
||||
"publish publish to npm" \
|
||||
"--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \
|
||||
"--dry-run,-n don't actually publish, just dry run"
|
||||
|
||||
builder_describe_outputs \
|
||||
configure /node_modules \
|
||||
build /developer/src/kmc-convert/build/src/main.js \
|
||||
api /developer/build/api/kmc-convert.api.json
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
#-------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
builder_run_action clean rm -rf ./build/ ./tsconfig.tsbuildinfo
|
||||
builder_run_action configure verify_npm_setup
|
||||
builder_run_action build tsc --build
|
||||
builder_run_action api api-extractor run --local --verbose
|
||||
|
||||
do_test() {
|
||||
eslint .
|
||||
cd test
|
||||
tsc -b
|
||||
cd ..
|
||||
readonly C8_THRESHOLD=20
|
||||
c8 -skip-full --reporter=lcov --reporter=text --lines $C8_THRESHOLD --statements $C8_THRESHOLD --branches $C8_THRESHOLD --functions $C8_THRESHOLD mocha "${builder_extra_params[@]}"
|
||||
builder_echo warning "Coverage thresholds are currently $C8_THRESHOLD%, which is lower than ideal."
|
||||
builder_echo warning "Please increase threshold in build.sh as test coverage improves."
|
||||
}
|
||||
|
||||
builder_run_action test do_test
|
||||
builder_run_action publish builder_publish_npm
|
||||
12
developer/src/kmc-convert/config/api-extractor.json
Normal file
12
developer/src/kmc-convert/config/api-extractor.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Config file for API Extractor. For more info, please visit: https://api-extractor.com
|
||||
*/
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
|
||||
"extends": "../../../config/api-extractor.base.json",
|
||||
"mainEntryPointFilePath": "<projectFolder>/build/src/main.d.ts",
|
||||
"docModel": {
|
||||
"enabled": true,
|
||||
"projectFolderUrl": "http://github.com/keymanapp/keyman/tree/master/developer/src/kmc-convert"
|
||||
}
|
||||
}
|
||||
67
developer/src/kmc-convert/package.json
Normal file
67
developer/src/kmc-convert/package.json
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"name": "@keymanapp/kmc-convert",
|
||||
"description": "Keyman Developer keyboard conversion tools",
|
||||
"keywords": [
|
||||
"keyboard",
|
||||
"keyman",
|
||||
"ldml",
|
||||
"unicode",
|
||||
"xkb",
|
||||
"keylayout",
|
||||
"inkey",
|
||||
"kmn",
|
||||
"msklc"
|
||||
],
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./build/src/main.js"
|
||||
},
|
||||
"files": [
|
||||
"/build/src/"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "gosh ./build.sh build",
|
||||
"test": "gosh ./build.sh test"
|
||||
},
|
||||
"author": "Sabine Schmitt",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/keymanapp/keyman/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@keymanapp/developer-utils": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@keymanapp/developer-test-helpers": "*",
|
||||
"@keymanapp/resources-gosh": "*",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "^20.4.1",
|
||||
"@types/semver": "^7.3.12",
|
||||
"c8": "^7.12.0",
|
||||
"chalk": "^2.4.2",
|
||||
"mocha": "^8.4.0",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"mocha": {
|
||||
"spec": "build/test/**/test-*.js",
|
||||
"require": [
|
||||
"source-map-support/register"
|
||||
]
|
||||
},
|
||||
"c8": {
|
||||
"all": true,
|
||||
"src": [
|
||||
"src/"
|
||||
],
|
||||
"exclude-after-remap": true,
|
||||
"exclude": [
|
||||
"test/",
|
||||
"src/converter-options.ts",
|
||||
"src/converter-artifacts.ts"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/keymanapp/keyman.git"
|
||||
}
|
||||
}
|
||||
27
developer/src/kmc-convert/src/converter-artifacts.ts
Normal file
27
developer/src/kmc-convert/src/converter-artifacts.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*
|
||||
* Output artifacts available from kmc-convert
|
||||
*/
|
||||
import { KeymanCompilerArtifactOptional, KeymanCompilerArtifacts } from '@keymanapp/developer-utils';
|
||||
|
||||
export interface ConverterArtifacts extends KeymanCompilerArtifacts { }
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Internal in-memory build artifacts from a successful compilation
|
||||
*/
|
||||
export interface ConverterToKmnArtifacts extends ConverterArtifacts {
|
||||
/**
|
||||
* Source keyboard filedata and filename
|
||||
*/
|
||||
kmn?: KeymanCompilerArtifactOptional;
|
||||
/**
|
||||
* Source on screen keyboard filedata and filename
|
||||
*/
|
||||
kvks?: KeymanCompilerArtifactOptional;
|
||||
/**
|
||||
* Source touch keyboard filedata and filename
|
||||
*/
|
||||
keymanTouchLayout?: KeymanCompilerArtifactOptional;
|
||||
};
|
||||
22
developer/src/kmc-convert/src/converter-class-factory.ts
Normal file
22
developer/src/kmc-convert/src/converter-class-factory.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*
|
||||
* Lists all the available converters and finds matching converter
|
||||
*/
|
||||
import { KeylayoutToKmnConverter } from './keylayout-to-kmn/keylayout-to-kmn-converter.js';
|
||||
|
||||
const converters = [
|
||||
KeylayoutToKmnConverter,
|
||||
];
|
||||
|
||||
export class ConverterClassFactory {
|
||||
static find(inputFilename: string, outputFilename: string) {
|
||||
|
||||
const converter = converters.find(c =>
|
||||
inputFilename.endsWith(c.INPUT_FILE_EXTENSION) &&
|
||||
outputFilename.endsWith(c.OUTPUT_FILE_EXTENSION)
|
||||
);
|
||||
|
||||
return converter;
|
||||
}
|
||||
}
|
||||
30
developer/src/kmc-convert/src/converter-messages.ts
Normal file
30
developer/src/kmc-convert/src/converter-messages.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*
|
||||
* Converter messages
|
||||
*/
|
||||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def } from '@keymanapp/developer-utils';
|
||||
|
||||
const Namespace = CompilerErrorNamespace.Converter;
|
||||
// const SevInfo = CompilerErrorSeverity.Info | Namespace;
|
||||
// const SevHint = CompilerErrorSeverity.Hint | Namespace;
|
||||
// const SevWarn = CompilerErrorSeverity.Warn | Namespace;
|
||||
const SevError = CompilerErrorSeverity.Error | Namespace;
|
||||
// const SevFatal = CompilerErrorSeverity.Fatal | Namespace;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class ConverterMessages {
|
||||
static ERROR_OutputFilenameIsRequired = SevError | 0x0001;
|
||||
static Error_OutputFilenameIsRequired = () =>
|
||||
m(this.ERROR_OutputFilenameIsRequired, `An output filename is required for keyboard conversion.`);
|
||||
|
||||
static ERROR_NoConverterFound = SevError | 0x0002;
|
||||
static Error_NoConverterFound = (o:{inputFilename: string, outputFilename: string}) =>
|
||||
m(this.ERROR_NoConverterFound, `No converter is available that can convert from '${def(o.inputFilename)}' to '${def(o.outputFilename)}'.`);
|
||||
|
||||
static ERROR_FileNotFound = SevError | 0x0003;
|
||||
static Error_FileNotFound = (o:{inputFilename: string}) =>
|
||||
m(this.ERROR_FileNotFound, `Input filename '${def(o.inputFilename)}' does not exist or could not be loaded.`);
|
||||
}
|
||||
17
developer/src/kmc-convert/src/converter-options.ts
Normal file
17
developer/src/kmc-convert/src/converter-options.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*
|
||||
* Converter options
|
||||
*/
|
||||
import { CompilerOptions } from "@keymanapp/developer-utils";
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Options for the keyboard converter
|
||||
*/
|
||||
export interface ConverterOptions extends CompilerOptions {
|
||||
/**
|
||||
* Fail if the keyboard conversion is not 100% complete
|
||||
*/
|
||||
failIfIncomplete?: boolean;
|
||||
};
|
||||
111
developer/src/kmc-convert/src/converter.ts
Normal file
111
developer/src/kmc-convert/src/converter.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*
|
||||
* Infrastructure for keyboard source file conversion tools
|
||||
*/
|
||||
import {
|
||||
CompilerCallbacks,
|
||||
CompilerOptions,
|
||||
defaultCompilerOptions,
|
||||
KeymanCompiler,
|
||||
KeymanCompilerResult,
|
||||
} from "@keymanapp/developer-utils";
|
||||
import { ConverterClassFactory } from './converter-class-factory.js';
|
||||
import { ConverterArtifacts } from "./converter-artifacts.js";
|
||||
import { ConverterMessages } from "./converter-messages.js";
|
||||
|
||||
export interface ConverterResult extends KeymanCompilerResult {
|
||||
/**
|
||||
* Internal in-memory build artifacts from a successful compilation. Caller
|
||||
* can write these to disk with {@link Converter.write}
|
||||
*/
|
||||
artifacts: ConverterArtifacts;
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
* Converts keyboards between different source file formats. The
|
||||
* compiler does not read or write from filesystem or network directly, but
|
||||
* relies on callbacks for all external IO.
|
||||
*/
|
||||
export class Converter implements KeymanCompiler {
|
||||
private callbacks: CompilerCallbacks;
|
||||
private options: CompilerOptions;
|
||||
|
||||
/**
|
||||
* Initialize the converter. Copies options.
|
||||
* @param callbacks - Callbacks for external interfaces, including message
|
||||
* reporting and file io
|
||||
* @param options - Compiler options
|
||||
* @returns false if initialization fails
|
||||
*/
|
||||
async init(callbacks: CompilerCallbacks, options: CompilerOptions): Promise<boolean> {
|
||||
this.options = { ...options };
|
||||
this.callbacks = callbacks;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a keyboard source file to another format. Returns an object
|
||||
* containing source artifacts on success. The files are passed in by name,
|
||||
* and the compiler will use callbacks as passed to the {@link Converter.init}
|
||||
* function to read any input files by disk.
|
||||
* @param infile - Path to source file.
|
||||
* @param outfile - Path to output file. The file will not be written to, but
|
||||
* will be included in the result for use by
|
||||
* {@link Converter.write}.
|
||||
* @returns Source artifacts on success, null on failure.
|
||||
*/
|
||||
async run(inputFilename: string, outputFilename?: string): Promise<ConverterResult> {
|
||||
|
||||
const converterOptions: CompilerOptions = {
|
||||
...defaultCompilerOptions,
|
||||
...this.options,
|
||||
};
|
||||
|
||||
if(!outputFilename) {
|
||||
this.callbacks.reportMessage(ConverterMessages.Error_OutputFilenameIsRequired());
|
||||
return null;
|
||||
}
|
||||
|
||||
const ConverterClass = ConverterClassFactory.find(inputFilename, outputFilename);
|
||||
if(!ConverterClass) {
|
||||
this.callbacks.reportMessage(ConverterMessages.Error_NoConverterFound({inputFilename, outputFilename}));
|
||||
return null;
|
||||
}
|
||||
|
||||
const binaryData = this.callbacks.loadFile(inputFilename);
|
||||
if(!binaryData) {
|
||||
this.callbacks.reportMessage(ConverterMessages.Error_FileNotFound({inputFilename}));
|
||||
return null;
|
||||
}
|
||||
|
||||
const converter = new ConverterClass(this.callbacks, converterOptions);
|
||||
const artifacts = await converter.run(inputFilename, outputFilename, binaryData);
|
||||
// Note: any subsequent errors in conversion will have been reported by the converter
|
||||
return artifacts ? { artifacts } : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write artifacts from a successful compile to disk, via callbacks methods.
|
||||
* The artifacts written may include:
|
||||
*
|
||||
* - .kmn file - source keyboard used by Keyman on desktop platforms
|
||||
* - .kvks file - source on screen keyboard used by Keyman on desktop platforms
|
||||
* - .keyman-touch-layout file - source touch layout keyboard for touch platforms
|
||||
* - other keyboard source files as implemented
|
||||
*
|
||||
* @param artifacts - object containing artifact data to write out
|
||||
* @returns true on success
|
||||
*/
|
||||
async write(artifacts: ConverterArtifacts): Promise<boolean> {
|
||||
for(const key of Object.keys(artifacts)) {
|
||||
if(artifacts[key]) {
|
||||
this.callbacks.fs.writeFileSync(artifacts[key].filename, artifacts[key].data);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*
|
||||
* Converts macOS/Ukelele .keylayout files to Keyman .kmn
|
||||
*/
|
||||
import { CompilerCallbacks, CompilerOptions } from "@keymanapp/developer-utils";
|
||||
import { ConverterToKmnArtifacts } from "../converter-artifacts.js";
|
||||
|
||||
export class KeylayoutToKmnConverter {
|
||||
static readonly INPUT_FILE_EXTENSION = '.keylayout';
|
||||
static readonly OUTPUT_FILE_EXTENSION = '.kmn';
|
||||
|
||||
constructor(/*private*/ _callbacks: CompilerCallbacks, /*private*/ _options: CompilerOptions) {
|
||||
// TODO: if these are needed, uncomment /*private*/ and remove _, and they will then
|
||||
// be available as class properties
|
||||
}
|
||||
|
||||
async run(inputFilename: string, outputFilename: string, binaryData: Uint8Array): Promise<ConverterToKmnArtifacts> {
|
||||
if(!inputFilename || !outputFilename || !binaryData) {
|
||||
throw new Error('Invalid parameters');
|
||||
}
|
||||
|
||||
console.error('TODO: implement KeylayoutToKmnConverter');
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
9
developer/src/kmc-convert/src/main.ts
Normal file
9
developer/src/kmc-convert/src/main.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*
|
||||
* Keyboard source file conversion tools
|
||||
*/
|
||||
|
||||
export { Converter } from './converter.js';
|
||||
export { ConverterOptions } from './converter-options.js';
|
||||
export { ConverterMessages } from './converter-messages.js';
|
||||
44
developer/src/kmc-convert/test/helpers/index.ts
Normal file
44
developer/src/kmc-convert/test/helpers/index.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*
|
||||
* Helpers and utilities for the Mocha tests.
|
||||
*/
|
||||
import * as path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import 'mocha';
|
||||
import { assert } from 'chai';
|
||||
import { compilerEventFormat, CompilerOptions, } from "@keymanapp/developer-utils";
|
||||
import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
|
||||
|
||||
|
||||
/**
|
||||
* Builds a path to the fixture with the given path components.
|
||||
*
|
||||
* e.g., makePathToFixture('basic.xml')
|
||||
*
|
||||
* @param components One or more path components.
|
||||
*/
|
||||
export function makePathToFixture(...components: string[]): string {
|
||||
return fileURLToPath(new URL(path.join('..', '..', '..', 'test', 'fixtures', ...components), import.meta.url));
|
||||
}
|
||||
|
||||
export const compilerTestCallbacks = new TestCompilerCallbacks();
|
||||
|
||||
export const compilerTestOptions: CompilerOptions = {};
|
||||
|
||||
beforeEach(function() {
|
||||
compilerTestCallbacks.clear();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
if (this.currentTest.state !== 'passed') {
|
||||
compilerTestCallbacks.messages.forEach(message => console.log(message.message));
|
||||
}
|
||||
});
|
||||
|
||||
export function checkMessages() {
|
||||
if(compilerTestCallbacks.messages.length > 0) {
|
||||
console.log(compilerTestCallbacks.messages);
|
||||
}
|
||||
assert.isEmpty(compilerTestCallbacks.messages, compilerEventFormat(compilerTestCallbacks.messages));
|
||||
}
|
||||
13
developer/src/kmc-convert/test/test-converter-messages.ts
Normal file
13
developer/src/kmc-convert/test/test-converter-messages.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*/
|
||||
import 'mocha';
|
||||
import { ConverterMessages } from '../src/converter-messages.js';
|
||||
import { verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers';
|
||||
import { CompilerErrorNamespace } from '@keymanapp/developer-utils';
|
||||
|
||||
describe('ConverterMessages', function () {
|
||||
it('should have a valid ConverterMessages object', function() {
|
||||
return verifyCompilerMessagesObject(ConverterMessages, CompilerErrorNamespace.Converter);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*/
|
||||
import 'mocha';
|
||||
import {assert} from 'chai';
|
||||
import {compilerTestCallbacks, compilerTestOptions} from './helpers/index.js';
|
||||
import {KeylayoutToKmnConverter} from '../src/keylayout-to-kmn/keylayout-to-kmn-converter.js';
|
||||
|
||||
describe('KeylayoutToKmnConverter', function() {
|
||||
|
||||
before(function() {
|
||||
compilerTestCallbacks.clear();
|
||||
});
|
||||
|
||||
it('should throw on null inputs', async function () {
|
||||
// const inputFilename = makePathToFixture('file.keylayout');
|
||||
const converter = new KeylayoutToKmnConverter(compilerTestCallbacks, compilerTestOptions);
|
||||
// note, could use 'chai as promised' library to make this more fluent:
|
||||
let threw = false;
|
||||
try {
|
||||
await converter.run(null, null, null);
|
||||
} catch {
|
||||
threw = true;
|
||||
}
|
||||
assert.isTrue(threw);
|
||||
});
|
||||
|
||||
});
|
||||
19
developer/src/kmc-convert/test/tsconfig.json
Normal file
19
developer/src/kmc-convert/test/tsconfig.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../kmc/tsconfig.kmc-base.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"rootDirs": ["./", "../src/"],
|
||||
"outDir": "../build/test",
|
||||
"baseUrl": ".",
|
||||
},
|
||||
"include": [
|
||||
"**/test-*.ts",
|
||||
"./helpers/index.ts",
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../common/web/test-helpers/" },
|
||||
{ "path": "../../common/web/utils/" },
|
||||
{ "path": "../" }
|
||||
]
|
||||
}
|
||||
15
developer/src/kmc-convert/tsconfig.json
Normal file
15
developer/src/kmc-convert/tsconfig.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"outDir": "build/src/",
|
||||
"rootDir": "src/",
|
||||
"baseUrl": ".",
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../common/web/utils/" },
|
||||
]
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ builder_describe "Build Keyman Keyboard Compiler kmc" \
|
|||
"@/developer/src/common/web/utils" \
|
||||
"@/developer/src/kmc-analyze" \
|
||||
"@/developer/src/kmc-copy" \
|
||||
"@/developer/src/kmc-convert" \
|
||||
"@/developer/src/kmc-generate" \
|
||||
"@/developer/src/kmc-keyboard-info" \
|
||||
"@/developer/src/kmc-kmn" \
|
||||
|
|
@ -54,7 +55,7 @@ function do_build() {
|
|||
#-------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
function do_test() {
|
||||
builder_do_typescript_tests 50
|
||||
builder_do_typescript_tests 45
|
||||
./test/command-line-tests.sh test
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
"@keymanapp/keyman-version": "*",
|
||||
"@keymanapp/kmc-analyze": "*",
|
||||
"@keymanapp/kmc-copy": "*",
|
||||
"@keymanapp/kmc-convert": "*",
|
||||
"@keymanapp/kmc-generate": "*",
|
||||
"@keymanapp/kmc-keyboard-info": "*",
|
||||
"@keymanapp/kmc-kmn": "*",
|
||||
|
|
|
|||
73
developer/src/kmc/src/commands/convert.ts
Normal file
73
developer/src/kmc/src/commands/convert.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL International. MIT License.
|
||||
*
|
||||
* Declares the 'convert' command line action
|
||||
*/
|
||||
import * as path from 'path';
|
||||
import { Command } from 'commander';
|
||||
import { NodeCompilerCallbacks } from '../util/NodeCompilerCallbacks.js';
|
||||
import { InfrastructureMessages } from '../messages/infrastructureMessages.js';
|
||||
import { BaseOptions } from '../util/baseOptions.js';
|
||||
import { exitProcess } from '../util/sysexits.js';
|
||||
import { Converter, ConverterOptions } from '@keymanapp/kmc-convert';
|
||||
import { CompilerCallbacks } from '@keymanapp/developer-utils';
|
||||
|
||||
export function declareConvert(program: Command) {
|
||||
const command = program.command('convert <infile...>');
|
||||
BaseOptions.addLogLevel(command);
|
||||
BaseOptions.addLogFormat(command);
|
||||
command
|
||||
.description('Convert keyboard source files between formats')
|
||||
.option('-p, --out-path <path>', 'Path for output files')
|
||||
.requiredOption('-t, --out-type <type>', 'File extension for output files, e.g. .kmn, required')
|
||||
.action(async (filenames: string[], _options: any, commander: any): Promise<never|void> => {
|
||||
const options = commander.optsWithGlobals();
|
||||
const callbacks = new NodeCompilerCallbacks({logLevel: options.logLevel});
|
||||
|
||||
for(const filename of filenames) {
|
||||
const outputFilename = path.join(
|
||||
options.outPath ?? path.dirname(filename),
|
||||
path.basename(filename, path.extname(filename)) + options.outType
|
||||
);
|
||||
|
||||
if(!await convert(callbacks, options, filename, outputFilename)) {
|
||||
return await exitProcess(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function convert(
|
||||
callbacks: CompilerCallbacks,
|
||||
options: ConverterOptions,
|
||||
inputFilename: string,
|
||||
outputFilename: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const converter = new Converter();
|
||||
if(!await converter.init(callbacks, options)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = await converter.run(inputFilename, outputFilename);
|
||||
if(!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!await converter.write(result.artifacts)) {
|
||||
return false;
|
||||
}
|
||||
} catch(e) {
|
||||
callbacks.reportMessage(InfrastructureMessages.Fatal_UnexpectedException({e}));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* these are exported only for unit tests, do not use
|
||||
*/
|
||||
export const convertUnitTestEndpoints = {
|
||||
convert
|
||||
};
|
||||
|
|
@ -15,6 +15,7 @@ import { declareMessage } from './commands/messageCommand.js';
|
|||
import { kmcSentryOptions } from './util/kmcSentryOptions.js';
|
||||
import { declareGenerate } from './commands/generate.js';
|
||||
import { declareCopy } from './commands/copy.js';
|
||||
import { declareConvert } from './commands/convert.js';
|
||||
|
||||
await TestKeymanSentry.runTestIfCLRequested(kmcSentryOptions);
|
||||
if(KeymanSentry.isEnabled()) {
|
||||
|
|
@ -60,6 +61,7 @@ async function run() {
|
|||
|
||||
declareBuild(program);
|
||||
declareAnalyze(program);
|
||||
declareConvert(program);
|
||||
declareMessage(program);
|
||||
declareGenerate(program);
|
||||
declareCopy(program);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { ModelCompilerMessages } from '@keymanapp/kmc-model';
|
|||
import { ModelInfoCompilerMessages } from '@keymanapp/kmc-model-info';
|
||||
import { PackageCompilerMessages } from '@keymanapp/kmc-package';
|
||||
import { InfrastructureMessages } from './infrastructureMessages.js';
|
||||
import { ConverterMessages } from '@keymanapp/kmc-convert';
|
||||
|
||||
// Maps every compiler error namespace to the corresponding implementation
|
||||
const messageNamespaces: Record<CompilerErrorNamespace, any> = {
|
||||
|
|
@ -24,6 +25,7 @@ const messageNamespaces: Record<CompilerErrorNamespace, any> = {
|
|||
[CompilerErrorNamespace.KeyboardInfoCompiler]: KeyboardInfoCompilerMessages,
|
||||
[CompilerErrorNamespace.Copier]: CopierMessages,
|
||||
[CompilerErrorNamespace.Generator]: GeneratorMessages,
|
||||
[CompilerErrorNamespace.Converter]: ConverterMessages,
|
||||
};
|
||||
|
||||
// This works around pain points in enumerating enum members in Typescript
|
||||
|
|
@ -49,4 +51,5 @@ export const messageSources: Record<CompilerErrorNamespace, CompilerMessageSourc
|
|||
[CompilerErrorNamespace.KmwCompiler]: { module: 'kmc-kmn', class: KmwCompilerMessages },
|
||||
[CompilerErrorNamespace.ModelInfoCompiler]: { module: 'kmc-model-info', class: ModelInfoCompilerMessages },
|
||||
[CompilerErrorNamespace.KeyboardInfoCompiler]: { module: 'kmc-keyboard-info', class: KeyboardInfoCompilerMessages },
|
||||
[CompilerErrorNamespace.Converter]: { module: 'kmc-convert', class: ConverterMessages },
|
||||
};
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
{ "path": "../../../common/web/types" },
|
||||
{ "path": "../kmc-analyze" },
|
||||
{ "path": "../kmc-copy" },
|
||||
{ "path": "../kmc-convert" },
|
||||
{ "path": "../kmc-generate" },
|
||||
{ "path": "../kmc-keyboard-info" },
|
||||
{ "path": "../kmc-kmn" },
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ readonly PACKAGES=(
|
|||
developer/src/common/web/utils
|
||||
developer/src/kmc-analyze
|
||||
developer/src/kmc-copy
|
||||
developer/src/kmc-convert
|
||||
developer/src/kmc-generate
|
||||
developer/src/kmc-keyboard-info
|
||||
developer/src/kmc-kmn
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ sourcemap_paths=(
|
|||
./src/kmc/build
|
||||
./src/kmc-analyze/build
|
||||
./src/kmc-copy/build
|
||||
./src/kmc-convert/build
|
||||
./src/kmc-generate/build
|
||||
./src/kmc-keyboard-info/build
|
||||
./src/kmc-kmn/build
|
||||
|
|
|
|||
216
package-lock.json
generated
216
package-lock.json
generated
|
|
@ -15,6 +15,7 @@
|
|||
"developer/src/common/web/utils",
|
||||
"developer/src/kmc-analyze",
|
||||
"developer/src/kmc-copy",
|
||||
"developer/src/kmc-convert",
|
||||
"developer/src/kmc-generate",
|
||||
"developer/src/kmc-keyboard-info",
|
||||
"developer/src/kmc-kmn",
|
||||
|
|
@ -269,6 +270,7 @@
|
|||
"@keymanapp/developer-utils": "*",
|
||||
"@keymanapp/keyman-version": "*",
|
||||
"@keymanapp/kmc-analyze": "*",
|
||||
"@keymanapp/kmc-convert": "*",
|
||||
"@keymanapp/kmc-copy": "*",
|
||||
"@keymanapp/kmc-generate": "*",
|
||||
"@keymanapp/kmc-keyboard-info": "*",
|
||||
|
|
@ -381,6 +383,216 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"developer/src/kmc-convert": {
|
||||
"name": "@keymanapp/kmc-convert",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@keymanapp/developer-utils": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@keymanapp/developer-test-helpers": "*",
|
||||
"@keymanapp/resources-gosh": "*",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "^20.4.1",
|
||||
"@types/semver": "^7.3.12",
|
||||
"c8": "^7.12.0",
|
||||
"chalk": "^2.4.2",
|
||||
"mocha": "^8.4.0",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
},
|
||||
"developer/src/kmc-convert/node_modules/@types/mocha": {
|
||||
"version": "5.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz",
|
||||
"integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"developer/src/kmc-convert/node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"developer/src/kmc-convert/node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"developer/src/kmc-convert/node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"developer/src/kmc-convert/node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"developer/src/kmc-convert/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"developer/src/kmc-convert/node_modules/js-yaml": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz",
|
||||
"integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"developer/src/kmc-convert/node_modules/minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"developer/src/kmc-convert/node_modules/mocha": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz",
|
||||
"integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ungap/promise-all-settled": "1.1.2",
|
||||
"ansi-colors": "4.1.1",
|
||||
"browser-stdout": "1.3.1",
|
||||
"chokidar": "3.5.1",
|
||||
"debug": "4.3.1",
|
||||
"diff": "5.0.0",
|
||||
"escape-string-regexp": "4.0.0",
|
||||
"find-up": "5.0.0",
|
||||
"glob": "7.1.6",
|
||||
"growl": "1.10.5",
|
||||
"he": "1.2.0",
|
||||
"js-yaml": "4.0.0",
|
||||
"log-symbols": "4.0.0",
|
||||
"minimatch": "3.0.4",
|
||||
"ms": "2.1.3",
|
||||
"nanoid": "3.1.20",
|
||||
"serialize-javascript": "5.0.1",
|
||||
"strip-json-comments": "3.1.1",
|
||||
"supports-color": "8.1.1",
|
||||
"which": "2.0.2",
|
||||
"wide-align": "1.1.3",
|
||||
"workerpool": "6.1.0",
|
||||
"yargs": "16.2.0",
|
||||
"yargs-parser": "20.2.4",
|
||||
"yargs-unparser": "2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"_mocha": "bin/_mocha",
|
||||
"mocha": "bin/mocha"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.12.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mochajs"
|
||||
}
|
||||
},
|
||||
"developer/src/kmc-convert/node_modules/mocha/node_modules/escape-string-regexp": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"developer/src/kmc-convert/node_modules/mocha/node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"developer/src/kmc-convert/node_modules/mocha/node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"developer/src/kmc-convert/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"developer/src/kmc-convert/node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"developer/src/kmc-copy": {
|
||||
"name": "@keymanapp/kmc-copy",
|
||||
"license": "MIT",
|
||||
|
|
@ -1700,6 +1912,10 @@
|
|||
"resolved": "developer/src/kmc-analyze",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@keymanapp/kmc-convert": {
|
||||
"resolved": "developer/src/kmc-convert",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@keymanapp/kmc-copy": {
|
||||
"resolved": "developer/src/kmc-copy",
|
||||
"link": true
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
"developer/src/common/web/utils",
|
||||
"developer/src/kmc-analyze",
|
||||
"developer/src/kmc-copy",
|
||||
"developer/src/kmc-convert",
|
||||
"developer/src/kmc-generate",
|
||||
"developer/src/kmc-keyboard-info",
|
||||
"developer/src/kmc-kmn",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@
|
|||
{ "path": "./developer/src/kmc-copy/test/tsconfig.json" },
|
||||
{ "path": "./developer/src/kmc-generate/tsconfig.json" },
|
||||
{ "path": "./developer/src/kmc-generate/test/tsconfig.json" },
|
||||
{ "path": "./developer/src/kmc-convert/tsconfig.json" },
|
||||
{ "path": "./developer/src/kmc-convert/test/tsconfig.json" },
|
||||
{ "path": "./developer/src/kmc-kmn/test/tsconfig.json" },
|
||||
{ "path": "./developer/src/kmc-kmn/tsconfig.json" },
|
||||
{ "path": "./developer/src/kmc-keyboard-info/test/tsconfig.json" },
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue