From 4748e8c87f3c61dfff76d554c0d289e50a9b3b94 Mon Sep 17 00:00:00 2001 From: Luke Schoen Date: Fri, 12 Oct 2018 21:15:01 +0200 Subject: [PATCH] docs: Relates to #193. Generate Substrate Interface methods in API docs for RPC, Storage, and Extrinsics from Metadata (#265) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: Relates to #193. Generate Methods in API docs to Substrate Interfaces Extrinsics and Storage from Metadata * Steps to generate Gitbook: ``` yarn run build:methodsdoc; yarn run build; yarn gitbook serve; ``` * Allows generating a docs/METHODS_STORAGE.md and docs/METHODS_EXTRINSICS.md files by running `yarn run build:methodsdoc` * Note: The script runs `node packages/types/src/MetadataMdWrapper.js`, which uses Babel.js to transpile the TypeScript file MetadataMd.ts into JavaScript as MetadataMd.js by using the babel-plugin-module-resolver dependency that has been added to package.json. It runs MetadataMd.js, which parses a hard-coded copy of a Metadata response from the Substrate Node, and then generates the two aforementioned Markdown files to be included in the Gitbook. * Important Note: DO NOT run `./node_modules/.bin/tsc packages/types/src/MetadataMd.ts; node packages/types/src/MetadataMd.js;`, as doing so may incorrectly transpile other TypeScript files, and will give errors like `TypeError: bn_js_1.default is not a constructor` when you run your unit tests. * Added links that work when you go to http://localhost:4000/api/ and click the "api" link on the side menu * Note: This was achieved by first adding the links in packages/api/README.md (i.e. `[Extrinsics (Substrate Interface)](../METHODS_EXTRINSICS.md)`). Note that the links could also be `METHODS_EXTRINSICS.html` and it would still work. It is VERY important to add links to these Markdown files in docs/SUMMARY.md (i.e. `[Storage](METHODS_STORAGE.md)`), as Gitbook generates HTML files in the same docs/ directory (i.e. it generates docs/METHODS_STORAGE.html) for any links provided in that file, and when you run `yarn run build; yarn gitbook serve` it will put a copy of those HTML files in the _book directory so it can be accessed in the browser. * Replaces PR #249 and #255 from jg-meta-md from jg-meta-md-luke branches * feat: Add script to generate decoded JSON file of the Metadata. Remove test. * Summary: * Running `yarn run build:decodedjson` will generate packages/types/src/Metadata.decoded.json from the Metadata in the Metadata.rpc.ts file. * docs: Manually add RPC Substrate Interface API docs including author, chain, state, system * docs: Append Storage Substrate methods to Storage Methods. Add consistent styling. Add descriptions * Add Description at top of Substrate Interface page for RPC, Extrinsics, and Storage in API docs * Add styling to Substrate Interface page for Extrinsics and Storage, including: * ▸ symbol before each method * bold method names * adding Summary label and only showing the summary label if a summary exists * adding a horizonal line between each section of methods * Fix by adding new line after description when there is one in Extrinscis and Storage * Manually creating a markdown file containing storage.substrate methods METHODS_STORAGE_SUBSTRATE.md, and then reading from the file and appending it to the bottom of the METHODS_STORAGE.md file that is generated, since the storage.substrate methods are not available from the Metadata. * Fix by reading and writing to file using UTF8 instead of ASCII so symbols appear correctly * Updating docs/METHODS_RPC.md and adding a horizontal line before the first section of methods * docs: Fix Readme file step numbering * docs: Generate the Substrate Interface Markdown files first when run yarn run build * refactor: Move babel register config into shared file * refactor: Use shared function to overcome codeclimate error * fix: Reword description of RPC methodsto address review comment * docs: Rename to Runtime Node Interfaces to address review comment * docs: Rename to Runtime Node Interface in api Readme to address review comment * docs: Fix minor typos * docs: Fixes #117 providing Readme ref to jsonrpc docs * fix: Remove preceding whitespace * docs: Rename so RPC is not referred to as an interface of the runtime * docs: Refer to Storage Substrate methods as always available to runtime implementation * test: Restore the test that was unnecessary deleted * Move scripts into packages/types/src/scripts * docs: Add script to generate RPC interface docs * fix: Fix TSlint error where element had any time due to no index signature * refactor: Restore code back to normal instead of bowing to codeclimate whims * refactor: Declare meaningful variables to make more readable * docs: Show .subscribe when it is isSubscription method --- README.md | 4 +- docs/SUMMARY.md | 5 + package.json | 5 +- packages/api/README.md | 13 +- packages/rpc-provider/README.md | 4 +- packages/type-jsonrpc/README.md | 2 +- packages/type-jsonrpc/src/chain.ts | 2 +- packages/type-jsonrpc/src/index.ts | 4 +- packages/types/babelConfig.js | 15 ++ packages/types/src/Metadata.spec.js | 1 - .../src/scripts/METHODS_STORAGE_SUBSTRATE.md | 26 +++ .../types/src/scripts/MetadataJsonDecode.ts | 29 ++++ .../src/scripts/MetadataJsonDecodeWrapper.js | 7 + packages/types/src/scripts/MetadataMd.ts | 150 ++++++++++++++++++ .../types/src/scripts/MetadataMdWrapper.js | 7 + 15 files changed, 260 insertions(+), 14 deletions(-) create mode 100644 packages/types/babelConfig.js create mode 100644 packages/types/src/scripts/METHODS_STORAGE_SUBSTRATE.md create mode 100644 packages/types/src/scripts/MetadataJsonDecode.ts create mode 100644 packages/types/src/scripts/MetadataJsonDecodeWrapper.js create mode 100644 packages/types/src/scripts/MetadataMd.ts create mode 100644 packages/types/src/scripts/MetadataMdWrapper.js diff --git a/README.md b/README.md index 90d7807956..b285bbaacc 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,8 @@ To get started - 3. Ensure that you have a recent version of Yarn, for development purposes [Yarn >=1.3.2](https://yarnpkg.com/docs/install) is required. 4. Install the dependencies by running `yarn` 5. Build the API Docs, via `yarn run build` -5. Ready! Now you can launch the API Docs, via `yarn gitbook serve` -6. Access the API Docs via [http://localhost:4000](http://localhost:4000) +6. Ready! Now you can launch the API Docs, via `yarn gitbook serve` +7. Access the API Docs via [http://localhost:4000](http://localhost:4000) ## tutorials diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index a8e1d68f6e..d90af1bf54 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -7,6 +7,11 @@ - [HttpProvider](rpc-provider/classes/_http_index_.httpprovider.md) - [WsProvider](rpc-provider/classes/_ws_index_.wsprovider.md) +## Node Interfaces +- [RPC](METHODS_RPC.md) +- [Storage chain state (runtime)](METHODS_STORAGE.md) +- [Extrinsics (runtime)](METHODS_EXTRINSICS.md) + ## Examples - [ApiPromise](examples/promise/README.md) diff --git a/package.json b/package.json index 6335fa8379..eae0a9ee2d 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,10 @@ "typescript": "^3.0.1" }, "scripts": { - "build": "polkadot-dev-build-ts && polkadot-dev-build-docs", + "build": "yarn run build:methodsdoc && polkadot-dev-build-ts && polkadot-dev-build-docs", "build:htmldoc": "yarn clean && typedoc --theme default --out docs/html", + "build:methodsdoc": "node packages/types/src/scripts/MetadataMdWrapper.js", + "build:decodedjson": "node packages/types/src/scripts/MetadataJsonDecodeWrapper.js", "check": "tslint --project . && tsc --noEmit", "clean": "polkadot-dev-clean-build", "postinstall": "polkadot-dev-yarn-only", @@ -25,6 +27,7 @@ "@polkadot/ts": "^0.1.30" }, "dependencies": { + "babel-plugin-module-resolver": "^3.1.1", "gitbook-plugin-include-codeblock": "^3.2.2", "gitbook-plugin-mermaid-gb3": "^2.1.0", "gitbook-plugin-sunlight-highlighter": "^0.4.3" diff --git a/packages/api/README.md b/packages/api/README.md index 2f5de34fb6..d6929dd0a1 100644 --- a/packages/api/README.md +++ b/packages/api/README.md @@ -4,22 +4,25 @@ The Polkadot-JS API provides easy-to-use wrappers around JSONRPC calls that flow The API wrappers provide a standard interface for use - -- A static `.create()` that returns an API istance when connected, decorated and ready-to use +- A static `.create()` that returns an API instance when connected, decorated and ready-to use - The above is just a wrapper for `new Api() `, exposing the `isReady` getter - `api.rpc.
.` provides access to actual RPC calls, be it for queries, submission or retrieving chain information + - [RPC (node interface)](../METHODS_RPC.md) - `api.query.
.` provides access to chain state queries. These are dynamically populated based on what the runtime provides -- `api.tx.
.` provides the ability to create transaction, like chain state, this list is populated from a runtime query + - [Storage chain state (runtime node interface)](../METHODS_STORAGE.md) +- `api.tx.
.` provides the ability to create a transaction, like chain state, this list is populated from a runtime query + - [Extrinsics (runtime node interface)](../METHODS_EXTRINSICS.md) ## API Selection There are two flavours of the API provided, one allowing a standard interface via JavaScript Promises and the second provides an Observable wrapper using [RxJS](https://github.com/ReactiveX/rxjs). Depending on your use-case and familiarity, you can choose either (or even both) for your application. -- [[ApiPromise]] All interface calls returns Promises, including the static `.create(...)`. Additionally any subscription method use `(value) => {}` callbacks, returning the value as the subscription is updated. -- [[ApiRx]] All interface calls return RxJS Observables, including the static `.create(...)`. In the same fashion and subscription-based methods return long-running Observables that update with the latest values. +- [[ApiPromise]] All interface calls returns Promises, including the static `.create(...)`. Additionally any subscription method uses `(value) => {}` callbacks, returning the value as the subscription is updated. +- [[ApiRx]] All interface calls return RxJS Observables, including the static `.create(...)`. In the same fashion subscription-based methods return long-running Observables that update with the latest values. ## Dynamic by default -Subtrate (on which Polkadot is built) used on-chain on-chain WASM runtimes, allowing for upgradability. Each runtime defining the actual chain extrinsics (submitted transactions and block intrinsics) as well as available entries in the chain state. Due to this, the API endpoints for queries and transactions are dynamically populated from the running chain. +Substrate (upon which Polkadot is built) uses on-chain WASM runtimes, allowing for upgradability. Each runtime defining the actual chain extrinsics (submitted transactions and block intrinsics) as well as available entries in the chain state. Due to this, the API endpoints for queries and transactions are dynamically populated from the running chain. Due to this dynamic nature, this API departs from traditional APIs which only has fixed endpoints, driving use by what is available by the runtime. As a start, this generic nature has a learning curve, although the provided documentation, examples and linked documentation tries to make that experience as seamless as possible. diff --git a/packages/rpc-provider/README.md b/packages/rpc-provider/README.md index 932c694953..6be0c2fc90 100644 --- a/packages/rpc-provider/README.md +++ b/packages/rpc-provider/README.md @@ -1,10 +1,10 @@ # @polkadot/rpc-provider -Generic transport providers to handle the transport of method calls to and from Polkadot clients from applications interacting with it. It provides an interface to making RPC class and is generally, unless you are operating at a low-level and taking care of encoding and decoding of parameters/results, it won't be directly used, rather only passed to a higher-level interface. +Generic transport providers to handle the transport of method calls to and from Polkadot clients from applications interacting with it. It provides an interface to making RPC calls and is generally, unless you are operating at a low-level and taking care of encoding and decoding of parameters/results, it won't be directly used, rather only passed to a higher-level interface. ## Provider Selection -There are two flavours of the providers provided, one allowing for using HTTP as a transport machanism, the other using WebSockets. It is generally recommended to use the [[WsProvider]] since in addition to standard calls, it allows for subscriptions where all changes to state can be pushed from the node to the client. +There are two flavours of the providers provided, one allowing for using HTTP as a transport mechanism, the other using WebSockets. It is generally recommended to use the [[WsProvider]] since in addition to standard calls, it allows for subscriptions where all changes to state can be pushed from the node to the client. Both providers are usable (as is the API), in both browser-based and Node.js environments. Polyfills for unsupported functionality are automatically applied based on feature-detection. diff --git a/packages/type-jsonrpc/README.md b/packages/type-jsonrpc/README.md index acd647a5be..d44358efd5 100644 --- a/packages/type-jsonrpc/README.md +++ b/packages/type-jsonrpc/README.md @@ -2,7 +2,7 @@ A definition of all the methods exposed in a general Polkadot client application. These are used not only to provide a comprehensive code-generated document of the available methods, but are also used in the API to auto-generate endpoints with the required type-checking. -For a list of currently exposed methods, see the [method documentation](docs/README.md). +For a list of currently exposed methods, see the [method documentation](docs/METHODS_RPC.md). ## Usage diff --git a/packages/type-jsonrpc/src/chain.ts b/packages/type-jsonrpc/src/chain.ts index e7d2caa866..2019becd01 100644 --- a/packages/type-jsonrpc/src/chain.ts +++ b/packages/type-jsonrpc/src/chain.ts @@ -40,7 +40,7 @@ const getHead: RpcMethodOpt = { }; const getRuntimeVersion: RpcMethodOpt = { - description: ' Get the runtime version', + description: 'Get the runtime version', params: [ createParam('hash', 'Hash', { isOptional: true }) ], diff --git a/packages/type-jsonrpc/src/index.ts b/packages/type-jsonrpc/src/index.ts index f393c0bcfd..a926ffac3d 100644 --- a/packages/type-jsonrpc/src/index.ts +++ b/packages/type-jsonrpc/src/index.ts @@ -2,12 +2,14 @@ // This software may be modified and distributed under the terms // of the ISC license. See the LICENSE file for details. +import { RpcSection } from './types'; + import author from './author'; import chain from './chain'; import state from './state'; import system from './system'; -const interfaces = { +const interfaces: { [key: string]: RpcSection } = { author, chain, state, diff --git a/packages/types/babelConfig.js b/packages/types/babelConfig.js new file mode 100644 index 0000000000..ac9863dc5e --- /dev/null +++ b/packages/types/babelConfig.js @@ -0,0 +1,15 @@ +#!/usr/bin/env node +// Copyright 2017-2018 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +require('@babel/register')({ + extensions: ['.js', '.ts'], + plugins: [ + ['module-resolver', { + alias: { + '^@polkadot/types(.*)': './packages/types/src\\2' + } + }] + ] +}); diff --git a/packages/types/src/Metadata.spec.js b/packages/types/src/Metadata.spec.js index 0b84f7e049..f9dd2de6b5 100644 --- a/packages/types/src/Metadata.spec.js +++ b/packages/types/src/Metadata.spec.js @@ -8,7 +8,6 @@ import rpcdata from './Metadata.rpc'; describe('Metadata', () => { it('decodes properly', () => { const decoded = new Metadata().fromJSON(rpcdata); - const str = JSON.stringify(decoded.toJSON()); console.error(str); diff --git a/packages/types/src/scripts/METHODS_STORAGE_SUBSTRATE.md b/packages/types/src/scripts/METHODS_STORAGE_SUBSTRATE.md new file mode 100644 index 0000000000..374b0826c7 --- /dev/null +++ b/packages/types/src/scripts/METHODS_STORAGE_SUBSTRATE.md @@ -0,0 +1,26 @@ + +--- + +### substrate + +_These are keys that are always available to the runtime implementation_ + +▸ **code**(): `Bytes` +- **summary**: Wasm code of the runtime. + +▸ **heapPages**(): `u64` +- **summary**: Number of wasm linear memory pages required for execution of the runtime. + +▸ **authorityCount**(): `u32` +- **summary**: Number of authorities. + +▸ **authorityPrefix**(): `u32` +- **summary**: Prefix under which authorities are storied. + +▸ **extrinsicIndex**(): `u32` +- **summary**: Current extrinsic index (u32) is stored under this key. + +▸ **changesTrieConfig**(): `u32` +- **summary**: Changes trie configuration is stored under this key. + +--- diff --git a/packages/types/src/scripts/MetadataJsonDecode.ts b/packages/types/src/scripts/MetadataJsonDecode.ts new file mode 100644 index 0000000000..1478d94ec5 --- /dev/null +++ b/packages/types/src/scripts/MetadataJsonDecode.ts @@ -0,0 +1,29 @@ +// Copyright 2017-2018 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +const fs = require('fs'); + +import Metadata from '../Metadata'; +import rpcdata from '../Metadata.rpc'; + +function metadataStringifyAsJson () { + const decoded = new Metadata().fromJSON(rpcdata); + + return JSON.stringify(decoded.toJSON(), null, 2); +} + +function writeToJson () { + const options = { encoding: 'ascii' }; + const writeStream = fs.createWriteStream('packages/types/src/Metadata.decoded.json', options); + + writeStream.write(metadataStringifyAsJson()); + + writeStream.on('finish', () => { + console.log('wrote all decoded metadata to JSON file'); + }); + + writeStream.end('\n'); +} + +writeToJson(); diff --git a/packages/types/src/scripts/MetadataJsonDecodeWrapper.js b/packages/types/src/scripts/MetadataJsonDecodeWrapper.js new file mode 100644 index 0000000000..c4af47e451 --- /dev/null +++ b/packages/types/src/scripts/MetadataJsonDecodeWrapper.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node +// Copyright 2017-2018 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +require('../../babelConfig.js'); +require('./MetadataJsonDecode.ts'); diff --git a/packages/types/src/scripts/MetadataMd.ts b/packages/types/src/scripts/MetadataMd.ts new file mode 100644 index 0000000000..0db606eb1b --- /dev/null +++ b/packages/types/src/scripts/MetadataMd.ts @@ -0,0 +1,150 @@ +// Copyright 2017-2018 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +const fs = require('fs'); +import { stringCamelCase, stringLowerFirst } from '@polkadot/util/string'; + +import interfaces from '../../../type-jsonrpc/src'; +import Metadata from '../Metadata'; +import rpcdata from '../Metadata.rpc'; +import Method from '../Method'; + +// some intro text goes in here +const DESC_RPC = '\n\n_The following RPC methods are the Remote Calls that are available by default and allow you to interact with the actual node, query, and submit. The RPCs are provided by Substrate itself. The RPCs are never exposed by the runtime._'; +const DESC_EXTRINSICS = '\n\n_The following Extrinsics methods are part of the default Substrate runtime. Since an Extrinsic is a holder of an object that is just an array of bytes to be included, it does not have a return._'; +const DESC_STORAGE = '\n\n_The following Storage methods are part of the default Substrate runtime._'; + +function addRpc () { + const renderHeading = `## JSON-RPC${DESC_RPC}\n`; + + return Object.keys(interfaces).reduce((md, sectionName) => { + const section = interfaces[sectionName]; + const renderSection = `${md}\n___\n\n### ${sectionName}\n\n_${section.description}_\n`; + + return Object.keys(section.methods).reduce((md, methodName) => { + const method = section.methods[methodName]; + const args = method.params.map(({ name, isOptional, type }) => { + return name + (isOptional ? '?' : '') + ': `' + type + '`'; + }).join(', '); + const type = '`' + method.type + '`'; + const isSub = method.isSubscription; + const renderMethod = `${md}\n▸ **${methodName}**(${args})`; + const renderReturnType = `: ${type}`; + const renderSignature = `${renderMethod}${renderReturnType}`; + const renderSignatureSub = `${renderMethod}**.subscribe**(CALLBACK)${renderReturnType}`; + const renderSummary = `${method && method.description ? `\n- **summary**: ${method.description}\n` : `\n\n`}`; + + return isSub ? `${renderSignatureSub}${renderSummary}` : `${renderSignature}${renderSummary}`; + }, renderSection); + }, renderHeading); +} + +function addExtrinsics (metadata: any) { + const renderHeading = `## Extrinsics${DESC_EXTRINSICS}`; + + return metadata.modules.reduce((md: string, meta: any) => { + if (!meta.module.call || !meta.module.call.functions.length) { + return md; + } + + const sectionName = stringCamelCase(meta.prefix.toString()); + const renderSection = `${md}\n___\n### ${sectionName}\n`; + + return meta.module.call.functions.reduce((md: string, func: any) => { + const methodName = stringCamelCase(func.name.toString()); + const args = Method.filterOrigin(func).map(({ name, type }) => `${name}: ` + '`' + type + '`').join(', '); + const doc = func.documentation.reduce((md: string, doc: string) => `${md} ${doc}`, ''); + const renderSignature = `${md}\n▸ **${methodName}**(${args})`; + const renderSummary = `${doc ? `\n- **summary**: ${doc}\n` : '\n'}`; + + return renderSignature + renderSummary; + }, renderSection); + }, renderHeading); +} + +function addStorage (metadata: any) { + const renderHeading = `## Storage${DESC_STORAGE}`; + + return metadata.modules.reduce((md: string, moduleMetadata: any) => { + if (!moduleMetadata.storage) { + return md; + } + + const sectionName = stringLowerFirst(moduleMetadata.storage.prefix.toString()); + const renderSection = `${md}\n___\n### ${sectionName}\n`; + + return moduleMetadata.storage.functions.reduce((md: string, func: any) => { + const methodName = stringLowerFirst(func.name.toString()); + const arg = func.type.isMap ? ('`' + func.type.asMap.key.toString() + '`') : ''; + const doc = func.documentation.reduce((md: string, doc: string) => `${md} ${doc}`, ''); + const renderSignature = `${md}\n▸ **${methodName}**(${arg}): ` + '`' + func.type + '`'; + const renderSummary = `${doc ? `\n- **summary**: ${doc}\n` : '\n'}`; + + return renderSignature + renderSummary; + }, renderSection); + }, renderHeading); +} + +function metadataStorageMethodsAsText () { + const metadata = new Metadata().fromJSON(rpcdata); + return addStorage(metadata); +} + +function metadataExtrinsicsMethodsAsText () { + const metadata = new Metadata().fromJSON(rpcdata); + return addExtrinsics(metadata); +} + +function writeToRpcMd () { + const options = { flags: 'w', encoding: 'utf8' }; + const writeStream = fs.createWriteStream('docs/METHODS_RPC.md', options); + + writeStream.write(addRpc()); + + writeStream.on('finish', () => { + console.log('wrote all rpc method metadata to Gitbook Markdown file'); + }); + + writeStream.end(); +} + +function writeToStorageMd () { + const optionsRead = { flags: 'r', encoding: 'utf8' }; + fs.readFile('packages/types/src/scripts/METHODS_STORAGE_SUBSTRATE.md', optionsRead, function read (err: Error, data: string) { + if (err) { + throw err; + } + + // 'utf8', 'ascii', 'binary', 'hex', 'base64', or 'utf16le' + const options = { flags: 'w', encoding: 'utf8' }; + const writeStream = fs.createWriteStream('docs/METHODS_STORAGE.md', options); + + writeStream.write(metadataStorageMethodsAsText()); + writeStream.write(data); + + // finish emitting event when all data flushed from stream + writeStream.on('finish', () => { + console.log('wrote all storage method metadata to Gitbook Markdown file'); + }); + + writeStream.end(); + }); +} + +function writeToExtrinsicsMd () { + const options = { flags: 'w', encoding: 'utf8' }; + const writeStream = fs.createWriteStream('docs/METHODS_EXTRINSICS.md', options); + + writeStream.write(metadataExtrinsicsMethodsAsText()); + + writeStream.on('finish', () => { + console.log('wrote all extrinsics method metadata to Gitbook Markdown file'); + }); + + writeStream.end(); +} + +writeToRpcMd(); +writeToStorageMd(); +writeToExtrinsicsMd(); diff --git a/packages/types/src/scripts/MetadataMdWrapper.js b/packages/types/src/scripts/MetadataMdWrapper.js new file mode 100644 index 0000000000..43eeea7468 --- /dev/null +++ b/packages/types/src/scripts/MetadataMdWrapper.js @@ -0,0 +1,7 @@ +#!/usr/bin/env node +// Copyright 2017-2018 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +require('../../babelConfig.js'); +require('./MetadataMd.ts');