diff --git a/.github/workflows/github-ci.yml b/.github/workflows/github-ci.yml index f36e83db9..b7b3e8255 100644 --- a/.github/workflows/github-ci.yml +++ b/.github/workflows/github-ci.yml @@ -6,7 +6,7 @@ on: - 2.x.x-release pull_request: release: - types: [published] + types: [ published ] env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -29,8 +29,8 @@ jobs: needs: danger strategy: matrix: - node-version: [10.x] - project: [frontend, api, blockchain, provisioning] + node-version: [ 18.x ] + project: [ frontend, api, blockchain, provisioning ] defaults: run: working-directory: ${{ matrix.project }} @@ -41,10 +41,10 @@ jobs: audit: runs-on: ubuntu-latest - needs: [danger, lint] + needs: [ danger, lint ] strategy: matrix: - node-version: [10.x] + node-version: [ 18.x ] project: [ frontend, @@ -56,6 +56,7 @@ jobs: email-notification-service, storage-service, logging-service, + migration ] defaults: @@ -72,11 +73,11 @@ jobs: test: runs-on: ubuntu-latest - needs: [danger, lint, audit] + needs: [ danger, lint, audit ] strategy: matrix: - node-version: [10.x] - project: [frontend, api] + node-version: [ 18.x ] + project: [ frontend, api ] defaults: run: working-directory: ${{ matrix.project }} @@ -103,7 +104,7 @@ jobs: build: runs-on: ubuntu-latest - needs: [danger, lint, audit, test] + needs: [ danger, lint, audit, test ] strategy: matrix: @@ -118,6 +119,7 @@ jobs: email-notification-service, storage-service, logging-service, + migration ] include: - project: frontend @@ -138,6 +140,8 @@ jobs: image_name: trubudget/storage-service - project: logging-service image_name: trubudget/logging-service + - project: migration + image_name: trubudget/migration defaults: run: working-directory: ${{ matrix.project }} @@ -161,7 +165,7 @@ jobs: notification: runs-on: ubuntu-latest - needs: [lint, test, danger, audit, build] + needs: [ lint, test, danger, audit, build ] env: GITHUB_BASE_REF: ${{ github.base_ref}} GITHUB_HEAD_REF: ${{ github.head_ref}} diff --git a/migration/.dockerignore b/migration/.dockerignore new file mode 100644 index 000000000..cb7c7de56 --- /dev/null +++ b/migration/.dockerignore @@ -0,0 +1,4 @@ +dist +node_modules +.env* +*.gz \ No newline at end of file diff --git a/migration/.env.example b/migration/.env.example new file mode 100644 index 000000000..cd85280b1 --- /dev/null +++ b/migration/.env.example @@ -0,0 +1,14 @@ +DESTINATION_API_BASE_URL="http://localhost:8080" +SOURCE_RPC_PORT= +SOURCE_RPC_USER= +SOURCE_RPC_PASSWORD= +SOURCE_RPC_HOST= +DESTINATION_RPC_PORT= +DESTINATION_RPC_USER= +DESTINATION_RPC_PASSWORD= +DESTINATION_RPC_HOST= +BACKUP_FILE_LOCATION= +DESTINATION_BLOCKCHAIN_BASE_URL="http://localhost:8085" +ROOT_SECRET= +ORGANIZATION= +MIGRATION_USER_PASSWORD= diff --git a/migration/.gitignore b/migration/.gitignore new file mode 100644 index 000000000..79f185284 --- /dev/null +++ b/migration/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +.env +dist/ +.idea/ +.DS_Store +*.gz \ No newline at end of file diff --git a/migration/Dockerfile b/migration/Dockerfile new file mode 100644 index 000000000..1d131a909 --- /dev/null +++ b/migration/Dockerfile @@ -0,0 +1,12 @@ +FROM node:18.4.0-alpine + +WORKDIR /home/node + +COPY package*.json ./ +RUN npm ci + +COPY src/ src/ +COPY tsconfig.json . +RUN npm run build + +CMD ["npm" ,"start"] \ No newline at end of file diff --git a/migration/README.md b/migration/README.md new file mode 100644 index 000000000..fe489cbfd --- /dev/null +++ b/migration/README.md @@ -0,0 +1,98 @@ +# TruBudget Migration Script + +### Migrate an existing TruBudget instance to a new instance + +1. Create a backup of your old TruBudget instance. Make sure the source instance uses version 1.30.1 +2. Download the latest TruBudget release (2.x), this instance will be the destination instance. +3. With the release of TruBudget 2.0, some environment variables changed. Make sure to adapt the `.env` file on the + destination instance to your need. If you are not already using the operation script now it's a great chance to start + using it! +4. Set the following environment variables in the .env file of the destination instance + +- `AUTOSTART: false` +- `NODE_ENV: development` + +7. Use the operation script to bootstrap the new set-up. Make sure to enable all desired features (i.e. + storage-service). **If you do not use the operation script make sure the provisioning is disabled!** +8. Copy the `.env.example` of the migration script to `.env` & set all variables accordingly. +9. Run the migration script using `npm run start` +10. Once the migration finished make sure to set following environment variables on the destination instance by + changing the `.env` file + - AUTOSTART: true + - NODE_ENV: production + +#### Environment Variables + +In the following the required environment variables can be found. All the described variables are required variables. +Most of them can be found in + +| Env Variable | Description | +| ------------------------------- | ------------------------------------------------ | +| DESTINATION_API_BASE_URL | The base url of the destination api | +| SOURCE_RPC_PORT | The RPC port of the source multichain | +| SOURCE_RPC_USER | The RPC user of the source multichain | +| SOURCE_RPC_PASSWORD | The RPC password of the source multichain | +| SOURCE_RPC_HOST | The host of the source multichain | +| DESTINATION_RPC_PORT | The RPC port of the destination multichain | +| DESTINATION_RPC_USER | The RPC user of the destination multichain | +| DESTINATION_RPC_PASSWORD | The RPC password of the destination multichain | +| DESTINATION_RPC_HOST | The host of the destination multichain | +| BACKUP_FILE_LOCATION | The **absolute path** to the backup file | +| DESTINATION_BLOCKCHAIN_BASE_URL | Base url of the blockchain | +| ROOT_SECRET | The root users password | +| ORGANIZATION | The name of the organization you want to migrate | +| MIGRATION_USER_PASSWORD | The password used for the migration user | + +#### FAQ + +- **Will the migrated data be modified during migration?** No, per default the data remains untouched. You can adapt + some parameters tho if you want to. For this head over to the development section. +- **Does the user see, that a migration happened?** Yes, there will be marker in the history tab of every workflow item, + indicating that the data has been copy to the chain via a script. +- **Why does the user need to see that a migration happened?** One of the main reasons for using blockchains, is the + fact, that once the data is written, no one can edit the data anymore. If data gets copied form one instance to + another, it's important that the user knows this has happened to provide a layer of transparency. +- **Why upgrade anyway to 2.x (or further)?** As we continuously work on improving TruBudget, we sometimes have to make + big changes to the system which are not compatible with prior versions of TruBudget. For more information have a look + at the TruBudget Changelog + +### Development + +#### Structure + +``` +. +├── src/ +│ ├── customMigration: custom migration procedures for different streams +│ ├── helper: a collection of helper +│ ├── types: type definitions +│ ├── assert: a collection of methods to check if sour & destination data is the same +│ ├── migrate : core logic migration process is managed here +│ ├── index: entry point +│ └── rpc: abstraction to interact with multichain +└── .env.example: example of the enviroment variables +``` + +#### Run + +1. Follow step "Migrate an existing TruBudget instance to a new instance" to create all required instances +2. Set env variables +3. Run via: + `npm i` + `npm run dev` +4. Happy coding! + +#### The migration function + +By default, the function `migrate` copy all stream items on each stream on the chain and check that the data filed on +the destination chain has not been changed. In some cases this behavior is not desired since the data should not be +copied to the chain but rather written by the API to the chain. +For this reason, the migration function accepts an additional parameter `customMigrations`. +Here you can pass your own migration function to the script. A custom migration function consists of a `stream` +, `function` and +a `verifyer`. The `stream` describes for which stream the function will be applied. The `function` is the responsible +for moving the stream item from one chain to another. The `verifyer` is responsible for asserting the correctness of the +moved stream item on the destination chain (or API depending on what you want to move / assert). + +There are two custom migration functions implemented by default which handle the transfer of files. These can be +used as example for future implementations. diff --git a/migration/docker-compose.yaml b/migration/docker-compose.yaml new file mode 100644 index 000000000..d94edf5a6 --- /dev/null +++ b/migration/docker-compose.yaml @@ -0,0 +1,21 @@ +version: "3" +services: + migration: + build: + context: . + dockerfile: Dockerfile + environment: + SOURCE_RPC_PORT: 8000 + SOURCE_RPC_USER: multichainrpc + SOURCE_RPC_PASSWORD: k1oR5s12kY93NaUz1w5nQHJynqTtLw8Lk68E7KwWHQfW + SOURCE_RPC_HOST: #todo + DESTINATION_API_BASE_URL: + DESTINATION_RPC_PORT: 8000 + DESTINATION_RPC_USER: multichainrpc + DESTINATION_RPC_PASSWORD: k1oR5s12kY93NaUz1w5nQHJynqTtLw8Lk68E7KwWHQfW + DESTINATION_RPC_HOST: #todo + BACKUP_FILE_LOCATION:#todo + DESTINATION_BLOCKCHAIN_BASE_URL: #todo + ROOT_SECRET: root-secret + ORGANIZATION: KfW + MIGRATION_USER_PASSWORD: test diff --git a/migration/nodemon.json b/migration/nodemon.json new file mode 100644 index 000000000..796240c96 --- /dev/null +++ b/migration/nodemon.json @@ -0,0 +1,12 @@ +{ + "ignore": [ + "**/*.test.ts", + "**/*.spec.ts", + "node_modules" + ], + "watch": [ + "src" + ], + "exec": "ts-node ./src/index.ts", + "ext": "ts" +} diff --git a/migration/package-lock.json b/migration/package-lock.json new file mode 100644 index 000000000..1957df807 --- /dev/null +++ b/migration/package-lock.json @@ -0,0 +1,1839 @@ +{ + "name": "trubudget-migration", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "trubudget-migration", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "async": "^3.2.3", + "axios": "^0.24.0", + "dotenv": "^10.0.0", + "lodash": "^4.17.21", + "multichain-node": "^2.2.0-version1.0.4", + "sodium-native": "^3.3.0", + "tar-fs": "^2.1.1" + }, + "devDependencies": { + "@types/async": "^3.2.15", + "@types/tar-fs": "^2.0.1", + "better-npm-audit": "^3.7.3", + "install": "^0.13.0", + "nodemon": "^2.0.15", + "ts-node": "^10.4.0" + } + }, + "node_modules/@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "node_modules/@types/async": { + "version": "3.2.15", + "resolved": "https://registry.npmjs.org/@types/async/-/async-3.2.15.tgz", + "integrity": "sha512-PAmPfzvFA31mRoqZyTVsgJMsvbynR429UTTxhmfsUCrWGh3/fxOrzqBtaTPJsn4UtzTv4Vb0+/O7CARWb69N4g==", + "dev": true + }, + "node_modules/@types/node": { + "version": "16.11.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.11.tgz", + "integrity": "sha512-KB0sixD67CeecHC33MYn+eYARkqTheIRNuu97y2XMjR7Wu3XibO1vaY6VBV6O/a89SPI81cEUIYT87UqUWlZNw==", + "dev": true + }, + "node_modules/@types/tar-fs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/tar-fs/-/tar-fs-2.0.1.tgz", + "integrity": "sha512-qlsQyIY9sN7p221xHuXKNoMfUenOcvEBN4zI8dGsYbYCqHtTarXOEXSIgUnK+GcR0fZDse6pAIc5pIrCh9NefQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tar-stream": "*" + } + }, + "node_modules/@types/tar-stream": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@types/tar-stream/-/tar-stream-2.2.2.tgz", + "integrity": "sha512-1AX+Yt3icFuU6kxwmPakaiGrJUwG44MpuiqPg4dSolRFk6jmvs4b3IbUol9wKDLIgU76gevn3EwE8y/DkSJCZQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", + "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "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/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" + }, + "node_modules/axios": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", + "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "dependencies": { + "follow-redirects": "^1.14.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/better-npm-audit": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/better-npm-audit/-/better-npm-audit-3.7.3.tgz", + "integrity": "sha512-zsSiidlP5n7KpCYdAmkellu4JYA4IoRUUwrBMv/R7TwT8vcRfk5CQ2zTg7yUy4bdWkKtAj7VVdPQttdMbx+n5Q==", + "dev": true, + "dependencies": { + "commander": "^8.0.0", + "dayjs": "^1.10.6", + "lodash.get": "^4.4.2", + "table": "^6.7.1" + }, + "bin": { + "better-npm-audit": "index.js" + }, + "engines": { + "node": ">= 8.12" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/dayjs": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.4.tgz", + "integrity": "sha512-Zj/lPM5hOvQ1Bf7uAvewDaUcsJoI6JmNqmHhHl3nyumwe0XHwt8sWdOVAPACJzCebL8gQCi+K49w7iKWnGwX9g==", + "dev": true + }, + "node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "engines": { + "node": ">=10" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/install": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz", + "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "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==", + "dev": true + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "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 + }, + "node_modules/multichain-node": { + "version": "2.2.0-version1.0.4", + "resolved": "https://registry.npmjs.org/multichain-node/-/multichain-node-2.2.0-version1.0.4.tgz", + "integrity": "sha512-1y9U7SXo8osBD/y7TZF1/4/PMLJJTi7AYVZ6EkTZSV9WfbxP/CAok+AviUCaFBykljKgz1WNRljle4lmDS/5tQ==" + }, + "node_modules/node-gyp-build": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", + "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/nodemon": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.19.tgz", + "integrity": "sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "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" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/simple-update-notifier": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", + "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/sodium-native": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-3.3.0.tgz", + "integrity": "sha512-rg6lCDM/qa3p07YGqaVD+ciAbUqm6SoO4xmlcfkbU5r1zIGrguXztLiEtaLYTV5U6k8KSIUFmnU3yQUSKmf6DA==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/table": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/ts-node": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "@types/async": { + "version": "3.2.15", + "resolved": "https://registry.npmjs.org/@types/async/-/async-3.2.15.tgz", + "integrity": "sha512-PAmPfzvFA31mRoqZyTVsgJMsvbynR429UTTxhmfsUCrWGh3/fxOrzqBtaTPJsn4UtzTv4Vb0+/O7CARWb69N4g==", + "dev": true + }, + "@types/node": { + "version": "16.11.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.11.tgz", + "integrity": "sha512-KB0sixD67CeecHC33MYn+eYARkqTheIRNuu97y2XMjR7Wu3XibO1vaY6VBV6O/a89SPI81cEUIYT87UqUWlZNw==", + "dev": true + }, + "@types/tar-fs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/tar-fs/-/tar-fs-2.0.1.tgz", + "integrity": "sha512-qlsQyIY9sN7p221xHuXKNoMfUenOcvEBN4zI8dGsYbYCqHtTarXOEXSIgUnK+GcR0fZDse6pAIc5pIrCh9NefQ==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/tar-stream": "*" + } + }, + "@types/tar-stream": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@types/tar-stream/-/tar-stream-2.2.2.tgz", + "integrity": "sha512-1AX+Yt3icFuU6kxwmPakaiGrJUwG44MpuiqPg4dSolRFk6jmvs4b3IbUol9wKDLIgU76gevn3EwE8y/DkSJCZQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "acorn": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", + "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" + }, + "axios": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", + "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "requires": { + "follow-redirects": "^1.14.4" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "better-npm-audit": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/better-npm-audit/-/better-npm-audit-3.7.3.tgz", + "integrity": "sha512-zsSiidlP5n7KpCYdAmkellu4JYA4IoRUUwrBMv/R7TwT8vcRfk5CQ2zTg7yUy4bdWkKtAj7VVdPQttdMbx+n5Q==", + "dev": true, + "requires": { + "commander": "^8.0.0", + "dayjs": "^1.10.6", + "lodash.get": "^4.4.2", + "table": "^6.7.1" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "dayjs": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.4.tgz", + "integrity": "sha512-Zj/lPM5hOvQ1Bf7uAvewDaUcsJoI6JmNqmHhHl3nyumwe0XHwt8sWdOVAPACJzCebL8gQCi+K49w7iKWnGwX9g==", + "dev": true + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "follow-redirects": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "install": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz", + "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "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==", + "dev": true + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "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 + }, + "multichain-node": { + "version": "2.2.0-version1.0.4", + "resolved": "https://registry.npmjs.org/multichain-node/-/multichain-node-2.2.0-version1.0.4.tgz", + "integrity": "sha512-1y9U7SXo8osBD/y7TZF1/4/PMLJJTi7AYVZ6EkTZSV9WfbxP/CAok+AviUCaFBykljKgz1WNRljle4lmDS/5tQ==" + }, + "node-gyp-build": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", + "integrity": "sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==" + }, + "nodemon": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.19.tgz", + "integrity": "sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-from-string": { + "version": "2.0.2", + "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 + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "simple-update-notifier": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", + "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", + "dev": true, + "requires": { + "semver": "~7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "sodium-native": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-3.3.0.tgz", + "integrity": "sha512-rg6lCDM/qa3p07YGqaVD+ciAbUqm6SoO4xmlcfkbU5r1zIGrguXztLiEtaLYTV5U6k8KSIUFmnU3yQUSKmf6DA==", + "requires": { + "node-gyp-build": "^4.3.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "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, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + } + }, + "tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "requires": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "requires": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + } + }, + "ts-node": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + } + }, + "typescript": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", + "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", + "dev": true, + "peer": true + }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/migration/package.json b/migration/package.json new file mode 100644 index 000000000..6565a7fda --- /dev/null +++ b/migration/package.json @@ -0,0 +1,31 @@ +{ + "name": "trubudget-migration", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "nodemon", + "start": "node ./dist/index.js", + "build": "tsc --outDir ./dist ./src/index.ts", + "audit": "better-npm-audit audit" + }, + "author": "Martin Mayr", + "license": "MIT", + "devDependencies": { + "@types/async": "^3.2.15", + "@types/tar-fs": "^2.0.1", + "better-npm-audit": "^3.7.3", + "install": "^0.13.0", + "nodemon": "^2.0.15", + "ts-node": "^10.4.0" + }, + "dependencies": { + "async": "^3.2.3", + "axios": "^0.24.0", + "dotenv": "^10.0.0", + "lodash": "^4.17.21", + "multichain-node": "^2.2.0-version1.0.4", + "sodium-native": "^3.3.0", + "tar-fs": "^2.1.1" + } +} diff --git a/migration/src/assert.ts b/migration/src/assert.ts new file mode 100644 index 000000000..bc5797c90 --- /dev/null +++ b/migration/src/assert.ts @@ -0,0 +1,126 @@ +import {getAllStreamItems, getFromTxOutData, getStreamItemByTx,} from "./helper/migrationHelper"; + +var _ = require("lodash"); + +const difference = (object: any, base: any) => { + const changes = (object: any, base: any) => { + return _.transform(object, function (result: any, value: any, key: any) { + if (!_.isEqual(value, base[key])) { + result[key] = + _.isObject(value) && _.isObject(base[key]) + ? changes(value, base[key]) + : value; + } + }); + }; + return changes(object, base); +}; + +export const assertChainEquals = async ( + sourceChain: any, + destinationChain: any +) => { + const clone = await getAllStreamItems(destinationChain, "users"); + const orginal = await getAllStreamItems(sourceChain, "users"); + if (!orginal || !clone) { + console.log("User Assertion Failed! Original or clone is undefined"); + return false; + } + + if (orginal.length > clone.length) { + console.log( + `User Assertion Failed! Original and clone have not the same length. Original is: ${orginal.length}, clone is: ${clone.length}` + ); + + return false; + } + for (const originalItem of orginal) { + for (const cloneItem of clone) { + const diff = difference(originalItem.data, cloneItem.data); + if ( + !diff || + Object.keys(diff).length !== 0 || + !Object.getPrototypeOf(diff) === Object.prototype + ) { + console.log( + "User Assertion Failed! Original and clone are not the same! Original is: ", + JSON.stringify(originalItem), + "Clone is: ", + JSON.stringify(cloneItem), + "Difference is: ", + JSON.stringify(diff) + ); + + return false; + } + } + } + + return true; +}; + +export interface AssertParams { + sourceChain: any; + destinationChain: any; + stream: string; + sourceChainTx: string; + destinationChainTx: string; + additionalData?: any; +} + +export const assertStreamItem = async ({ + sourceChain, + destinationChain, + stream, + sourceChainTx, + destinationChainTx, + }: AssertParams) => { + let clone = await getStreamItemByTx( + destinationChain, + stream, + destinationChainTx + ); + let original = await getStreamItemByTx(sourceChain, stream, sourceChainTx); + if (!original || !clone) { + throw new Error("User Assertion Failed! Original or clone is undefined"); + } + + if ( + original.data && + original.data.hasOwnProperty("vout") && + original.data.hasOwnProperty("txid") + ) { + original = await getFromTxOutData(sourceChain, original); + } + + if ( + clone.data && + clone.data.hasOwnProperty("vout") && + clone.data.hasOwnProperty("txid") + ) { + clone = await getFromTxOutData(destinationChain, clone); + } + + const diff = difference(original.data, clone.data); + + if ( + !diff || + Object.keys(diff).length !== 0 || + !Object.getPrototypeOf(diff) === Object.prototype + ) { + console.log( + "User Assertion Failed! Original and clone are not the same! Original is: ", + JSON.stringify(original), + "Clone is: ", + JSON.stringify(clone), + "Difference is: ", + JSON.stringify(diff) + ); + throw new Error( + `User Assertion Failed! Original and clone are not the same! Difference is: + ${JSON.stringify(diff)}` + ); + } + + return true; +}; diff --git a/migration/src/customMigration/migrateDocuments.ts b/migration/src/customMigration/migrateDocuments.ts new file mode 100644 index 000000000..e48c3dfaa --- /dev/null +++ b/migration/src/customMigration/migrateDocuments.ts @@ -0,0 +1,173 @@ +import mapLimit from "async/mapLimit"; +import * as crypto from "crypto"; +import { + downloadFileFromApi, + getApiInstanceForUser, + getWorkflowItemDetails, + grantAllPermissionsOnWorkflowItem, + uploadViaApi, + WorkflowItemDetailsDocument, +} from "../helper/apiHelper"; +import { + extractFileContentFromDocumentsOnChain, + getStreamItemByTx, +} from "../helper/migrationHelper"; +import { + MigrateFunction, + MigrationCompleted, + MigrationStatus, + MoveFunction, + VerifyParams, +} from "../migrate"; +import ApplicationConfiguration from "../helper/config"; + +const MAX_ASYNC_OPERATIONS = 3; + +const hashBase64 = (base64String: string): Promise => { + return new Promise((resolve) => { + const hash = crypto.createHash("sha256"); + hash.update(Buffer.from(base64String, "base64")); + resolve(hash.digest("hex")); + }); +}; + +export const documentUploader: MigrateFunction = { + stream: "offchain_documents", + function: async (params: MoveFunction): Promise => { + const { sourceChain, item } = params; + try { + if (!item.available) + return { + status: MigrationStatus.Failed, + }; + const document = await extractFileContentFromDocumentsOnChain( + sourceChain, + item + ); + if (!document || document.eventType !== "workflowitem_document_uploaded") + return { + status: MigrationStatus.Failed, + }; + + //TODO: what do we do with storage_service_url_published events? + const { projectId, subprojectId, workflowitemId, fileMetadata } = + document; + + const migrationUserApi = await getApiInstanceForUser( + ApplicationConfiguration.MIGRATION_USER_USERNAME, + ApplicationConfiguration.MIGRATION_USER_PASSWORD + ); + + const rootUserApi = await getApiInstanceForUser( + "root", + ApplicationConfiguration.ROOT_SECRET + ); + + await grantAllPermissionsOnWorkflowItem( + rootUserApi, + ApplicationConfiguration.MIGRATION_USER_USERNAME, + projectId, + subprojectId, + workflowitemId + ); + /*await grantAllRightsToUser( + rootUserApi, + ApplicationConfiguration.MIGRATION_USER_USERNAME + );*/ + await uploadViaApi(migrationUserApi, { + projectId, + subprojectId, + workflowitemId, + fileMetadata: { + document: { + ...fileMetadata, + }, + }, + }); + + //There is no need to save changes on destination chain since API is processing request. + + return { + sourceChainTx: item.txid, + destinationChainTx: "Uploaded via API.", + status: MigrationStatus.Ok, + additionalData: { + projectId, + subprojectId, + workflowitemId, + }, + }; + } catch (error) { + throw Error( + `Error while uploading file ${params.item.txid} via API with ${error.message}` + ); + } + }, + verifier: async (params: VerifyParams): Promise => { + const { sourceChain, stream, sourceChainTx, additionalData } = params; + const sourceItem = await getStreamItemByTx( + sourceChain, + stream, + sourceChainTx + ); + const documentOnSourceChain = await extractFileContentFromDocumentsOnChain( + sourceChain, + sourceItem + ); + const documentOnSourceChainHash = await hashBase64( + documentOnSourceChain.fileMetadata.base64 + ); + + const { + projectId: destinationProjectId, + subprojectId: destinationSubprojectId, + workflowitemId: destinationWorkflowitemId, + } = additionalData; + + const migrationUserApi = await getApiInstanceForUser( + ApplicationConfiguration.MIGRATION_USER_USERNAME, + ApplicationConfiguration.MIGRATION_USER_PASSWORD + ); + + const destinationWorkflowItem = await getWorkflowItemDetails( + migrationUserApi, + destinationProjectId, + destinationSubprojectId, + destinationWorkflowitemId + ); + + // We do not know the new id of the document since api defines id + // Workaround: get workflow item & check all files with same name & hash + const documentsToVerify = destinationWorkflowItem.documents.filter( + (document: WorkflowItemDetailsDocument) => { + return ( + document.fileName === documentOnSourceChain.fileMetadata.fileName && + document.hash === documentOnSourceChainHash + ); + } + ); + + const destinationFilesAvailable = documentsToVerify.length > 0; + + const allDocumentsReachable = await mapLimit( + documentsToVerify, + MAX_ASYNC_OPERATIONS, + async (document) => { + const destinationFileIsReachable = await downloadFileFromApi( + migrationUserApi, + destinationProjectId, + destinationSubprojectId, + destinationWorkflowitemId, + document.id + ); + return destinationFileIsReachable.hash === document.hash; + } + ); + + return ( + documentsToVerify.length === allDocumentsReachable.length && + destinationFilesAvailable && + allDocumentsReachable.every((e) => e === true) + ); + }, +}; diff --git a/migration/src/customMigration/migrateWorkflowitems.ts b/migration/src/customMigration/migrateWorkflowitems.ts new file mode 100644 index 000000000..347af9f28 --- /dev/null +++ b/migration/src/customMigration/migrateWorkflowitems.ts @@ -0,0 +1,121 @@ +import { AssertParams, assertStreamItem } from "../assert"; +import { getFromTxOutData } from "../helper/migrationHelper"; +import { + MigrateFunction, + MigrationCompleted, + MigrationStatus, + MoveFunction, + VerifyParams, +} from "../migrate"; +import { createStreamItem } from "../rpc"; +import { DataJSON } from "../types/item"; +interface updatedJSON extends DataJSON { + update: { + documents: []; + }; +} + +interface createdJSON extends DataJSON { + workflowitem: { + documents: []; + }; +} +export const makeProjectUploader = (projectId: string): MigrateFunction => { + return { + stream: projectId, + function: async (params: MoveFunction): Promise => { + const { sourceChain, destinationChain, item, stream } = params; + + try { + if (!item.available) + return { + status: MigrationStatus.Failed, + }; + let itemToMigrate = item; + const txId = item.txid; + let status = MigrationStatus.Ok; + + if ( + item.data && + item.data.hasOwnProperty("vout") && + item.data.hasOwnProperty("txid") + ) { + itemToMigrate = await getFromTxOutData(sourceChain, item); + } + if ( + itemToMigrate.data.json.type === "workflowitem_updated" && + (itemToMigrate.data.json as unknown as updatedJSON).update + .documents && + (itemToMigrate.data.json as unknown as updatedJSON).update.documents + .length + ) { + const newJson = itemToMigrate.data.json as unknown as updatedJSON; + newJson.update.documents = []; + status = MigrationStatus.Skipped; + itemToMigrate = { + ...itemToMigrate, + data: { ...itemToMigrate.data, json: newJson as DataJSON }, + }; + } else if ( + itemToMigrate.data.json.type === "workflowitem_created" && + (itemToMigrate.data.json as unknown as createdJSON).workflowitem + .documents && + (itemToMigrate.data.json as unknown as createdJSON).workflowitem + .documents.length + ) { + const newJson = itemToMigrate.data.json as unknown as createdJSON; + newJson.workflowitem.documents = []; + status = MigrationStatus.Skipped; + itemToMigrate = { + ...itemToMigrate, + data: { ...itemToMigrate.data, json: newJson as DataJSON }, + }; + } + const req = await createStreamItem( + destinationChain, + stream, + item.keys, + itemToMigrate + ); + console.log( + `Created key ${JSON.stringify( + item.keys + )} on destination chain with tx ${req}` + ); + return { + sourceChainTx: txId, + destinationChainTx: req, + status, + }; + } catch (error) { + console.log(error); + throw Error( + `Error while uploading file ${params.item.txid} via API with ${error.message}` + ); + } + }, + verifier: async (params: VerifyParams): Promise => { + const { + sourceChain, + destinationChain, + stream, + sourceChainTx, + destinationChainTx, + status, + } = params; + const assertion: AssertParams = { + sourceChain, + destinationChain, + stream, + sourceChainTx, + destinationChainTx, + }; + + if (status === MigrationStatus.Skipped) { + return true; + } + + return await assertStreamItem(assertion); + }, + }; +}; diff --git a/migration/src/helper/apiHelper.ts b/migration/src/helper/apiHelper.ts new file mode 100644 index 000000000..80fcc484b --- /dev/null +++ b/migration/src/helper/apiHelper.ts @@ -0,0 +1,279 @@ +import axios, {AxiosInstance, AxiosResponse} from "axios"; +import ApplicationConfiguration from "./config"; + +export interface UploadMetadata { + projectId: string; + subprojectId: string; + workflowitemId: string; + fileMetadata: { + document: { id: string; base64: string; fileName: string }; + }; +} + +export interface WorkflowItemDetails { + id: string; + creationUnixTs: string; + status: string; + amountType: string; + displayName: string; + description: string; + amount: string; + assignee: string; + currency: string; + additionalData: any; + workflowitemType: string; + documents: WorkflowItemDetailsDocument[]; +} + +export interface WorkflowItemDetailsDocument { + hash: string; + fileName: string; + id: string; + available: boolean; +} + +export const workflowitemIntents = [ + "workflowitem.intent.listPermissions", + "workflowitem.intent.grantPermission", + "workflowitem.intent.revokePermission", + "workflowitem.view", + "workflowitem.viewHistory", + "workflowitem.assign", + "workflowitem.update", + "workflowitem.close", +]; + +const apiInstances = new Map(); + +const apiIsReady = async (apiBaseUrl: string): Promise => { + try { + console.log("Check if Api is ready..."); + const response: AxiosResponse = await axios.get( + `${apiBaseUrl}/api/readiness`, + {timeout: 5000} + ); + return response.status === 200; + } catch (error) { + console.log(`Api (${apiBaseUrl}) not reachable.`); + return false; + } +}; + +const waitForApi = async (delayInS: number, maxRetries: number = 50) => { + let retries = 0; + while (retries < maxRetries) { + if (await apiIsReady(ApplicationConfiguration.DESTINATION_API_BASE_URL)) { + console.log("Api reports readiness!"); + return; + } else { + console.log(`Retry in ${delayInS} seconds...`); + await new Promise((resolve) => setTimeout(resolve, delayInS * 1000)); + } + retries++; + } + console.log(`Max retries of ${maxRetries} reached. Api not ready. Exit.`); + process.exit(1); +}; + +export const getApiInstanceForUser = async ( + username: string, + password: string +): Promise => { + await waitForApi(10, 50); + const apiInstance = apiInstances.get(username); + if (apiInstance) return apiInstance; + + const newApiInstanceForUser = await authAgainstApi(username, password); + apiInstances.set(username, newApiInstanceForUser); + return newApiInstanceForUser; +}; + +const authAgainstApi = async ( + username: string, + password: string +): Promise => { + try { + console.log("Authenticate with root user"); + const auth = await axios.post( + `${ApplicationConfiguration.DESTINATION_API_BASE_URL}/api/user.authenticate`, + { + apiVersion: "1.0", + data: { + user: { + id: username, + password, + }, + }, + } + ); + const token = auth.data.data.user.token; + return axios.create({ + baseURL: `${ApplicationConfiguration.DESTINATION_API_BASE_URL}/api`, + headers: {Authorization: `Bearer ${token}`}, + }); + } catch (error) { + throw new Error( + `Can not authenticate user! Request failed wit status ${error}` + ); + } +}; + +export const uploadViaApi = async ( + api: AxiosInstance, + metadata: UploadMetadata +) => { + const {projectId, subprojectId, workflowitemId, fileMetadata} = metadata; + + const request = await api.post("/workflowitem.update", { + apiVersion: "1.0", + data: { + projectId, + subprojectId, + workflowitemId, + documents: [fileMetadata.document], + }, + }); + if (request.status !== 200) { + throw new Error( + `Error while performing upload to destination for file ${fileMetadata.document.fileName} to destination` + ); + } +}; + +export const downloadFileFromApi = async ( + api: AxiosInstance, + projectId: string, + subprojectId: string, + workflowitemId: string, + documentId: string +) => { + const request = await api.get( + `/workflowitem.downloadDocument?projectId=${projectId}&subprojectId=${subprojectId}&workflowitemId=${workflowitemId}&documentId=${documentId}` + ); + + if (request.status !== 200) { + throw new Error("Can not download file!"); + } + return request.data; +}; + +export const getWorkflowItemDetails = async ( + api: AxiosInstance, + projectId: string, + subprojectId: string, + workflowitemId: string +): Promise => { + const request = await api.get( + `/workflowitem.viewDetails?projectId=${projectId}&subprojectId=${subprojectId}&workflowitemId=${workflowitemId}` + ); + + if (request.status !== 200) { + throw new Error("Can not download file!"); + } + return request.data.data.workflowitem.data; +}; + +export const createUser = async ( + api: AxiosInstance, + displayName: string, + organization: string, + username: string, + password: string +): Promise => { + const request = await api.post(`/global.createUser`, { + apiVersion: "1.0", + data: { + user: { + displayName, + organization, + id: username, + password, + }, + }, + }); + + if (request.status !== 200) { + throw new Error(`Can not create user ${username}`); + } + + return request.data.data.user.id; +}; + +export const disableUser = async ( + api: AxiosInstance, + userId: string +): Promise => { + const request = await api.post(`/global.disableUser`, { + apiVersion: "1.0", + data: { + userId, + }, + }); + + if (request.status !== 200) { + throw new Error(`Can not disable user ${userId}`); + } +}; + +export const grantAllRightsToUser = async ( + api: AxiosInstance, + userId: string +): Promise => { + const request = await api.post(`/global.grantAllPermissions`, { + apiVersion: "1.0", + data: { + identity: userId, + }, + }); + + if (request.status !== 200) { + throw new Error(`Can not grant all rights to user ${userId}`); + } +}; + +export const grantAllPermissionsOnWorkflowItem = async ( + api: AxiosInstance, + userId: string, + projectId: string, + subprojectId: string, + workflowitemId +): Promise => { + for (const intent of workflowitemIntents) { + const request = await api.post(`/workflowitem.intent.grantPermission`, { + apiVersion: "1.0", + data: { + projectId, + subprojectId, + workflowitemId, + identity: userId, + intent, + }, + }); + + if (request.status !== 200) { + throw new Error(`Can not grant rights to user ${userId}`); + } + } +}; + +export const listUsers = async ( + api: AxiosInstance +): Promise<[ + { + username: string; + userId: string; + } +]> => { + const request = await api.get(`/user.list`); + + if (request.status !== 200) { + throw new Error(`Can not list users`); + } + + return request.data.data.items.map((el) => { + return { + username: el.displayName, + userId: el.id, + }; + }); +}; diff --git a/migration/src/helper/config.ts b/migration/src/helper/config.ts new file mode 100644 index 000000000..14cf464ff --- /dev/null +++ b/migration/src/helper/config.ts @@ -0,0 +1,72 @@ +require("dotenv").config(); +const { env } = process; + +export type ApplicationConfiguration = { + [configName in ConfigOption]: string; +}; + +export enum ConfigOption { + DESTINATION_API_BASE_URL = "DESTINATION_API_BASE_URL", + SOURCE_RPC_PORT = "SOURCE_RPC_PORT", + SOURCE_RPC_USER = "SOURCE_RPC_USER", + SOURCE_RPC_PASSWORD = "SOURCE_RPC_PASSWORD", + SOURCE_RPC_HOST = "SOURCE_RPC_HOST", + DESTINATION_RPC_PORT = "DESTINATION_RPC_PORT", + DESTINATION_RPC_USER = "DESTINATION_RPC_USER", + DESTINATION_RPC_PASSWORD = "DESTINATION_RPC_PASSWORD", + DESTINATION_RPC_HOST = "DESTINATION_RPC_HOST", + BACKUP_FILE_LOCATION = "BACKUP_FILE_LOCATION", + DESTINATION_BLOCKCHAIN_BASE_URL = "DESTINATION_BLOCKCHAIN_BASE_URL", + ROOT_SECRET = "ROOT_SECRET", + ORGANIZATION = "ORGANIZATION", + MIGRATION_USER_PASSWORD = "MIGRATION_USER_PASSWORD", + MIGRATION_USER_USERNAME = "MIGRATION_USER_USERNAME", +} + +const config: ApplicationConfiguration = { + DESTINATION_API_BASE_URL: env.DESTINATION_API_BASE_URL, + SOURCE_RPC_PORT: env.SOURCE_RPC_PORT, + SOURCE_RPC_USER: env.SOURCE_RPC_USER, + SOURCE_RPC_PASSWORD: env.SOURCE_RPC_PASSWORD, + SOURCE_RPC_HOST: env.SOURCE_RPC_HOST, + DESTINATION_RPC_PORT: env.DESTINATION_RPC_PORT, + DESTINATION_RPC_USER: env.DESTINATION_RPC_USER, + DESTINATION_RPC_PASSWORD: env.DESTINATION_RPC_PASSWORD, + DESTINATION_RPC_HOST: env.DESTINATION_RPC_HOST, + BACKUP_FILE_LOCATION: env.BACKUP_FILE_LOCATION, + DESTINATION_BLOCKCHAIN_BASE_URL: env.DESTINATION_BLOCKCHAIN_BASE_URL, + ROOT_SECRET: env.ROOT_SECRET, + ORGANIZATION: env.ORGANIZATION, + MIGRATION_USER_PASSWORD: env.MIGRATION_USER_PASSWORD, + // This is hard-coding in intended. + // TruBudget frontend can display that a migration has happened looking at the history & searching for the migration-user + MIGRATION_USER_USERNAME: "migration-user", +}; + +const isEnvironmentSet = ( + config: ApplicationConfiguration +): ApplicationConfiguration => { + if ( + config.DESTINATION_API_BASE_URL && + config.SOURCE_RPC_PORT && + config.SOURCE_RPC_USER && + config.SOURCE_RPC_PASSWORD && + config.SOURCE_RPC_HOST && + config.DESTINATION_RPC_PORT && + config.DESTINATION_RPC_USER && + config.DESTINATION_RPC_PASSWORD && + config.DESTINATION_RPC_HOST && + config.BACKUP_FILE_LOCATION && + config.DESTINATION_BLOCKCHAIN_BASE_URL && + config.ROOT_SECRET && + config.ORGANIZATION && + config.MIGRATION_USER_PASSWORD + ) + return config; + + throw new Error( + "At least one environment variable is not set. Please review your settings!" + ); +}; + +export default isEnvironmentSet(config); diff --git a/migration/src/helper/configureDestination.ts b/migration/src/helper/configureDestination.ts new file mode 100644 index 000000000..c1849dd5c --- /dev/null +++ b/migration/src/helper/configureDestination.ts @@ -0,0 +1,95 @@ +import { readFile } from "fs/promises"; +import ApplicationConfiguration from "./config"; +import { + createUser, + disableUser, + getApiInstanceForUser, + listUsers, +} from "./apiHelper"; +import * as fs from "fs"; +import { extract, pack } from "tar-fs"; +import axios from "axios"; + +const TEMP_WALLET_FILE_NAME = "wallet.gz"; +const ARCHIVE_SUFFIX = ".gz"; //this is funny, backup has a gz suffix but is a tar ... + +export const configureDestinationChain = async (): Promise => { + await extractWallet(ApplicationConfiguration.BACKUP_FILE_LOCATION); + + //TODO: this is hacky ... + const absPath = ApplicationConfiguration.BACKUP_FILE_LOCATION.substring( + 0, + ApplicationConfiguration.BACKUP_FILE_LOCATION.lastIndexOf("/") + ); + const file = await readFile(`${absPath}/${TEMP_WALLET_FILE_NAME}`); + const config = { + headers: { "content-type": "application/gzip" }, + maxContentLength: 1074790400, + maxBodyLength: 1074790400, + }; + console.log( + `Restoring wallet by sending post request to ${ApplicationConfiguration.DESTINATION_BLOCKCHAIN_BASE_URL}/restoreWallet` + ); + const restoreWalletRequest = await axios.post( + `${ApplicationConfiguration.DESTINATION_BLOCKCHAIN_BASE_URL}/restoreWallet`, + file, + config + ); + if (restoreWalletRequest.status !== 200) { + throw new Error("Can not restore wallet on destination chain!"); + } + fs.unlinkSync(`./${TEMP_WALLET_FILE_NAME}`); + console.log("Available wallet addresses are", restoreWalletRequest.data); +}; + +const extractWallet = async (path: string) => { + const outDir = path.replace(ARCHIVE_SUFFIX, ""); + return new Promise((resolve, reject) => { + const stream = fs.createReadStream(path).pipe(extract(outDir)); + console.log("Extract wallet..."); + stream.on("finish", async () => { + pack(outDir, { + entries: ["params.dat", "params.dat.bak", "wallet.dat", "wallet"], // only the specific entries will be packed + }) + .pipe(fs.createWriteStream(`./${TEMP_WALLET_FILE_NAME}`)) + .on("finish", () => { + fs.rmdirSync(path.replace(ARCHIVE_SUFFIX, ""), { recursive: true }); + console.log("wallet extracted."); + resolve(true); + }) + .on("error", () => reject()); + }); + }); +}; + +export const createMigrationUser = async (): Promise => { + const rootApi = await getApiInstanceForUser( + "root", + ApplicationConfiguration.ROOT_SECRET + ); + await createUser( + rootApi, + ApplicationConfiguration.MIGRATION_USER_USERNAME, + ApplicationConfiguration.ORGANIZATION, + ApplicationConfiguration.MIGRATION_USER_USERNAME, + ApplicationConfiguration.MIGRATION_USER_PASSWORD + ); +}; + +export const disableMigrationUser = async (): Promise => { + const rootApi = await getApiInstanceForUser( + "root", + ApplicationConfiguration.ROOT_SECRET + ); + const users = await listUsers(rootApi); + const migrationUser = users.find( + (u) => u.username == ApplicationConfiguration.MIGRATION_USER_USERNAME + ); + + if (!migrationUser) + throw new Error( + "Can not disable user - no migration user found on destination!" + ); + + await disableUser(rootApi, migrationUser.userId); +}; diff --git a/migration/src/helper/migrationHelper.ts b/migration/src/helper/migrationHelper.ts new file mode 100644 index 000000000..b3508e180 --- /dev/null +++ b/migration/src/helper/migrationHelper.ts @@ -0,0 +1,131 @@ +import { + getItemByTx, + getTxOutData, + listStreamItems, + listStreamKeyItems, + listStreamKeys, + listStreams, +} from "../rpc"; +import { Item } from "../types/item"; +import { StreamInfo } from "../types/stream"; + +export interface OnchainDocument { + projectId: string; + subprojectId: string; + workflowitemId: string; + fileMetadata: { + id: string; + base64: string; + fileName: string; + }; + + eventType: string; +} + +const getAllStreams = async ( + multichain: any +): Promise => { + try { + const s = await listStreams(multichain); + return s.map((el: any) => { + return { + name: el.name, + details: el.details, + }; + }); + } catch (e) { + console.log("error", e); + } +}; + +const listStreamContent = async (multichain: any, stream: string) => { + try { + return await listStreamItems(multichain, stream); + } catch (e) { + console.log("error", e); + } +}; + +const getAllStreamItems = async ( + multichain: any, + stream: string +): Promise => { + const allItems = new Array(); + const keys = await listStreamKeys(multichain, stream); + for await (const key of keys) { + const items = await listStreamKeyItems(multichain, stream, key.key); + allItems.push(...items); + } + return allItems; +}; + +const getStreamItemByTx = async ( + multichain: any, + stream: string, + tx: string +): Promise => { + try { + return await getItemByTx(multichain, stream, tx); + } catch (e) { + console.log("error", e); + } +}; + +const getFromTxOutData = async (multichain: any, item: Item) => { + if ( + item.data && + item.data.hasOwnProperty("vout") && + item.data.hasOwnProperty("txid") + ) { + const { txid, vout } = item.data as any; + return await getTxOutData(multichain, txid, vout); + } + throw new Error(`No document found for item ${item.txid}`); +}; + +export const extractFileContentFromDocumentsOnChain = async ( + multichain: any, + item: Item +): Promise => { + // Large offchain documents stored in offchain storage + if ( + item.data && + item.data.hasOwnProperty("vout") && + item.data.hasOwnProperty("txid") + ) { + const data = await getFromTxOutData(multichain, item); + if (data.json.document) { + const { projectId, subprojectId, workflowitemId, document, type } = + data.json; + return { + projectId, + subprojectId, + workflowitemId, + fileMetadata: { + ...document, + }, + eventType: type, + }; + } + } + // small documents stored on chain + const { projectId, subprojectId, workflowitemId, document } = item.data + .json as any; + return { + projectId, + subprojectId, + workflowitemId, + fileMetadata: { + ...document, + }, + eventType: item.data.json.type, + }; +}; + +export { + getAllStreams, + listStreamContent, + getAllStreamItems, + getStreamItemByTx, + getFromTxOutData as getFromTxOutData, +}; diff --git a/migration/src/index.ts b/migration/src/index.ts new file mode 100644 index 000000000..46efaf2bb --- /dev/null +++ b/migration/src/index.ts @@ -0,0 +1,88 @@ +import { documentUploader } from "./customMigration/migrateDocuments"; +import { makeProjectUploader } from "./customMigration/migrateWorkflowitems"; +import ApplicationConfiguration from "./helper/config"; +import { getAllStreams } from "./helper/migrationHelper"; +import { CustomMigrations, migrate } from "./migrate"; +import { StreamInfo } from "./types/stream"; +import { + configureDestinationChain, + createMigrationUser, + disableMigrationUser, +} from "./helper/configureDestination"; +let migrationSource = require("multichain-node")({ + port: ApplicationConfiguration.SOURCE_RPC_PORT, + host: ApplicationConfiguration.SOURCE_RPC_HOST, + user: ApplicationConfiguration.SOURCE_RPC_USER, + pass: ApplicationConfiguration.SOURCE_RPC_PASSWORD, +}); + +let migrationDestination = require("multichain-node")({ + port: ApplicationConfiguration.DESTINATION_RPC_PORT, + host: ApplicationConfiguration.DESTINATION_RPC_HOST, + user: ApplicationConfiguration.DESTINATION_RPC_USER, + pass: ApplicationConfiguration.DESTINATION_RPC_PASSWORD, +}); + +const customMigrationFunctions: CustomMigrations = {}; +customMigrationFunctions["offchain_documents"] = documentUploader; + +(async function () { + try { + console.log("generate Project Migration Function..."); + await generateProjectMigrationFunction(); + console.log("configure Destination Chain..."); + await configureDestinationChain(); + console.log("create Migration User"); + await createMigrationUser(); + console.log("run Migration"); + await runMigration(); + console.log("disable Migration User"); + await disableMigrationUser(); + await listStreams(); + } catch (e) { + console.log(e); + } +})(); + +async function listStreams() { + console.log( + "Available streams on src are: ", + ((await getAllStreams(migrationSource)) || []).map( + (e: StreamInfo) => e.name + ) + ); + + console.log( + "Available streams on dest are: ", + ((await getAllStreams(migrationDestination)) || []).map( + (e: StreamInfo) => e.name + ) + ); +} + +async function generateProjectMigrationFunction() { + console.log( + `Get projects of source stream ${ApplicationConfiguration.SOURCE_RPC_HOST}:${ApplicationConfiguration.SOURCE_RPC_PORT} ...` + ); + const projectIds = await getProjectStreams(migrationSource); + for (const projectId of projectIds) { + customMigrationFunctions[projectId] = makeProjectUploader(projectId); + } +} + +async function getProjectStreams(multichain): Promise { + const streams = await getAllStreams(multichain); + const projects = streams + .filter((el) => el.details.kind === "project") + .map((el) => el.name); + return projects; +} + +async function runMigration() { + await migrate( + migrationSource, + migrationDestination, + ["org:MySlaveOrga"], + customMigrationFunctions + ); +} diff --git a/migration/src/migrate.ts b/migration/src/migrate.ts new file mode 100644 index 000000000..af2bf6131 --- /dev/null +++ b/migration/src/migrate.ts @@ -0,0 +1,218 @@ +import { AssertParams, assertStreamItem } from "./assert"; +import { + getAllStreamItems, + getAllStreams, + getFromTxOutData, +} from "./helper/migrationHelper"; +import { createStream, createStreamItem, listStreams } from "./rpc"; +import { Item } from "./types/item"; + +export interface MoveFunction { + sourceChain: any; + destinationChain: any; + stream: string; + item: Item; +} + +export interface MigrationCompleted { + sourceChainTx?: string; + destinationChainTx?: string; + additionalData?: any; + status: MigrationStatus; +} + +export enum MigrationStatus { + Ok = "OK", + Skipped = "Skipped", + Failed = "Failed", +} + +export interface VerifyParams { + sourceChain: any; + destinationChain: any; + stream: string; + sourceChainTx: string; + destinationChainTx: string; + status?: MigrationStatus; + additionalData?: any; +} + +export interface MigrateFunction { + stream: string; + + function(params: MoveFunction): Promise; + + verifier(params: VerifyParams): Promise; +} + +export type CustomMigrations = { + [streamName: string]: MigrateFunction; +}; + +const createStreamIfNotExists = async ( + destination: any, + streamName: string, + kind: any +) => { + try { + const streams = await listStreams(destination); + if (!streams.find((stream) => stream.name === streamName)) { + await createStream(destination, "stream", streamName, { + kind, + }); + console.log(`Created ${streamName} stream`); + } else { + console.log("Using existing stream: ", streamName); + } + } catch (err) { + console.log("Error while creating destination stream!", err); + } +}; + +const migrateStream = async ( + sourceChain: any, + destinationChain: any, + stream: string, + mover?: MigrateFunction +): Promise => { + const allItemsOnChain = await getAllStreamItems(sourceChain, stream); + + const result = []; + // move the events using the default method + for (const item of allItemsOnChain) { + if (!mover) { + try { + let itemToMigrate = item; + const txId = item.txid; + + if ( + item.data && + item.data.hasOwnProperty("vout") && + item.data.hasOwnProperty("txid") + ) { + itemToMigrate = await getFromTxOutData(sourceChain, item); + } + const req = await createStreamItem( + destinationChain, + stream, + item.keys, + item + ); + console.log( + `Created key ${JSON.stringify( + item.keys + )} on destination chain with tx ${req}` + ); + + result.push({ + destinationChainTx: req, + sourceChainTx: txId, + status: MigrationStatus.Ok, + }); + } catch (error) { + throw new Error(error); + } + } else { + try { + // move with custom migration function + const req = await mover.function({ + sourceChain, + destinationChain, + stream, + item, + }); + if (!(req.status === MigrationStatus.Ok)) { + console.warn( + `Item was not migrate to destination chain! Result was: ${JSON.stringify( + req + )} + THIS CAN BE IGNORED IF YOU KNOW WHAT YOU ARE DOING!` + ); + continue; + } + + console.log( + `Created key ${JSON.stringify( + item.keys + )} on destination chain with tx ${req.destinationChainTx}` + ); + result.push({ + destinationChainTx: req.destinationChainTx, + sourceChainTx: req.sourceChainTx, + status: req.status, + additionalData: req.additionalData, + }); + } catch (error) { + throw new Error(error); + } + } + } + return result; +}; + +export const migrate = async ( + sourceChain: any, + destinationChain: any, + streamBlacklist: string[] = [], + customMigrations: CustomMigrations = {} +) => { + try { + const streamsOnSourceChain = await getAllStreams(sourceChain); + if (!streamsOnSourceChain) throw Error("No streams on source chain"); + + // filter out streams + const streamsOfInterest = streamsOnSourceChain.filter( + (stream) => !streamBlacklist.includes(stream.name) + ); + + for (const stream of streamsOfInterest) { + const customMigration = customMigrations[stream.name]; + + await createStreamIfNotExists( + destinationChain, + stream.name, + stream.details.kind + ); + + const migratedStream = await migrateStream( + sourceChain, + destinationChain, + stream.name, + customMigration + ); + if (!migratedStream) + throw new Error(`Can not migrate stream! ${JSON.stringify(stream)}`); + + // verify the migrated events are valid + for (const streamItem of migratedStream) { + if ( + !streamItem || + !streamItem.sourceChainTx || + !streamItem.destinationChainTx + ) + throw new Error( + `Assertion failed! Incomplete stream item ! ${JSON.stringify( + streamItem + )}` + ); + + const assertion: AssertParams = { + sourceChain, + destinationChain, + stream: stream.name, + sourceChainTx: streamItem.sourceChainTx, + destinationChainTx: streamItem.destinationChainTx, + additionalData: streamItem.additionalData, + }; + + !customMigration + ? await assertStreamItem(assertion) + : await customMigration.verifier(assertion); + } + } + } catch (error) { + console.error(error); + throw new Error(error); + } + return true; +}; diff --git a/migration/src/rpc.ts b/migration/src/rpc.ts new file mode 100644 index 000000000..aa5022eea --- /dev/null +++ b/migration/src/rpc.ts @@ -0,0 +1,251 @@ +import { Address } from "./types/address"; +import { Item } from "./types/item"; +import { Stream, StreamKey } from "./types/stream"; +import { StreamItem } from "./types/streamItem"; + +export const info = (multichain: any) => { + return multichain.getInfo((err: any, info: any) => { + if (err) { + throw err; + } + return info; + }); +}; + +export const listStreams = (multichain: any): Promise => { + return new Promise((resolve, reject) => { + multichain.listStreams((err: any, stream: any) => { + if (err) { + reject(err); + } + resolve(stream); + }); + }); +}; + +export const listStreamItems = ( + multichain: any, + stream: string +): Promise => { + return new Promise((resolve, reject) => { + multichain.listStreamItems( + { + stream, + verbose: true, + count: 1000, + }, + + (err: any, itmes: any) => { + if (err) { + reject(err); + } + resolve(itmes); + } + ); + }); +}; + +export const streamItem = ( + multichain: any, + stream: string, + tx: string +): Promise => { + return new Promise((resolve, reject) => { + multichain.getStreamItem( + { + stream, + txid: tx, + verbose: true, + }, + (err: any, itmes: any) => { + if (err) { + reject(err); + } + resolve(itmes); + } + ); + }); +}; + +export const createStreamItem = ( + multichain: any, + stream: string, + key: string[], + item: Item +): Promise => { + return new Promise((resolve, reject) => { + multichain.publish( + { + stream, + verbose: true, + key, + data: { json: item.data.json }, + }, + async (err: any, itmes: any) => { + if (err) { + return reject(err); + } + resolve(itmes); + } + ); + }); +}; + +export const createStream = async ( + multichain: any, + type: string, + name: string, + details: any +) => { + return new Promise((resolve, reject) => { + multichain.create( + { + type, + name, + open: true, + details, + }, + (err: any, itmes: any) => { + if (err) { + reject(err); + } + resolve(itmes); + } + ); + }); +}; + +export const listStreamKeys = ( + multichain: any, + stream: string +): Promise => { + return new Promise((resolve, reject) => { + multichain.listStreamKeys( + { + stream, + count: 1000000, + }, + (err: any, itmes: any) => { + if (err) { + reject(err); + } + resolve(itmes); + } + ); + }); +}; + +export const listStreamKeyItems = ( + multichain: any, + stream: string, + key: string +): Promise => { + return new Promise((resolve, reject) => { + multichain.listStreamKeyItems( + { + stream, + key, + count: 1000000, + }, + (err: any, itmes: any) => { + if (err) { + reject(err); + } + resolve(itmes); + } + ); + }); +}; + +export const getItemByTx = ( + multichain: any, + stream: string, + txid: string +): Promise => { + return new Promise((resolve, reject) => { + multichain.getStreamItem( + { + stream, + txid, + }, + (err: any, item: any) => { + if (err) { + reject(err); + } + resolve(item); + } + ); + }); +}; + +export const getTxOutData = ( + multichain: any, + txid: string, + vout: string +): Promise => { + return new Promise((resolve, reject) => { + multichain.getTxOutData( + { + txid, + vout, + }, + (err: any, itmes: any) => { + if (err) { + reject(err); + } + resolve(itmes); + } + ); + }); +}; +export const importPrivKey = (multichain: any, key: any) => { + return new Promise((resolve, reject) => { + multichain.importPrivKey( + { + privkey: key, + rescan: true, + }, + (err: any, data: any) => { + if (err) { + reject(err); + } + resolve(data); + } + ); + }); +}; + +export const dumpPrivKey = ( + multichain: any, + address: string +): Promise => { + return new Promise((resolve, reject) => { + multichain.dumpPrivKey( + { + address, + }, + (err: any, data: any) => { + if (err) { + reject(err); + } + resolve(data); + } + ); + }); +}; + +export const getAddresses = (multichain: any): Promise => { + return new Promise((resolve, reject) => { + multichain.getAddresses( + { + verbose: true, + }, + (err: any, data: any) => { + if (err) { + reject(err); + } + resolve(data); + } + ); + }); +}; diff --git a/migration/src/types/address.d.ts b/migration/src/types/address.d.ts new file mode 100644 index 000000000..46af927a7 --- /dev/null +++ b/migration/src/types/address.d.ts @@ -0,0 +1,11 @@ +export interface Address { + address: string; + ismine: boolean; + iswatchonly: boolean; + isscript: boolean; + pubkey: string; + iscompressed: boolean; + account: string; + synchronized: boolean; + startblock?: number; +} diff --git a/migration/src/types/item.d.ts b/migration/src/types/item.d.ts new file mode 100644 index 000000000..97698c39f --- /dev/null +++ b/migration/src/types/item.d.ts @@ -0,0 +1,27 @@ +//Base Item. Most items can inherit from here +export interface Item { + publishers: string[]; + keys: string[]; + offchain: boolean; + available: boolean; + data: Data; + confirmations: number; + blockhash: string; + blockindex: number; + blocktime: number; + txid: string; + vout: number; + valid: boolean; + time: number; + timereceived: number; +} + +export interface Data { + json: DataJSON; +} + +export interface DataJSON { + type: string; + source: string; + publisher: string; +} diff --git a/migration/src/types/stream.d.ts b/migration/src/types/stream.d.ts new file mode 100644 index 000000000..31082ed99 --- /dev/null +++ b/migration/src/types/stream.d.ts @@ -0,0 +1,43 @@ +export interface Stream { + name: string; + createtxid: string; + streamref: string; + restrict: Restrict; + details: Details; + subscribed: boolean; + retrieve: boolean; + indexes: Indexes; + synchronized: boolean; + items: number; + confirmed: number; + keys: number; + publishers: number; +} + +export interface Details {} + +export interface Indexes { + items: boolean; + keys: boolean; + publishers: boolean; + itemsLocal: boolean; + keysLocal: boolean; + publishersLocal: boolean; +} + +export interface Restrict { + write: boolean; + onchain: boolean; + offchain: boolean; +} + +export interface StreamInfo { + name: string; + details: { kind: string }; +} + +export interface StreamKey { + key: string; + items: number; + confirmed: number; +} diff --git a/migration/src/types/streamItem.d.ts b/migration/src/types/streamItem.d.ts new file mode 100644 index 000000000..229d93816 --- /dev/null +++ b/migration/src/types/streamItem.d.ts @@ -0,0 +1,25 @@ +export interface StreamItem { + publishers: string[]; + keys: string[]; + offchain: boolean; + available: boolean; + data: Data; + confirmations: number; + blockhash: string; + blockindex: number; + blocktime: number; + txid: string; + vout: number; + valid: boolean; + time: number; + timereceived: number; +} + +export interface Data { + json: JSON; +} + +export interface JSON { + address: string; + privkey: string; +} diff --git a/migration/src/types/user.d.ts b/migration/src/types/user.d.ts new file mode 100644 index 000000000..4a4719478 --- /dev/null +++ b/migration/src/types/user.d.ts @@ -0,0 +1,27 @@ +export interface CreateUser { + type: string; + source: string; + publisher: string; + user: User; + time: Date; +} + +export interface User { + id: string; + displayName: string; + organization: string; + passwordHash: string; + address: string; + encryptedPrivKey: string; + permissions: CreatePermissions; + additionalData: any; +} + +export interface CreatePermissions { + userView: string[]; + userAuthenticate: string[]; + userChangePassword: string[]; + userIntentListPermissions: string[]; + userIntentGrantPermission: string[]; + userIntentRevokePermission: string[]; +} diff --git a/migration/tsconfig.json b/migration/tsconfig.json new file mode 100644 index 000000000..e00f9c69d --- /dev/null +++ b/migration/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "esModuleInterop": true, + "target": "es6", + "moduleResolution": "node", + "sourceMap": true, + "outDir": "out" + }, + "lib": ["es2015"] +} \ No newline at end of file