mmir-plugin-exports

2.7.0 • Public • Published

Plugin Exports

MIT license GitHub package.json version npm API Guides

Utilities for creating metadata files (module-ids.gen.js, module-config.gen.js) and tools for managing compatiblity modules, typings, and project metadata.

Overview:

pluginexport: Creating and Updating module-ids.gen.js

running pluginexport <dir>

parses the package.json of a plugin and

  • creates alias i.e. paths entries for all files in (recursively except for workers (sub)dir(s))
    • directories.lib
  • creates a list of workers for all files in (non-recursive!)
    • <custom field> mmir.workers
      string | Array<string>: single or list of directory/ies or file(s)
  • creates a list of exported modules for (non-recursive!)
    • <custom field> mmir.exports:
      string | Array<string>: single or list of directory/ies or file(s)
  • creates a list of exported files for (non-recursive!)
    • <custom field> mmir.exportedFiles:
      string | Array<string>: single or list of file(s)
  • creates an object modes for
    • <custom field> mmir.modes:
      {[modeName: string]: ModeDefinition}: where each ModeDefinition may have file-replacement-mappings and, optionally, exports and exportFiles fields
      • ModeDefinition: {[originalFile: string]: ReplacementFile, exports?, exportFiles?}
  • creates (string) entry buildConfig for
    • <custom field> mmir.buildConfig:
      string: a file(s) containing a CommonJS module that exports a single or a list of (mmir) build configurations for the plugin
  • adds as entry to exported modules with alias/path for the plugin itself, i.e {<plugin ID>: <main file>}
    • main
  • creates a list of dependencies for all entries in
    • dependencies

pluginexport: Creating and Updating module-config.gen.js

running pluginexport <dir>

parses the config.d.ts of a plugin and

  • exports the properties (name) of [.*]PluginConfig interface as pluginName:
    export interface SomeNamePluginConfig { <the plugin config name>: <the config entry type>; }
    • if there is only one property specified, the generated module will represent a single plugin, e.g.
      pluginName: "someName",
      config: [
        ...
    • if multiple properties are declared, the generated module's pluginName will be an array and an additional field plugins will hold the corresponding plugin information, e.g.
      pluginName: ["pl1", "pl2"],
      plugins: {
        pl1: {
          pluginName: "pl1",
          config: [
            ...
    • the properties' type will be used as "main" entry point for creating the config-list, e.g.
        somePluginName: SomePluginConfigEntry;
        ...
      }
      export interface SomePluginConfigEntry extends MediaManagerPluginEntry {
        someConfig: string;
      }
      ->
      pluginName: "somePluginName",
      config: ["someConfig"],
    • optionally, if relevant for the plugin, the SpeechConfig entry is specified by adding it as Union type:
      somePluginName: SomePluginConfigEntry | SomePluginSpeechConfigEntry;
      ...
    }
    ...
    export interface SomePluginSpeechConfigEntry extends SpeechConfigPluginEntry {
      someSpeechConfig: string;
    }
    ->
    pluginName: "somePluginName",
    config: ...,
    speechConfig: ["someSpeechConfig"],
    • if configuration-properties (or speech-configuration) have JSDoc @default values specified, they will be included in the property defaultValues (NOTE the default value must be JSON.parse'able or simple string):
      somePluginName: SomePluginConfigEntry | SomePluginSpeechConfigEntry;
      ...
    }
    ...
    export interface SomePluginConfigEntry extends MediaManagerPluginEntry {
      /** @default theDefault */
      someConfig: string;
    }
    export interface SomePluginSpeechConfigEntry extends SpeechConfigPluginEntry {
      /** @default 7 */
      someSpeechNumConfig: number;
    }
    ->
    pluginName: "somePluginName",
    config: [/** <js-doc> */"someConfig"],
    speechConfig: [/** <js-doc> */"someSpeechNumConfig"],
    defaultValues: { someConfig: "theDefault"},
    defaultspeechValues: { someSpeechNumConfig: 7},
  • exports other interfaces (their properties) with suffix PluginSpeechConfigEntry as speechConfig-property lists:
    export interface SomeNamePluginSpeechConfigEntry {...
    • NOTE should extend mmir.SpeechConfigPluginEntry
  • exports other interfaces (their properties) with suffix PluginConfigEntry as config-property lists:
    export interface AnotherNamePluginConfigEntry {...
    • NOTE should extend mmir.MediaManagerPluginEntry
  • exports enums as properties:
    export enum SomeName {...
  • NOTE: protected/special property names (for properties in <*>PluginConfigEntry and <*>PluginSpeechConfigEntry)
    • mod: module name for the configuration entry of MediaManager.plugins.env
    • ctx: the (optional) context for the configuration entry of MediaManager.plugins.env
    • config: the custom plugin-configuration for the configuration entry of MediaManager.plugins.env

NOTE all interfaces etc. must be defined in the root of config.d.ts

In addition, if it exists, 'pluginexport' parses the build-configuration file build-config.ts and collects all exported variables of type AppConfig (type from module mmir-tooling) into a list and stores it into field buildConfigs. (In addition, exported variables which's type end with AppConfig are also collected into the field buildConfigs, e.g. SomeAppConfig)

For example:

import { AppConfig } from 'mmir-tooling';
import { WebpackAppConfig } from 'mmir-webpack';
export const buildConfig: AppConfig = {
  states: {
    models: {
      myStateModel: {
        moduleId: 'mmirf/myCustomStateModel',
        file: __dirname + '/states/my-model.xml'
      },
      myOtherModel: {
        moduleId: 'mmirf/otherCustomStateModel',
        file: __dirname + '/states/my-other-model.xml'
      },
    }
  }
};
export const buildConfigLibDependencies: WebpackAppConfig = {
  includeModules: ['mmirf/util/extendDeep']
};

->

buildConfigs: [
  {
    states: {
      models: {
        myStateModel: {
          moduleId: 'mmirf/myCustomStateModel',
          file: __dirname + '/states/my-model.xml'
        },
        myOtherModel: {
          moduleId: 'mmirf/otherCustomStateModel',
          file: __dirname + '/states/my-other-model.xml'
        },
      }
    }
  },
  {
    includeModules: ['mmirf/util/extendDeep']
  }
]

NOTE 1: build-configuration parsing only considers AppConfig variables that are immediately initialized or creator functions that return build configurations (or FALSY values).

Example with build-config creator function:

import { PluginExportBuildConfigCreator , AppConfig } from 'mmir-tooling';

// a dynamic build-config specification using a creator function with:
//   type PluginExportBuildConfigCreator = (pluginConfig: PluginConfig & TTSPluginSpeechConfig, runtimeConfig: RuntimeConfiguration, pluginBuildConfigs: PluginExportBuildConfig[]) => PluginExportBuildConfig;
export const buildConfigLibDependencies: PluginExportBuildConfigCreator = function(pluginConfig, runtimeConfig, pluginBuildConfigs) {
  if(pluginConfig && pluginConfig.encoder === 'wav'){
    return {
      includeModules: ['mmir-plugin-encoder-core/workers/wavEncoder']
    }
  }
};

// ... and a non-dynamic build-config specification:
export const buildConfig: AppConfig = {
  states: {
    models: {
      myStateModel: {
        moduleId: 'mmirf/myCustomStateModel',
        file: __dirname + '/states/my-model.xml'
      }
    }
  }
};

->

buildConfigs: [
  function(pluginConfig, _runtimeConfig, _pluginBuildConfigs) {
    if(pluginConfig && pluginConfig.encoder === 'wav'){
      return {
        includeModules: ['mmir-plugin-encoder-core/workers/wavEncoder']
      }
    }
  },
  {
    states: {
      models: {
        myStateModel: {
          moduleId: 'mmirf/myCustomStateModel',
          file: __dirname + '/states/my-model.xml'
        }
      }
    }
  }
],

NOTE 2: The build-config creator function must be specified using pure javascript, i.e. without any typescript annotations except for the function type itself (i.e. PluginExportBuildConfigCreator); if the creator function returns a FALSY value, it will be ignored.

If a module defines multiple plugins, build-configs for specific plugins can be specified via PluginBuildConfig, similar to the configuration definitions via PluginConfig.

//define specific build-configs per plugin:
export class PluginBuildConfig {
  /*
   * list of build-configs for plugin asrAndroid
   * NOTE: variables must be defined as const in the main module context (see below)
   */
  asrAndroid: Partial<WebpackAppConfig>[] = [
    simpleBuildConfigLibDependencies,
    {
      includeModules: ['direct/declaration']
    }
  ];

  /*
   * single of build-config for plugin ttsAndroid
   */
  ttsAndroid: PluginExportBuildConfigCreator = function(_pluginConfig, _runtimeConfig, _pluginBuildConfigs) {
    return {
      includeModules: ['member/direct/other/module']
    }
  };
}

/*
 * referenced in specific build-config for asrAndroid
 */
const simpleBuildConfigLibDependencies: WebpackAppConfig = {
  includeModules: ['mmirf/util/extendDeep']
};

/*
 * non-specific build-config (i.e. for all plugins) need to be exported
 */
export const buildConfigLibDependencies: PluginExportBuildConfigCreator = function(pluginConfig, _runtimeConfig, _pluginBuildConfigs) {
  if(pluginConfig && pluginConfig.encoder === 'wav'){
    return {
      includeModules: ['mmir-plugin-encoder-core/workers/wavEncoder']
    }
  }
};

createcompat: Creating Media Plugin Compatibility Modules

running createcompat <dir>

if run with a directory as input:
parses the package.json of the plugin and

  • for each entry in <custom field> mmir.compat.{<name>: <entry>} a compatibility is created:
    • <name>: the source file (relative path within the package)
    • <entry>: the details for generating the compatibility module with {file: <file path>, type: <module type>, exportedName?: string}:
      • file: the target file path where the created module will be stored (within the package)
      • type: the module type, one of "media" | "asr" | "tts" | "custom" | "none" (DEFAULT: "media")
      • exportedName: if type is "custom", the name for the (global) variable must be specified, to which the module will be exported
      • template: if type is "none", the template file into which the original content will be inserted (use /*orig-content*/ as placeholder in the template file to indicate, where the original file content should be inserted)
      • async: (OPTIONAL) in case type is not "media" this boolean value indicates if the plugin's define dependencies need to be required asynchronously (i.e. have not been required before; in case of type "media" the dependencies will already be required asynchronously)
        if specified, an async-wrapper will be generated, but:
        [WARNING] note that the compatibility code will produces errors, if the exported object/function of plugin is immediately used (i.e. works only if exported object/function is used asynchronously)
      • dependencyMapping: (OPTIONAL) mapping for dependencies (in plugin's define statement): will be replaced in the generated compatibility code
        example: dependencyMapping: {"mmirf/events": "./eventEmitter.js"}
      • additionalDependencies: (OPTIONAL) map of additional dependencies (for the plugin's define statement): {<variable name>: <dependency>} will be appended to the existing dependencies in the generated compatibility code
        example: additionalDependencies: {"eventWrapper": "./eventEmitterWrapper.js"}
        NOTE additionalDependencies are not subject to the replacement of dependencyMapping
  • examples:
    • mmir.compat = {"./www/voiceRecorder.js": {"file": "./res/voiceRecorderCompat.js", "type": "custom", "exportedName": "Recorder"}}
    • mmir.compat = {"./www/webAudioInput.js": {"file": "./www/alt/webAudioInputCompat.js","type": "media"}
    • mmir.compat = {"./www/encoder.js": {"file": "./www/alt/encoderCompat.js","type": "none","template":"./res/enconder-compat-template.js"}

dtsdownlevel: Creating Compatibility Typings

running dtsdownlevel <dir> will create backwards compatible typings *.d.ts for use in projects that use typescript < 3.8 (the targeted compatiblity level is typescript version 3.5).

By default, the dtsdownlevel script will copy the downleveled typings to <dir>/ts3.6, which can be made available for backwards compatibility, by adding an entry to the package.json file, e.g. for the input typings directory lib:

  "typesVersions": {
    "<3.9": {
      "lib/*": [
        "lib/ts3.6/*"
      ]
    }
  },

The option --dir <out-dir> allows to specify a custom output directory (as a subdirectory of the input <dir>):
dtsdownlevel <dir> --dir <out-dir>

By default, the dtsdownlevel will abort, if the targeted <out-dir> is not empty. Using the --force flag will empty the <out-dir>, i.e. force writing to the directory, even if it is not empty.

Cordova Helper Scripts for MODE

The helper scripts will add support for cordova configuration/variable entry MMIR_PLUGIN_MODE (in plugin's plugin.xml or the using project's config.xml), for using mode dependent implementation files.

Supported modes are

  • "normal": the default implementation
  • "compat": the compatibility implementation file(s)
  • "wepack": special modus for webpack driven builds:
    if webpack includes the implementation files during the build process, empty webpack-mode files can be added, to avoid including duplicate/unused source files.

For example, a plugin may specify in its plugin.xml

    <!-- MMIR_PLUGIN_MODE: "normal" | "compat" | "webpack" -->
    <preference name="MMIR_PLUGIN_MODE" default="normal" />
    <hook src="res/js/before_plugin_install_hook.js" type="before_plugin_install" />

When the cordova plugin is installed, it will read the compat-configuration of the plugin's package.json in custom field "mmir", e.g.

"mmir": {
  "compat": {
    "./www/myImpl.js": {
      "file": "./www/alt/myImplCompat.js",
      "type": "asr"
    }
  }
},

and use its source- and target-directory for applying the webpack or compat mode: for any file of the source directory for which a file with suffix Webpack (for webpack mode), or Compat (for compat mode), the replacement file will be used in case the corresponding mode is activated.

In the example above, the source directory would be "./www" and the target directory where the mode-files need to be located, would be at "./www/alt".

A specific mode can be activated in various ways:

  • as variable via a preference-tag in the plugin's plugin.xml (i.e. setting the default mode):
    <variable name="MMIR_PLUGIN_MODE" value="normal" />
  • via command-line arguement when installing the plugin:
    cordova plugin add ... --variable MMIR_PLUGIN_MODE=webpack
  • by a preference-tag in the using-project's config.xml:
    <preference name="MMIR_PLUGIN_MODE" value="webpack" />
    NOTE if the preference-tag is added to config.xml before installing/adding the cordova plugin, the command-line argument can be omitted

copycordovascripts: Install / Copy Scripts into Plugin

after adding entry into package.json:

"scripts": {
  "install-cordova-scripts": "copycordovascripts res/js"
}

running the command:

npm run install-cordova-scripts

will copy the cordova helper scripts into the plugin's sub-directory res/js/.

updateversion: Update version Field in npm and cordova Configuration Files

The script

updateversion <directory> --set-version <version>

allows to update the version in npm and cordova configuration files without reformatting the files.

The target directory will be scanned for the following configuration files:

  • package.json
  • package-lock.json (needs to explicitly enabled with flag --enable-package-lock)
  • config.xml (cordova app project configuration)
  • plugin.xml (cordova plugin project configuration)

Updating for specific files can be disabled by --disable-<package | config | plugin>.

If no version is specified with --set-version <version>, the version is read from package.json by default, and the other configuration files are updated with it.

Alternatively, the version can be read from another configuration file using --from-<package | config | plugin>.

In addition, the script may target a specific file, or multiple files and directories

updateversion <file1> <directory1> <file2> --set-version <version>

For writing version information to arbitrary text files, regluar rexpressions can be used. However, if specifying an regular expression, it cannot target files that were discovered by parsing an directory, i.e. the file must be specifically be given.

The regular expression must be specified in form of a JavaScript RegExp literal, e.g.

/^(my-exprsion) matches$/i

If the expression contains spaces, it must be wrapped in quotes.

Example:

updateversion <file> --version-regexp "/@version \d+\.\d+\.\d+/i" --from-package

For conveinance, §VERSION§ can be used as a placeholder to match version strings that follow the semantic versioning convention (e.g. 1.0.4-alpha):

updateversion <file> --version-regexp "/@version §VERSION§/i" --from-package

If capture groups are used in the regular expression, an additional argument allows to specify a replacement pattern which can contain references to the capture groups $<group number> (where the first group has number 1).
The version number (either specified by --set-version or read from a configuration file) can be referenced by the special group number 0, i.e. $0.

Example:

updateversion <file> --version-regexp "/(@version) \d+\.\d+\.\d+/i" --replace-pattern "$1 $0" --from-package

which would a replacement string for the version number <version>: "@version <version>" (i.e. instead of replacing the complete match with the version number)

Note that §VERSION§ defines one capture groupt itself, e.g.

# in this example capture group $2 would contain the original (found) version string
updateversion <file> --version-regexp "/(@version) §VERSION§( production)/i" --replace-pattern "$1 $0$3" --from-package

For conveinance, the script can be added to the package.json after installing the package, e.g. the following will update the version that is read from the package.json file, and will update all supported configuration files (inlcuding package-lock.json), except for the plugin.xml file:

"scripts": {
  "update-version": "updateversion ./ --enable-package-lock --disable-plugin"
}

Package Sidebar

Install

npm i mmir-plugin-exports

Weekly Downloads

126

Version

2.7.0

License

MIT

Unpacked Size

220 kB

Total Files

40

Last publish

Collaborators

  • russa