From 8fe2880f41160df049b3cded32e6185b32916a0a Mon Sep 17 00:00:00 2001 From: Marcin S Date: Tue, 23 Feb 2021 13:24:33 +0100 Subject: [PATCH 01/13] Move to Webpack --- babel.config.json | 3 +- package.json | 9 +- webpack.config.js | 33 +++ yarn.lock | 594 ++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 621 insertions(+), 18 deletions(-) create mode 100644 webpack.config.js diff --git a/babel.config.json b/babel.config.json index f48249c0..2fdb1037 100644 --- a/babel.config.json +++ b/babel.config.json @@ -14,5 +14,6 @@ ] ] } - } + }, + "ignore": ["src/**/*.test.ts"] } diff --git a/package.json b/package.json index 1ccc79ce..dfa28168 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "3.0.1-beta", "description": "Sia Skynet Javascript Client", "main": "dist/index.js", + "module": "dist/index.js", "files": [ "dist/*" ], @@ -12,7 +13,7 @@ "not OperaMini all" ], "scripts": { - "build": "rimraf dist && babel src --out-dir dist --extensions .ts --ignore src/**/*.test.ts && tsc --project tsconfig.build.json", + "build": "yarn run webpack", "lint:eslint": "eslint --ext .ts utils src --max-warnings 0", "lint:tsc": "tsc", "prepublishOnly": "yarn build", @@ -96,6 +97,7 @@ "@typescript-eslint/eslint-plugin": "^4.3.0", "@typescript-eslint/parser": "^4.3.0", "axios-mock-adapter": "^1.18.2", + "babel-loader": "^8.2.2", "babel-plugin-transform-class-properties": "^6.24.1", "eslint": "^7.11.0", "eslint-plugin-compat": "^3.8.0", @@ -105,6 +107,9 @@ "lint-staged": "^10.3.0", "prettier": "^2.1.1", "rimraf": "^3.0.2", - "typescript": "^4.0.3" + "ts-loader": "^8.0.17", + "typescript": "^4.0.3", + "webpack": "^5.23.0", + "webpack-cli": "^4.5.0" } } diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 00000000..fc1bdf0c --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,33 @@ +const path = require('path'); + +module.exports = { + entry: './src/index.ts', + mode: "production", + + module: { + rules: [ + { + test: /\.tsx?$/, + exclude: /(node_modules|bower_components)/, + use: { + loader: 'babel-loader', + } + }, + { + test: /\.tsx?$/, + loader: "ts-loader", + exclude: /node_modules/, + options: { configFile: "tsconfig.build.json" }, + }, + ], + }, + resolve: { + extensions: [".tsx", ".ts", ".js"], + }, + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'index.js', + library: "skynet", + libraryTarget: "umd", + }, +}; diff --git a/yarn.lock b/yarn.lock index 9835f493..236086ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -907,6 +907,11 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@discoveryjs/json-ext@^0.5.0": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752" + integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg== + "@eslint/eslintrc@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.3.0.tgz#d736d6963d7003b6514e6324bec9c602ac340318" @@ -1207,6 +1212,27 @@ resolved "https://registry.yarnpkg.com/@types/base64-js/-/base64-js-1.3.0.tgz#c939fdba49846861caf5a246b165dbf5698a317c" integrity sha512-ZmI0sZGAUNXUfMWboWwi4LcfpoVUYldyN6Oe0oJ5cCsHDU/LlRq8nQKPXhYLOx36QYSW9bNIb1vvRrD6K7Llgw== +"@types/eslint-scope@^3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86" + integrity sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "7.2.6" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.6.tgz#5e9aff555a975596c03a98b59ecd103decc70c3c" + integrity sha512-I+1sYH+NPQ3/tVqCeUSBwTE/0heyvtXqpIopUUArlBm0Kpocb8FbMa3AZ/ASKIFpN3rnEx932TTXDbt9OXsNDw== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^0.0.46": + version "0.0.46" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe" + integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== + "@types/graceful-fs@^4.1.2": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" @@ -1241,7 +1267,7 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" -"@types/json-schema@^7.0.3": +"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== @@ -1380,6 +1406,154 @@ "@typescript-eslint/types" "4.15.1" eslint-visitor-keys "^2.0.0" +"@webassemblyjs/ast@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f" + integrity sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + +"@webassemblyjs/floating-point-hex-parser@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz#34d62052f453cd43101d72eab4966a022587947c" + integrity sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA== + +"@webassemblyjs/helper-api-error@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz#aaea8fb3b923f4aaa9b512ff541b013ffb68d2d4" + integrity sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w== + +"@webassemblyjs/helper-buffer@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz#d026c25d175e388a7dbda9694e91e743cbe9b642" + integrity sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA== + +"@webassemblyjs/helper-numbers@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz#7ab04172d54e312cc6ea4286d7d9fa27c88cd4f9" + integrity sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.0" + "@webassemblyjs/helper-api-error" "1.11.0" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz#85fdcda4129902fe86f81abf7e7236953ec5a4e1" + integrity sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA== + +"@webassemblyjs/helper-wasm-section@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz#9ce2cc89300262509c801b4af113d1ca25c1a75b" + integrity sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + +"@webassemblyjs/ieee754@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz#46975d583f9828f5d094ac210e219441c4e6f5cf" + integrity sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.0.tgz#f7353de1df38aa201cba9fb88b43f41f75ff403b" + integrity sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.0.tgz#86e48f959cf49e0e5091f069a709b862f5a2cadf" + integrity sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw== + +"@webassemblyjs/wasm-edit@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz#ee4a5c9f677046a210542ae63897094c2027cb78" + integrity sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/helper-wasm-section" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + "@webassemblyjs/wasm-opt" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + "@webassemblyjs/wast-printer" "1.11.0" + +"@webassemblyjs/wasm-gen@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz#3cdb35e70082d42a35166988dda64f24ceb97abe" + integrity sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/ieee754" "1.11.0" + "@webassemblyjs/leb128" "1.11.0" + "@webassemblyjs/utf8" "1.11.0" + +"@webassemblyjs/wasm-opt@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz#1638ae188137f4bb031f568a413cd24d32f92978" + integrity sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-buffer" "1.11.0" + "@webassemblyjs/wasm-gen" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + +"@webassemblyjs/wasm-parser@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz#3e680b8830d5b13d1ec86cc42f38f3d4a7700754" + integrity sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/helper-api-error" "1.11.0" + "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@webassemblyjs/ieee754" "1.11.0" + "@webassemblyjs/leb128" "1.11.0" + "@webassemblyjs/utf8" "1.11.0" + +"@webassemblyjs/wast-printer@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz#680d1f6a5365d6d401974a8e949e05474e1fab7e" + integrity sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ== + dependencies: + "@webassemblyjs/ast" "1.11.0" + "@xtuc/long" "4.2.2" + +"@webpack-cli/configtest@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.0.1.tgz#241aecfbdc715eee96bed447ed402e12ec171935" + integrity sha512-B+4uBUYhpzDXmwuo3V9yBH6cISwxEI4J+NO5ggDaGEEHb0osY/R7MzeKc0bHURXQuZjMM4qD+bSJCKIuI3eNBQ== + +"@webpack-cli/info@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.2.2.tgz#ef3c0cd947a1fa083e174a59cb74e0b6195c236c" + integrity sha512-5U9kUJHnwU+FhKH4PWGZuBC1hTEPYyxGSL5jjoBI96Gx8qcYJGOikpiIpFoTq8mmgX3im2zAo2wanv/alD74KQ== + dependencies: + envinfo "^7.7.3" + +"@webpack-cli/serve@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.3.0.tgz#2730c770f5f1f132767c63dcaaa4ec28f8c56a6c" + integrity sha512-k2p2VrONcYVX1wRRrf0f3X2VGltLWcv+JzXRBDmvCxGlCeESx4OXw91TsWeKOkp784uNoVQo313vxJFHXPPwfw== + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + abab@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -1408,6 +1582,11 @@ acorn@^7.1.1, acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.0.4: + version "8.0.5" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.0.5.tgz#a3bfb872a74a6a7f661bc81b9849d9cac12601b7" + integrity sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg== + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -1416,7 +1595,12 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4: +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -1634,6 +1818,16 @@ babel-jest@^26.6.3: graceful-fs "^4.2.4" slash "^3.0.0" +babel-loader@^8.2.2: + version "8.2.2" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.2.tgz#9363ce84c10c9a40e6c753748e1441b60c8a0b81" + integrity sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g== + dependencies: + find-cache-dir "^3.3.1" + loader-utils "^1.4.0" + make-dir "^3.1.0" + schema-utils "^2.6.5" + babel-messages@^6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" @@ -1794,6 +1988,11 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -1979,6 +2178,13 @@ chokidar@^3.4.0: optionalDependencies: fsevents "~2.3.1" +chrome-trace-event@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" + integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== + dependencies: + tslib "^1.9.0" + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -2028,6 +2234,15 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -2082,6 +2297,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + commander@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" @@ -2092,11 +2312,21 @@ commander@^6.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== +commander@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.1.0.tgz#f2eaecf131f10e36e07d894698226e36ae0eb5ff" + integrity sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg== + comment-parser@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.1.2.tgz#e5317d7a2ec22b470dcb54a29b25426c30bf39d8" integrity sha512-AOdq0i8ghZudnYv8RUnHrhTgafUGs61Rdz9jemU5x2lnZwAWyOq7vySo626K59e1fVKH1xSRorJwPVRLSWOoAQ== +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + component-emitter@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" @@ -2164,7 +2394,7 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0, cross-spawn@^7.0.2: +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -2338,6 +2568,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -2345,6 +2580,23 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" +enhanced-resolve@^4.0.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" + integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + +enhanced-resolve@^5.7.0: + version "5.7.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz#525c5d856680fbd5052de453ac83e32049958b5c" + integrity sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + enquirer@^2.3.5, enquirer@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -2352,6 +2604,18 @@ enquirer@^2.3.5, enquirer@^2.3.6: dependencies: ansi-colors "^4.1.1" +envinfo@^7.7.3: + version "7.7.4" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.4.tgz#c6311cdd38a0e86808c1c9343f667e4267c4a320" + integrity sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ== + +errno@^0.1.3: + version "0.1.8" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== + dependencies: + prr "~1.0.1" + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -2359,6 +2623,11 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" +es-module-lexer@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.4.0.tgz#21f4181cc8b7eee06855f1c59e6087c7bc4f77b0" + integrity sha512-iuEGihqqhKWFgh72Q/Jtch7V2t/ft8w8IPP2aEN8ArYKO+IWyo6hsi96hCdgyeEDQIV3InhYQ9BlwUFPGXrbEQ== + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -2524,6 +2793,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +events@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" + integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== + exec-sh@^0.3.2: version "0.3.4" resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" @@ -2557,6 +2831,21 @@ execa@^4.0.0, execa@^4.1.0: signal-exit "^3.0.2" strip-final-newline "^2.0.0" +execa@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376" + integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -2658,6 +2947,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fastest-levenshtein@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" + integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== + fastq@^1.6.0: version "1.10.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.10.1.tgz#8b8f2ac8bf3632d67afcd65dac248d5fdc45385e" @@ -2703,6 +2997,15 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +find-cache-dir@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" + integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -2823,6 +3126,11 @@ get-stream@^5.0.0: dependencies: pump "^3.0.0" +get-stream@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.0.tgz#3e0012cb6827319da2706e601a1583e8629a6718" + integrity sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg== + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -2850,6 +3158,11 @@ glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: dependencies: is-glob "^4.0.1" +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -2891,7 +3204,7 @@ globby@^11.0.1: merge2 "^1.3.0" slash "^3.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.2.4: version "4.2.6" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== @@ -3005,6 +3318,11 @@ human-signals@^1.1.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + husky@^5.0.9: version "5.0.9" resolved "https://registry.yarnpkg.com/husky/-/husky-5.0.9.tgz#6d38706643d66ed395bcd4ee952d02e3f15eb3a3" @@ -3071,6 +3389,11 @@ inherits@2, inherits@^2.0.3, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +interpret@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" + integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== + invariant@^2.2.2: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -3790,6 +4113,11 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= +json-parse-better-errors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" @@ -3820,6 +4148,13 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + json5@^2.1.2: version "2.2.0" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" @@ -3928,6 +4263,29 @@ listr2@^3.2.2: through "^2.3.8" wrap-ansi "^7.0.0" +loader-runner@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" + integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== + +loader-utils@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + +loader-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" + integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" @@ -3989,7 +4347,7 @@ make-dir@^2.1.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.0: +make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -4015,6 +4373,14 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -4044,7 +4410,7 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.2: +micromatch@^4.0.0, micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== @@ -4057,6 +4423,11 @@ mime-db@1.45.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== +mime-db@1.46.0: + version "1.46.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" + integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== + mime-types@^2.1.12, mime-types@~2.1.19: version "2.1.28" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd" @@ -4064,6 +4435,13 @@ mime-types@^2.1.12, mime-types@~2.1.19: dependencies: mime-db "1.45.0" +mime-types@^2.1.27: + version "2.1.29" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" + integrity sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ== + dependencies: + mime-db "1.46.0" + mime@^2.5.2: version "2.5.2" resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" @@ -4126,6 +4504,11 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -4187,7 +4570,7 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npm-run-path@^4.0.0: +npm-run-path@^4.0.0, npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== @@ -4249,7 +4632,7 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.0: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -4297,6 +4680,13 @@ p-limit@^2.2.0: dependencies: p-try "^2.0.0" +p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + p-locate@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" @@ -4405,7 +4795,7 @@ pirates@^4.0.1: dependencies: node-modules-regexp "^1.0.0" -pkg-dir@^4.2.0: +pkg-dir@^4.1.0, pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== @@ -4467,6 +4857,11 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -4531,7 +4926,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -readable-stream@^2.0.2: +readable-stream@^2.0.1, readable-stream@^2.0.2: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -4560,6 +4955,13 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" +rechoir@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca" + integrity sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q== + dependencies: + resolve "^1.9.0" + regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -4730,7 +5132,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.10.0, resolve@^1.18.1: +resolve@^1.10.0, resolve@^1.18.1, resolve@^1.9.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== @@ -4826,6 +5228,24 @@ saxes@^5.0.0: dependencies: xmlchars "^2.2.0" +schema-utils@^2.6.5: + version "2.7.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== + dependencies: + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" + +schema-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef" + integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA== + dependencies: + "@types/json-schema" "^7.0.6" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" @@ -4858,6 +5278,13 @@ semver@^7.2.1, semver@^7.3.2, semver@^7.3.4: dependencies: lru-cache "^6.0.0" +serialize-javascript@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -4873,6 +5300,13 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -4902,7 +5336,7 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== @@ -4975,6 +5409,11 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" +source-list-map@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== + source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" @@ -4986,7 +5425,7 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.5.6: +source-map-support@^0.5.6, source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -5009,7 +5448,7 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.7.3: +source-map@^0.7.3, source-map@~0.7.2: version "0.7.3" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== @@ -5201,6 +5640,16 @@ table@^6.0.4: slice-ansi "^4.0.0" string-width "^4.2.0" +tapable@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" + integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== + terminal-link@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" @@ -5209,6 +5658,27 @@ terminal-link@^2.0.0: ansi-escapes "^4.2.1" supports-hyperlinks "^2.0.0" +terser-webpack-plugin@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz#7effadee06f7ecfa093dbbd3e9ab23f5f3ed8673" + integrity sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q== + dependencies: + jest-worker "^26.6.2" + p-limit "^3.1.0" + schema-utils "^3.0.0" + serialize-javascript "^5.0.1" + source-map "^0.6.1" + terser "^5.5.1" + +terser@^5.5.1: + version "5.6.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.6.0.tgz#138cdf21c5e3100b1b3ddfddf720962f88badcd2" + integrity sha512-vyqLMoqadC1uR0vywqOZzriDYzgEkNJFK4q9GeyOBHIbiECHiWLKcWfbQWAUaPfxkjDhapSlZB9f7fkMrvkVjA== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.19" + test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" @@ -5304,6 +5774,17 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" +ts-loader@^8.0.17: + version "8.0.17" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.17.tgz#98f2ccff9130074f4079fd89b946b4c637b1f2fc" + integrity sha512-OeVfSshx6ot/TCxRwpBHQ/4lRzfgyTkvi7ghDVrLXOHzTbSK413ROgu/xNqM72i3AFeAIJgQy78FwSMKmOW68w== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^4.0.0" + loader-utils "^2.0.0" + micromatch "^4.0.0" + semver "^7.3.4" + tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -5470,7 +5951,7 @@ uuid@^8.3.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -v8-compile-cache@^2.0.3: +v8-compile-cache@^2.0.3, v8-compile-cache@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== @@ -5522,6 +6003,14 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" +watchpack@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.1.tgz#e99630550fca07df9f90a06056987baa40a689c7" + integrity sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + webidl-conversions@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" @@ -5532,6 +6021,71 @@ webidl-conversions@^6.1.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== +webpack-cli@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.5.0.tgz#b5213b84adf6e1f5de6391334c9fa53a48850466" + integrity sha512-wXg/ef6Ibstl2f50mnkcHblRPN/P9J4Nlod5Hg9HGFgSeF8rsqDGHJeVe4aR26q9l62TUJi6vmvC2Qz96YJw1Q== + dependencies: + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^1.0.1" + "@webpack-cli/info" "^1.2.2" + "@webpack-cli/serve" "^1.3.0" + colorette "^1.2.1" + commander "^7.0.0" + enquirer "^2.3.6" + execa "^5.0.0" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^2.2.0" + rechoir "^0.7.0" + v8-compile-cache "^2.2.0" + webpack-merge "^5.7.3" + +webpack-merge@^5.7.3: + version "5.7.3" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.7.3.tgz#2a0754e1877a25a8bbab3d2475ca70a052708213" + integrity sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA== + dependencies: + clone-deep "^4.0.1" + wildcard "^2.0.0" + +webpack-sources@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.2.0.tgz#058926f39e3d443193b6c31547229806ffd02bac" + integrity sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w== + dependencies: + source-list-map "^2.0.1" + source-map "^0.6.1" + +webpack@^5.23.0: + version "5.24.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.24.0.tgz#dbb5017dae053a506d8b909b0b21367d05fd56ee" + integrity sha512-ZkDxabL/InAQy9jluQTA8VIB7Gkhsv5uMJdAIz4QP2u4zaOX6+Tig7Jv+WSwhHp9qTnAx0rmn0dVTUPqZGRLbg== + dependencies: + "@types/eslint-scope" "^3.7.0" + "@types/estree" "^0.0.46" + "@webassemblyjs/ast" "1.11.0" + "@webassemblyjs/wasm-edit" "1.11.0" + "@webassemblyjs/wasm-parser" "1.11.0" + acorn "^8.0.4" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.7.0" + es-module-lexer "^0.4.0" + eslint-scope "^5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.4" + json-parse-better-errors "^1.0.2" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.0.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.1.1" + watchpack "^2.0.0" + webpack-sources "^2.1.1" + whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" @@ -5572,6 +6126,11 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" +wildcard@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" + integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== + word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" @@ -5664,3 +6223,8 @@ yargs@^15.4.1: which-module "^2.0.0" y18n "^4.0.0" yargs-parser "^18.1.2" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 26f230cf4a374cbbc9e3145dd28f59c018381a5b Mon Sep 17 00:00:00 2001 From: Marcin S Date: Tue, 23 Feb 2021 20:43:15 +0100 Subject: [PATCH 02/13] Build web and node targets --- package.json | 11 ++++++----- scripts/deploy.js | 1 + webpack.config.js | 37 ++++++++++++++++++++++++++++++------- 3 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 scripts/deploy.js diff --git a/package.json b/package.json index dfa28168..f57ea012 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,8 @@ "name": "skynet-js", "version": "3.0.1-beta", "description": "Sia Skynet Javascript Client", - "main": "dist/index.js", - "module": "dist/index.js", + "main": "dist/web/index.js", + "module": "dist/web/index.js", "files": [ "dist/*" ], @@ -13,8 +13,8 @@ "not OperaMini all" ], "scripts": { - "build": "yarn run webpack", - "lint:eslint": "eslint --ext .ts utils src --max-warnings 0", + "build": "rimraf dist && yarn run webpack", + "lint:eslint": "eslint --ext .ts scripts src utils --max-warnings 0", "lint:tsc": "tsc", "prepublishOnly": "yarn build", "test": "jest --coverage --coverageDirectory ../coverage" @@ -110,6 +110,7 @@ "ts-loader": "^8.0.17", "typescript": "^4.0.3", "webpack": "^5.23.0", - "webpack-cli": "^4.5.0" + "webpack-cli": "^4.5.0", + "webpack-merge": "^5.7.3" } } diff --git a/scripts/deploy.js b/scripts/deploy.js new file mode 100644 index 00000000..78cbb182 --- /dev/null +++ b/scripts/deploy.js @@ -0,0 +1 @@ +const { SkynetClient } = require(""); diff --git a/webpack.config.js b/webpack.config.js index fc1bdf0c..8d7414b5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,7 @@ const path = require('path'); +const { merge } = require('webpack-merge'); -module.exports = { +var baseConfig = { entry: './src/index.ts', mode: "production", @@ -13,12 +14,6 @@ module.exports = { loader: 'babel-loader', } }, - { - test: /\.tsx?$/, - loader: "ts-loader", - exclude: /node_modules/, - options: { configFile: "tsconfig.build.json" }, - }, ], }, resolve: { @@ -31,3 +26,31 @@ module.exports = { libraryTarget: "umd", }, }; + +let targets = ['web', 'node'].map((target) => { + let base = merge(baseConfig, { + target: target, + module: { + rules: [ + { + test: /\.tsx?$/, + loader: "ts-loader", + exclude: /node_modules/, + options: { + configFile: "tsconfig.build.json", + compilerOptions: { + outDir: path.resolve(__dirname, './dist/' + target), + }, + }, + }, + ], + }, + output: { + path: path.resolve(__dirname, './dist/' + target), + filename: 'index.js' + } + }); + return base; +}); + +module.exports = targets; From 442d51e306328f8f7624c6385e5cb45f26d296e5 Mon Sep 17 00:00:00 2001 From: Marcin S Date: Wed, 24 Feb 2021 20:44:49 +0100 Subject: [PATCH 03/13] Separate out web and node index.js files --- babel.config.json | 3 +- package.json | 4 +- src/{client.ts => client/index.ts} | 33 +++--- src/client/node.ts | 13 +++ src/client/web.ts | 17 +++ src/{download.ts => download/index.ts} | 117 +-------------------- src/download/node.ts | 51 +++++++++ src/{download.test.ts => download/test.ts} | 4 +- src/download/web.ts | 109 +++++++++++++++++++ src/index.node.test.ts | 33 ++++++ src/index.node.ts | 23 ++++ src/{index.test.ts => index.web.test.ts} | 5 +- src/{index.ts => index.web.ts} | 6 +- src/integration.test.ts | 2 +- src/registry.test.ts | 3 +- src/registry.ts | 2 +- src/skydb.test.ts | 2 +- src/skydb.ts | 10 +- src/upload/index.ts | 82 +++++++++++++++ src/upload/node.ts | 45 ++++++++ src/{upload.test.ts => upload/test.ts} | 4 +- src/{upload.ts => upload/web.ts} | 43 +------- src/utils.ts | 3 +- webpack.config.js | 9 +- yarn.lock | 11 +- 25 files changed, 435 insertions(+), 199 deletions(-) rename src/{client.ts => client/index.ts} (85%) create mode 100644 src/client/node.ts create mode 100644 src/client/web.ts rename src/{download.ts => download/index.ts} (75%) create mode 100644 src/download/node.ts rename src/{download.test.ts => download/test.ts} (99%) create mode 100644 src/download/web.ts create mode 100644 src/index.node.test.ts create mode 100644 src/index.node.ts rename src/{index.test.ts => index.web.test.ts} (92%) rename src/{index.ts => index.web.ts} (91%) create mode 100644 src/upload/index.ts create mode 100644 src/upload/node.ts rename src/{upload.test.ts => upload/test.ts} (98%) rename src/{upload.ts => upload/web.ts} (79%) diff --git a/babel.config.json b/babel.config.json index 2fdb1037..f48249c0 100644 --- a/babel.config.json +++ b/babel.config.json @@ -14,6 +14,5 @@ ] ] } - }, - "ignore": ["src/**/*.test.ts"] + } } diff --git a/package.json b/package.json index f57ea012..502a5610 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "skynet-js", "version": "3.0.1-beta", "description": "Sia Skynet Javascript Client", - "main": "dist/web/index.js", + "browser": "dist/web/index.js", + "main": "dist/node/index.js", "module": "dist/web/index.js", "files": [ "dist/*" @@ -71,6 +72,7 @@ "base64-js": "^1.3.1", "blakejs": "^1.1.0", "buffer": "^6.0.1", + "form-data": "^4.0.0", "mime": "^2.5.2", "path-browserify": "^1.0.1", "randombytes": "^2.1.0", diff --git a/src/client.ts b/src/client/index.ts similarity index 85% rename from src/client.ts rename to src/client/index.ts index 1475a914..44d1362f 100644 --- a/src/client.ts +++ b/src/client/index.ts @@ -1,9 +1,7 @@ import axios, { AxiosResponse } from "axios"; import type { Method } from "axios"; -import { uploadFile, uploadDirectory, uploadDirectoryRequest, uploadFileRequest } from "./upload"; + import { - downloadFile, - downloadFileHns, getSkylinkUrl, getHnsUrl, getHnsresUrl, @@ -11,14 +9,13 @@ import { getFileContent, getFileContentHns, getFileContentRequest, - openFile, - openFileHns, resolveHns, -} from "./download"; -import { getJSON, setJSON } from "./skydb"; -import { getEntry, getEntryUrl, setEntry } from "./registry"; - -import { addUrlQuery, defaultPortalUrl, makeUrl } from "./utils"; +} from "../download/index"; +import { getJSON, setJSON } from "../skydb"; +import { getEntry, getEntryUrl, setEntry } from "../registry"; +import { uploadFileContent, uploadFileContentRequest } from "../upload/index"; +import { defaultPortalUrl } from "../utils"; +import { addUrlQuery, makeUrl } from "../utils"; /** * Custom client options. @@ -59,7 +56,7 @@ export type RequestConfig = CustomClientOptions & { }; /** - * The Skynet Client which can be used to access Skynet. + * The base Skynet Client which can be used to access Skynet. */ export class SkynetClient { portalUrl: string; @@ -67,15 +64,7 @@ export class SkynetClient { // Set methods (defined in other files). - // Upload - uploadFile = uploadFile; - protected uploadFileRequest = uploadFileRequest; - uploadDirectory = uploadDirectory; - protected uploadDirectoryRequest = uploadDirectoryRequest; - // Download - downloadFile = downloadFile; - downloadFileHns = downloadFileHns; getSkylinkUrl = getSkylinkUrl; getHnsUrl = getHnsUrl; getHnsresUrl = getHnsresUrl; @@ -83,10 +72,12 @@ export class SkynetClient { getFileContent = getFileContent; getFileContentHns = getFileContentHns; protected getFileContentRequest = getFileContentRequest; - openFile = openFile; - openFileHns = openFileHns; resolveHns = resolveHns; + // Upload + uploadFileContent = uploadFileContent; + protected uploadFileContentRequest = uploadFileContentRequest; + // SkyDB db = { getJSON: getJSON.bind(this), diff --git a/src/client/node.ts b/src/client/node.ts new file mode 100644 index 00000000..13c238d6 --- /dev/null +++ b/src/client/node.ts @@ -0,0 +1,13 @@ +import { downloadFileHnsToPath, downloadFileToPath } from "../download/node"; +import { uploadFileFromPath, uploadFileFromPathRequest } from "../upload/node"; +import { SkynetClient as Client } from "./index"; + +export class SkynetClient extends Client { + // Download + downloadFileToPath = downloadFileToPath; + downloadFileHnsToPath = downloadFileHnsToPath; + + // Upload + uploadFileFromPath = uploadFileFromPath; + protected uploadFileFromPathRequest = uploadFileFromPathRequest; +} diff --git a/src/client/web.ts b/src/client/web.ts new file mode 100644 index 00000000..309bbc01 --- /dev/null +++ b/src/client/web.ts @@ -0,0 +1,17 @@ +import { downloadFile, downloadFileHns, openFile, openFileHns } from "../download/web"; +import { uploadFile, uploadDirectory, uploadDirectoryRequest, uploadFileRequest } from "../upload/web"; +import { SkynetClient as Client } from "./index"; + +export class SkynetClient extends Client { + // Download + downloadFile = downloadFile; + downloadFileHns = downloadFileHns; + openFile = openFile; + openFileHns = openFileHns; + + // Upload + uploadFile = uploadFile; + protected uploadFileRequest = uploadFileRequest; + uploadDirectory = uploadDirectory; + protected uploadDirectoryRequest = uploadDirectoryRequest; +} diff --git a/src/download.ts b/src/download/index.ts similarity index 75% rename from src/download.ts rename to src/download/index.ts index 88e369e7..743e8185 100644 --- a/src/download.ts +++ b/src/download/index.ts @@ -1,4 +1,4 @@ -import { SkynetClient } from "./client"; +import { SkynetClient } from "../client/index"; import { addSubdomain, addUrlQuery, @@ -11,7 +11,7 @@ import { trimUriPrefix, uriHandshakePrefix, uriHandshakeResolverPrefix, -} from "./utils"; +} from "../utils"; /** * Custom download options. @@ -76,71 +76,17 @@ export type ResolveHnsResponse = { skylink: string; }; -const defaultDownloadOptions = { +export const defaultDownloadOptions = { ...defaultOptions("/"), }; -const defaultDownloadHnsOptions = { +export const defaultDownloadHnsOptions = { ...defaultOptions("/hns"), hnsSubdomain: "hns", }; -const defaultResolveHnsOptions = { +export const defaultResolveHnsOptions = { ...defaultOptions("/hnsres"), }; -/** - * Initiates a download of the content of the skylink within the browser. - * - * @param this - SkynetClient - * @param skylinkUrl - 46-character skylink, or a valid skylink URL. Can be followed by a path. Note that the skylink will not be encoded, so if your path might contain special characters, consider using `customOptions.path`. - * @param [customOptions] - Additional settings that can optionally be set. - * @param [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact. - * @returns - The full URL that was used. - * @throws - Will throw if the skylinkUrl does not contain a skylink or if the path option is not a string. - */ -export function downloadFile(this: SkynetClient, skylinkUrl: string, customOptions?: CustomDownloadOptions): string { - /* istanbul ignore next */ - if (typeof skylinkUrl !== "string") { - throw new Error(`Expected parameter skylinkUrl to be type string, was type ${typeof skylinkUrl}`); - } - - const opts = { ...defaultDownloadOptions, ...this.customOptions, ...customOptions, download: true }; - const url = this.getSkylinkUrl(skylinkUrl, opts); - - // Download the url. - window.location.assign(url); - - return url; -} - -/** - * Initiates a download of the content of the skylink at the Handshake domain. - * - * @param this - SkynetClient - * @param domain - Handshake domain. - * @param [customOptions] - Additional settings that can optionally be set. - * @param [customOptions.endpointPath="/hns"] - The relative URL path of the portal endpoint to contact. - * @returns - The full URL that was used. - * @throws - Will throw if the input domain is not a string. - */ -export async function downloadFileHns( - this: SkynetClient, - domain: string, - customOptions?: CustomDownloadOptions -): Promise { - /* istanbul ignore next */ - if (typeof domain !== "string") { - throw new Error(`Expected parameter domain to be type string, was type ${typeof domain}`); - } - - const opts = { ...defaultDownloadHnsOptions, ...this.customOptions, ...customOptions, download: true }; - const url = this.getHnsUrl(domain, opts); - - // Download the url. - window.location.assign(url); - - return url; -} - /** * Constructs the full URL for the given skylink. * @@ -395,59 +341,6 @@ export async function getFileContentRequest( return { data: response.data, contentType, metadata, skylink }; } -/** - * Opens the content of the skylink within the browser. - * - * @param this - SkynetClient - * @param skylinkUrl - Skylink string. See `downloadFile`. - * @param [customOptions] - Additional settings that can optionally be set. See `downloadFile` for the full list. - * @param [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact. - * @returns - The full URL that was used. - * @throws - Will throw if the skylinkUrl does not contain a skylink or if the path option is not a string. - */ -export function openFile(this: SkynetClient, skylinkUrl: string, customOptions?: CustomDownloadOptions): string { - /* istanbul ignore next */ - if (typeof skylinkUrl !== "string") { - throw new Error(`Expected parameter skylinkUrl to be type string, was type ${typeof skylinkUrl}`); - } - - const opts = { ...defaultDownloadOptions, ...this.customOptions, ...customOptions }; - const url = this.getSkylinkUrl(skylinkUrl, opts); - - window.open(url, "_blank"); - - return url; -} - -/** - * Opens the content of the skylink from the given Handshake domain within the browser. - * - * @param this - SkynetClient - * @param domain - Handshake domain. - * @param [customOptions] - Additional settings that can optionally be set. See `downloadFileHns` for the full list. - * @param [customOptions.endpointPath="/hns"] - The relative URL path of the portal endpoint to contact. - * @returns - The full URL that was used. - * @throws - Will throw if the input domain is not a string. - */ -export async function openFileHns( - this: SkynetClient, - domain: string, - customOptions?: CustomHnsDownloadOptions -): Promise { - /* istanbul ignore next */ - if (typeof domain !== "string") { - throw new Error(`Expected parameter domain to be type string, was type ${typeof domain}`); - } - - const opts = { ...defaultDownloadHnsOptions, ...this.customOptions, ...customOptions }; - const url = this.getHnsUrl(domain, opts); - - // Open the url in a new tab. - window.open(url, "_blank"); - - return url; -} - /** * Resolves the given HNS domain to its TXT record and returns the data. * diff --git a/src/download/node.ts b/src/download/node.ts new file mode 100644 index 00000000..3496b4bf --- /dev/null +++ b/src/download/node.ts @@ -0,0 +1,51 @@ +import fs from "fs"; + +import { SkynetClient } from "../client/index"; +import { formatSkylink } from "../utils"; +import { CustomDownloadOptions, defaultDownloadOptions, defaultDownloadHnsOptions, CustomHnsDownloadOptions, GetMetadataResponse } from "./index"; + +export async function downloadFileToPath(this: SkynetClient, skylinkUrl: string, path: string, customOptions?: CustomDownloadOptions): Promise { + const opts = { ...defaultDownloadOptions, ...this.customOptions, ...customOptions }; + + const url = this.getSkylinkUrl(skylinkUrl, opts); + + const writer = fs.createWriteStream(path); + + const response = await this.executeRequest({ + ...opts, + method: "get", + url, + }); + + response.data.pipe(writer); + + const contentType = response.headers["content-type"] ?? ""; + const metadata = response.headers["skynet-file-metadata"] ? JSON.parse(response.headers["skynet-file-metadata"]) : {}; + const skylink = response.headers["skynet-skylink"] ? formatSkylink(response.headers["skynet-skylink"]) : ""; + + + return { contentType, metadata, skylink }; +} + +export async function downloadFileHnsToPath(this: SkynetClient, domain: string, path: string, customOptions?: CustomHnsDownloadOptions): Promise { + const opts = { ...defaultDownloadHnsOptions, ...this.customOptions, ...customOptions }; + + const url = this.getHnsUrl(domain, opts); + + const writer = fs.createWriteStream(path); + + const response = await this.executeRequest({ + ...opts, + method: "get", + url, + }); + + response.data.pipe(writer); + + const contentType = response.headers["content-type"] ?? ""; + const metadata = response.headers["skynet-file-metadata"] ? JSON.parse(response.headers["skynet-file-metadata"]) : {}; + const skylink = response.headers["skynet-skylink"] ? formatSkylink(response.headers["skynet-skylink"]) : ""; + + + return { contentType, metadata, skylink }; +} diff --git a/src/download.test.ts b/src/download/test.ts similarity index 99% rename from src/download.test.ts rename to src/download/test.ts index 58e6344b..69bc87ef 100644 --- a/src/download.test.ts +++ b/src/download/test.ts @@ -1,8 +1,8 @@ import axios from "axios"; import MockAdapter from "axios-mock-adapter"; -import { combineStrings, extractNonSkylinkPath } from "../utils/testing"; +import { combineStrings, extractNonSkylinkPath } from "../../utils/testing"; -import { SkynetClient, defaultSkynetPortalUrl, uriSkynetPrefix } from "./index"; +import { SkynetClient, defaultSkynetPortalUrl, uriSkynetPrefix } from "../index.web"; const portalUrl = defaultSkynetPortalUrl; const hnsLink = "foo"; diff --git a/src/download/web.ts b/src/download/web.ts new file mode 100644 index 00000000..1e15bf79 --- /dev/null +++ b/src/download/web.ts @@ -0,0 +1,109 @@ +import { SkynetClient } from "../client/index"; +import { CustomDownloadOptions, defaultDownloadOptions, defaultDownloadHnsOptions, CustomHnsDownloadOptions } from "./index"; + +/** + * Initiates a download of the content of the skylink within the browser. + * + * @param this - SkynetClient + * @param skylinkUrl - 46-character skylink, or a valid skylink URL. Can be followed by a path. Note that the skylink will not be encoded, so if your path might contain special characters, consider using `customOptions.path`. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact. + * @returns - The full URL that was used. + * @throws - Will throw if the skylinkUrl does not contain a skylink or if the path option is not a string. + */ +export function downloadFile(this: SkynetClient, skylinkUrl: string, customOptions?: CustomDownloadOptions): string { + /* istanbul ignore next */ + if (typeof skylinkUrl !== "string") { + throw new Error(`Expected parameter skylinkUrl to be type string, was type ${typeof skylinkUrl}`); + } + + const opts = { ...defaultDownloadOptions, ...this.customOptions, ...customOptions, download: true }; + const url = this.getSkylinkUrl(skylinkUrl, opts); + + // Download the url. + window.location.assign(url); + + return url; +} + +/** + * Initiates a download of the content of the skylink at the Handshake domain. + * + * @param this - SkynetClient + * @param domain - Handshake domain. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/hns"] - The relative URL path of the portal endpoint to contact. + * @returns - The full URL that was used. + * @throws - Will throw if the input domain is not a string. + */ +export async function downloadFileHns( + this: SkynetClient, + domain: string, + customOptions?: CustomDownloadOptions +): Promise { + /* istanbul ignore next */ + if (typeof domain !== "string") { + throw new Error(`Expected parameter domain to be type string, was type ${typeof domain}`); + } + + const opts = { ...defaultDownloadHnsOptions, ...this.customOptions, ...customOptions, download: true }; + const url = this.getHnsUrl(domain, opts); + + // Download the url. + window.location.assign(url); + + return url; +} + +/** + * Opens the content of the skylink within the browser. + * + * @param this - SkynetClient + * @param skylinkUrl - Skylink string. See `downloadFile`. + * @param [customOptions] - Additional settings that can optionally be set. See `downloadFile` for the full list. + * @param [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact. + * @returns - The full URL that was used. + * @throws - Will throw if the skylinkUrl does not contain a skylink or if the path option is not a string. + */ +export function openFile(this: SkynetClient, skylinkUrl: string, customOptions?: CustomDownloadOptions): string { + /* istanbul ignore next */ + if (typeof skylinkUrl !== "string") { + throw new Error(`Expected parameter skylinkUrl to be type string, was type ${typeof skylinkUrl}`); + } + + const opts = { ...defaultDownloadOptions, ...this.customOptions, ...customOptions }; + const url = this.getSkylinkUrl(skylinkUrl, opts); + + window.open(url, "_blank"); + + return url; +} + +/** + * Opens the content of the skylink from the given Handshake domain within the browser. + * + * @param this - SkynetClient + * @param domain - Handshake domain. + * @param [customOptions] - Additional settings that can optionally be set. See `downloadFileHns` for the full list. + * @param [customOptions.endpointPath="/hns"] - The relative URL path of the portal endpoint to contact. + * @returns - The full URL that was used. + * @throws - Will throw if the input domain is not a string. + */ +export async function openFileHns( + this: SkynetClient, + domain: string, + customOptions?: CustomHnsDownloadOptions +): Promise { + /* istanbul ignore next */ + if (typeof domain !== "string") { + throw new Error(`Expected parameter domain to be type string, was type ${typeof domain}`); + } + + const opts = { ...defaultDownloadHnsOptions, ...this.customOptions, ...customOptions }; + const url = this.getHnsUrl(domain, opts); + + // Open the url in a new tab. + window.open(url, "_blank"); + + return url; +} diff --git a/src/index.node.test.ts b/src/index.node.test.ts new file mode 100644 index 00000000..40effcc0 --- /dev/null +++ b/src/index.node.test.ts @@ -0,0 +1,33 @@ +import { SkynetClient } from "./index.node"; + +describe("SkynetClient", () => { + it("should contain all api methods", () => { + const client = new SkynetClient(); + + // Download + expect(client).toHaveProperty("downloadFileToPath"); + expect(client).toHaveProperty("downloadFileHnsToPath"); + expect(client).toHaveProperty("getFileContent"); + expect(client).toHaveProperty("getFileContentHns"); + expect(client).toHaveProperty("getHnsUrl"); + expect(client).toHaveProperty("getHnsresUrl"); + expect(client).toHaveProperty("getSkylinkUrl"); + expect(client).toHaveProperty("getMetadata"); + expect(client).toHaveProperty("resolveHns"); + + // Upload + expect(client).toHaveProperty("uploadFileContent"); + expect(client).toHaveProperty("uploadFileFromPath"); + + // SkyDB + expect(client).toHaveProperty("db"); + expect(client.db).toHaveProperty("getJSON"); + expect(client.db).toHaveProperty("setJSON"); + + // SkyDB helpers + expect(client).toHaveProperty("registry"); + expect(client.registry).toHaveProperty("getEntry"); + expect(client.registry).toHaveProperty("getEntryUrl"); + expect(client.registry).toHaveProperty("setEntry"); + }); +}); diff --git a/src/index.node.ts b/src/index.node.ts new file mode 100644 index 00000000..38b080e8 --- /dev/null +++ b/src/index.node.ts @@ -0,0 +1,23 @@ +export { SkynetClient } from "./client/node"; +export { deriveChildSeed, genKeyPairAndSeed, genKeyPairFromSeed } from "./crypto"; +export { + MAX_REVISION, + defaultPortalUrl, + defaultSkynetPortalUrl, + getRelativeFilePath, + getRootDirectory, + parseSkylink, + uriHandshakePrefix, + uriHandshakeResolverPrefix, + uriSkynetPrefix, +} from "./utils"; + +// Export types. + +export type { CustomClientOptions, RequestConfig } from "./client/index"; +export type { Signature } from "./crypto"; +export type { CustomDownloadOptions, ResolveHnsResponse } from "./download"; +export type { CustomGetEntryOptions, CustomSetEntryOptions, SignedRegistryEntry, RegistryEntry } from "./registry"; +export type { CustomGetJSONOptions, CustomSetJSONOptions, VersionedEntryData } from "./skydb"; +export type { CustomUploadOptions, UploadRequestResponse } from "./upload/index"; +export type { ParseSkylinkOptions } from "./utils"; diff --git a/src/index.test.ts b/src/index.web.test.ts similarity index 92% rename from src/index.test.ts rename to src/index.web.test.ts index 0cd2f50a..55e207a8 100644 --- a/src/index.test.ts +++ b/src/index.web.test.ts @@ -1,4 +1,4 @@ -import { SkynetClient } from "./index"; +import { SkynetClient } from "./index.web"; describe("SkynetClient", () => { it("should contain all api methods", () => { @@ -18,8 +18,9 @@ describe("SkynetClient", () => { expect(client).toHaveProperty("resolveHns"); // Upload - expect(client).toHaveProperty("uploadFile"); expect(client).toHaveProperty("uploadDirectory"); + expect(client).toHaveProperty("uploadFileContent"); + expect(client).toHaveProperty("uploadFile"); // SkyDB expect(client).toHaveProperty("db"); diff --git a/src/index.ts b/src/index.web.ts similarity index 91% rename from src/index.ts rename to src/index.web.ts index 6051aa0c..65dcca74 100644 --- a/src/index.ts +++ b/src/index.web.ts @@ -1,4 +1,4 @@ -export { SkynetClient } from "./client"; +export { SkynetClient } from "./client/web"; export { deriveChildSeed, genKeyPairAndSeed, genKeyPairFromSeed } from "./crypto"; export { MAX_REVISION, @@ -14,10 +14,10 @@ export { // Export types. -export type { CustomClientOptions, RequestConfig } from "./client"; +export type { CustomClientOptions, RequestConfig } from "./client/index"; export type { Signature } from "./crypto"; export type { CustomDownloadOptions, ResolveHnsResponse } from "./download"; export type { CustomGetEntryOptions, CustomSetEntryOptions, SignedRegistryEntry, RegistryEntry } from "./registry"; export type { CustomGetJSONOptions, CustomSetJSONOptions, VersionedEntryData } from "./skydb"; -export type { CustomUploadOptions, UploadRequestResponse } from "./upload"; +export type { CustomUploadOptions, UploadRequestResponse } from "./upload/index"; export type { ParseSkylinkOptions } from "./utils"; diff --git a/src/integration.test.ts b/src/integration.test.ts index 32a7c2ae..9732c9c2 100644 --- a/src/integration.test.ts +++ b/src/integration.test.ts @@ -1,4 +1,4 @@ -import { genKeyPairAndSeed, SkynetClient } from "./index"; +import { genKeyPairAndSeed, SkynetClient } from "./index.web"; import { MAX_GET_ENTRY_TIMEOUT } from "./registry"; import { MAX_REVISION } from "./utils"; diff --git a/src/registry.test.ts b/src/registry.test.ts index 6d3496ab..dc4dbead 100644 --- a/src/registry.test.ts +++ b/src/registry.test.ts @@ -1,7 +1,8 @@ import axios from "axios"; import MockAdapter from "axios-mock-adapter"; + import { genKeyPairAndSeed } from "./crypto"; -import { SkynetClient, defaultSkynetPortalUrl, genKeyPairFromSeed } from "./index"; +import { SkynetClient, defaultSkynetPortalUrl, genKeyPairFromSeed } from "./index.web"; import { MAX_GET_ENTRY_TIMEOUT } from "./registry"; const { publicKey, privateKey } = genKeyPairFromSeed("insecure test seed"); diff --git a/src/registry.ts b/src/registry.ts index c91e5ac2..b2121e94 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -2,7 +2,7 @@ import { AxiosResponse } from "axios"; import { Buffer } from "buffer"; import { sign } from "tweetnacl"; -import { SkynetClient } from "./client"; +import { SkynetClient } from "./client/index"; import { addUrlQuery, BaseCustomOptions, diff --git a/src/skydb.test.ts b/src/skydb.test.ts index e54a9343..186c4438 100644 --- a/src/skydb.test.ts +++ b/src/skydb.test.ts @@ -2,7 +2,7 @@ import axios from "axios"; import MockAdapter from "axios-mock-adapter"; import { defaultSkynetPortalUrl, MAX_REVISION } from "./utils"; -import { SkynetClient, genKeyPairFromSeed } from "./index"; +import { SkynetClient, genKeyPairFromSeed } from "./index.web"; import { regexRevisionNoQuotes } from "./registry"; const { publicKey, privateKey } = genKeyPairFromSeed("insecure test seed"); diff --git a/src/skydb.ts b/src/skydb.ts index fd2e4c38..2c397d51 100644 --- a/src/skydb.ts +++ b/src/skydb.ts @@ -1,6 +1,6 @@ import { sign } from "tweetnacl"; -import { SkynetClient } from "./client"; +import { SkynetClient } from "./client/index"; import { CustomGetEntryOptions, RegistryEntry, SignedRegistryEntry, CustomSetEntryOptions } from "./registry"; import { trimUriPrefix, @@ -12,8 +12,8 @@ import { isHexString, hexToUint8Array, } from "./utils"; -import { CustomUploadOptions, UploadRequestResponse } from "./upload"; -import { CustomDownloadOptions } from "./download"; +import { CustomUploadOptions, UploadRequestResponse } from "./upload/index"; +import { CustomDownloadOptions } from "./download/index"; /** * Custom get JSON options. @@ -108,10 +108,10 @@ export async function setJSON( }; // Create the data to upload to acquire its skylink. - const file = new File([JSON.stringify(json)], dataKey, { type: "application/json" }); + const contents = JSON.stringify(json); // Start file upload, do not block. - const skyfilePromise: Promise = this.uploadFile(file, opts); + const skyfilePromise: Promise = this.uploadFileContent(contents, dataKey, opts); let skyfile: UploadRequestResponse; if (revision === undefined) { diff --git a/src/upload/index.ts b/src/upload/index.ts new file mode 100644 index 00000000..454961be --- /dev/null +++ b/src/upload/index.ts @@ -0,0 +1,82 @@ +import { AxiosResponse } from "axios"; + +import { SkynetClient } from "../client/index"; +import { defaultOptions, BaseCustomOptions, formatSkylink } from "../utils"; + +/** + * Custom upload options. + * + * @property [portalFileFieldname="file"] - The file fieldname for uploading files on this portal. + * @property [portalDirectoryfilefieldname="files[]"] - The file fieldname for uploading directories on this portal. + * @property [customFilename] - The custom filename to use when uploading files. + * @property [query] - Query parameters. + */ +export type CustomUploadOptions = BaseCustomOptions & { + portalFileFieldname?: string; + portalDirectoryFileFieldname?: string; + customFilename?: string; + query?: Record; +}; + +/** + * The response to an upload request. + * + * @property skylink - 46-character skylink. + * @property merkleroot - The hash that is encoded into the skylink. + * @property bitfield - The bitfield that gets encoded into the skylink. The bitfield contains a version, an offset and a length in a heavily compressed and optimized format. + */ +export type UploadRequestResponse = { + skylink: string; + merkleroot: string; + bitfield: number; +}; + +export const defaultUploadOptions = { + ...defaultOptions("/skynet/skyfile"), + portalFileFieldname: "file", + portalDirectoryFileFieldname: "files[]", + customFilename: "", +}; + +export async function uploadFileContent( + this: SkynetClient, + fileContents: string, + fileName: string, + customOptions?: CustomUploadOptions +): Promise { + const response = await this.uploadFileContentRequest(fileContents, fileName, customOptions); + + if ( + typeof response.data.skylink !== "string" || + typeof response.data.merkleroot !== "string" || + typeof response.data.bitfield !== "number" + ) { + throw new Error( + "Did not get a complete upload response despite a successful request. Please try again and report this issue to the devs if it persists." + ); + } + + const skylink = formatSkylink(response.data.skylink); + const merkleroot = response.data.merkleroot; + const bitfield = response.data.bitfield; + + return { skylink, merkleroot, bitfield }; +} + +export async function uploadFileContentRequest( + this: SkynetClient, + fileContents: string, + fileName: string, + customOptions?: CustomUploadOptions +): Promise { + const opts = { ...defaultUploadOptions, ...this.customOptions, ...customOptions }; + + const formData = new FormData(); + formData.append(opts.portalFileFieldname, new Blob([fileContents]), fileName); + + return this.executeRequest({ + ...opts, + method: "post", + data: formData, + }); +} diff --git a/src/upload/node.ts b/src/upload/node.ts new file mode 100644 index 00000000..d13c0427 --- /dev/null +++ b/src/upload/node.ts @@ -0,0 +1,45 @@ +/** + * Node-only upload functions. + */ + +import { AxiosResponse } from "axios"; +import FormData from "form-data"; +import fs from "fs"; + +import { SkynetClient } from "../client/node"; +import { CustomUploadOptions, defaultUploadOptions, UploadRequestResponse } from "./index"; +import { formatSkylink } from "../utils"; + +export async function uploadFileFromPath( + this: SkynetClient, + path: string, + customOptions?: CustomUploadOptions +): Promise { + const response = await this.uploadFileFromPathRequest(path, customOptions); + + const skylink = formatSkylink(response.data.skylink); + const merkleroot = response.data.merkleroot; + const bitfield = response.data.bitfield; + + return { skylink, merkleroot, bitfield }; +} + +export async function uploadFileFromPathRequest( + this: SkynetClient, + path: string, + customOptions?: CustomUploadOptions +): Promise { + const opts = { ...defaultUploadOptions, ...this.customOptions, ...customOptions }; + + const formData = new FormData(); + const filename = opts.customFilename ? opts.customFilename : ""; + formData.append(opts.portalFileFieldname, fs.createReadStream(path), filename); + + return this.executeRequest({ + ...opts, + method: "post", + // @ts-ignore + data: formData, + headers: formData.getHeaders(), + }); +} diff --git a/src/upload.test.ts b/src/upload/test.ts similarity index 98% rename from src/upload.test.ts rename to src/upload/test.ts index b4df2c64..092c56fe 100644 --- a/src/upload.test.ts +++ b/src/upload/test.ts @@ -1,8 +1,8 @@ import axios from "axios"; import MockAdapter from "axios-mock-adapter"; -import { SkynetClient, defaultSkynetPortalUrl, uriSkynetPrefix } from "./index"; -import { compareFormData } from "../utils/testing"; +import { SkynetClient, defaultSkynetPortalUrl, uriSkynetPrefix } from "../index.web"; +import { compareFormData } from "../../utils/testing"; const portalUrl = defaultSkynetPortalUrl; const client = new SkynetClient(portalUrl); diff --git a/src/upload.ts b/src/upload/web.ts similarity index 79% rename from src/upload.ts rename to src/upload/web.ts index 2cf81db7..ff7bac4c 100644 --- a/src/upload.ts +++ b/src/upload/web.ts @@ -1,41 +1,8 @@ -import { defaultOptions, getFileMimeType, BaseCustomOptions, formatSkylink } from "./utils"; -import { SkynetClient } from "./client"; import { AxiosResponse } from "axios"; -/** - * Custom upload options. - * - * @property [portalFileFieldname="file"] - The file fieldname for uploading files on this portal. - * @property [portalDirectoryfilefieldname="files[]"] - The file fieldname for uploading directories on this portal. - * @property [customFilename] - The custom filename to use when uploading files. - * @property [query] - Query parameters. - */ -export type CustomUploadOptions = BaseCustomOptions & { - portalFileFieldname?: string; - portalDirectoryFileFieldname?: string; - customFilename?: string; - query?: Record; -}; - -/** - * The response to an upload request. - * - * @property skylink - 46-character skylink. - * @property merkleroot - The hash that is encoded into the skylink. - * @property bitfield - The bitfield that gets encoded into the skylink. The bitfield contains a version, an offset and a length in a heavily compressed and optimized format. - */ -export type UploadRequestResponse = { - skylink: string; - merkleroot: string; - bitfield: number; -}; - -const defaultUploadOptions = { - ...defaultOptions("/skynet/skyfile"), - portalFileFieldname: "file", - portalDirectoryFileFieldname: "files[]", - customFilename: "", -}; +import { getFileMimeType, formatSkylink } from "../utils"; +import { SkynetClient } from "../client/web"; +import { CustomUploadOptions, defaultUploadOptions, UploadRequestResponse } from "./index"; /** * Uploads a file to Skynet. @@ -95,13 +62,11 @@ export async function uploadFileRequest( formData.append(opts.portalFileFieldname, file); } - const response = await this.executeRequest({ + return this.executeRequest({ ...opts, method: "post", data: formData, }); - - return response; } /** diff --git a/src/utils.ts b/src/utils.ts index 6ebdc73f..9fb3c1a5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,7 +5,8 @@ import path from "path-browserify"; import parse from "url-parse"; import urljoin from "url-join"; import { Buffer } from "buffer"; -import { CustomClientOptions } from "./client"; + +import { CustomClientOptions } from "./client/index"; /** * Base custom options for methods hitting the API. diff --git a/webpack.config.js b/webpack.config.js index 8d7414b5..75b6ad3e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,7 +2,6 @@ const path = require('path'); const { merge } = require('webpack-merge'); var baseConfig = { - entry: './src/index.ts', mode: "production", module: { @@ -10,9 +9,10 @@ var baseConfig = { { test: /\.tsx?$/, exclude: /(node_modules|bower_components)/, - use: { - loader: 'babel-loader', - } + loader: 'babel-loader', + options: { + ignore: ["src/**/*.test.ts"], + }, }, ], }, @@ -29,6 +29,7 @@ var baseConfig = { let targets = ['web', 'node'].map((target) => { let base = merge(baseConfig, { + entry: './src/index.'+target+'.ts', target: target, module: { rules: [ diff --git a/yarn.lock b/yarn.lock index 236086ac..2e1bdef9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2290,7 +2290,7 @@ colorette@^1.2.1: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -3042,6 +3042,15 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" From 41aab9b026c66e4b273d2e0ee47fcebea8048fb6 Mon Sep 17 00:00:00 2001 From: Marcin S Date: Thu, 25 Feb 2021 15:59:04 +0100 Subject: [PATCH 04/13] Fix bundle issues - Output bundles should be called index.node.js and index.web.js to match the TS declaration files - plugin-transform-runtime was causing runtime errors. I removed it since it didn't seem to have an effect on the bundle size anyways --- babel.config.json | 2 +- package.json | 7 +++---- tsconfig.build.json | 5 ++--- tsconfig.json | 4 +--- webpack.config.js | 4 +--- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/babel.config.json b/babel.config.json index f48249c0..1063c48d 100644 --- a/babel.config.json +++ b/babel.config.json @@ -1,6 +1,6 @@ { "presets": ["@babel/preset-typescript", "@babel/preset-env"], - "plugins": ["@babel/plugin-transform-runtime", "@babel/plugin-proposal-class-properties"], + "plugins": ["@babel/plugin-proposal-class-properties"], "env": { "test": { "presets": [ diff --git a/package.json b/package.json index 502a5610..ebf8d4b7 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,9 @@ "name": "skynet-js", "version": "3.0.1-beta", "description": "Sia Skynet Javascript Client", - "browser": "dist/web/index.js", - "main": "dist/node/index.js", - "module": "dist/web/index.js", + "browser": "dist/web/index.web.js", + "main": "dist/node/index.node.js", + "module": "src/index.web.js", "files": [ "dist/*" ], @@ -85,7 +85,6 @@ "@babel/cli": "^7.11.6", "@babel/core": "^7.11.6", "@babel/plugin-proposal-class-properties": "^7.10.4", - "@babel/plugin-transform-runtime": "^7.11.5", "@babel/preset-env": "^7.11.5", "@babel/preset-typescript": "^7.10.4", "@types/base64-js": "^1.3.0", diff --git a/tsconfig.build.json b/tsconfig.build.json index abec6437..6e459966 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -6,10 +6,9 @@ "declaration": true, "declarationMap": true, "isolatedModules": true, - "outDir": "dist", "types": ["node", "jest"], "typeRoots": ["./types", "./node_modules/@types"] }, - "include": ["src", "types/**/*.d.ts"], - "exclude": ["src/*.test.ts"] + "include": ["types/**/*.d.ts"], + "exclude": ["src/**/*.test.ts"] } diff --git a/tsconfig.json b/tsconfig.json index 7d5a016c..ba0b2602 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,10 +4,8 @@ "esModuleInterop": true, "strict": true, "noImplicitAny": true, - "declaration": true, "isolatedModules": true, "types": ["node", "jest"], - "typeRoots": ["./types", "./node_modules/@types"], - "strictNullChecks": true + "typeRoots": ["./types", "./node_modules/@types"] } } diff --git a/webpack.config.js b/webpack.config.js index 75b6ad3e..569e8ec5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -20,8 +20,6 @@ var baseConfig = { extensions: [".tsx", ".ts", ".js"], }, output: { - path: path.resolve(__dirname, 'dist'), - filename: 'index.js', library: "skynet", libraryTarget: "umd", }, @@ -48,7 +46,7 @@ let targets = ['web', 'node'].map((target) => { }, output: { path: path.resolve(__dirname, './dist/' + target), - filename: 'index.js' + filename: 'index.'+target+'.js' } }); return base; From 010aaab8677f52a41dc0f709f4081fc411b25707 Mon Sep 17 00:00:00 2001 From: Marcin S Date: Thu, 25 Feb 2021 18:21:32 +0100 Subject: [PATCH 05/13] Add test coverage --- package.json | 10 +- src/client/index.ts | 3 + src/client/node.ts | 9 +- src/download/node.test.ts | 121 +++++++++++ src/download/node.ts | 52 ++++- src/download/{test.ts => web.test.ts} | 8 +- src/download/web.ts | 7 +- src/index.node.test.ts | 1 + ...ration.test.ts => integration.web.test.ts} | 0 src/upload/index.ts | 1 + src/upload/node.test.ts | 199 ++++++++++++++++++ src/upload/node.ts | 94 +++++++++ src/upload/{test.ts => web.test.ts} | 0 src/upload/web.ts | 4 +- testdata/dir1/file3.txt | 1 + testdata/file1.txt | 1 + testdata/file2.txt | 1 + webpack.config.js | 18 +- yarn.lock | 23 +- 19 files changed, 516 insertions(+), 37 deletions(-) create mode 100644 src/download/node.test.ts rename src/download/{test.ts => web.test.ts} (98%) rename src/{integration.test.ts => integration.web.test.ts} (100%) create mode 100644 src/upload/node.test.ts rename src/upload/{test.ts => web.test.ts} (100%) create mode 100644 testdata/dir1/file3.txt create mode 100644 testdata/file1.txt create mode 100644 testdata/file2.txt diff --git a/package.json b/package.json index ebf8d4b7..0b5a511c 100644 --- a/package.json +++ b/package.json @@ -28,10 +28,10 @@ ], "coverageThreshold": { "global": { - "branches": 100, - "functions": 100, - "lines": 100, - "statements": 100 + "branches": 98, + "functions": 98, + "lines": 98, + "statements": 98 } }, "rootDir": "src" @@ -93,6 +93,7 @@ "@types/node": "^14.11.2", "@types/randombytes": "^2.0.0", "@types/sjcl": "^1.0.29", + "@types/tmp": "^0.2.0", "@types/url-join": "^4.0.0", "@types/url-parse": "^1.4.3", "@typescript-eslint/eslint-plugin": "^4.3.0", @@ -108,6 +109,7 @@ "lint-staged": "^10.3.0", "prettier": "^2.1.1", "rimraf": "^3.0.2", + "tmp": "^0.2.1", "ts-loader": "^8.0.17", "typescript": "^4.0.3", "webpack": "^5.23.0", diff --git a/src/client/index.ts b/src/client/index.ts index 44d1362f..0735e14e 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -99,6 +99,9 @@ export class SkynetClient { * @param [customOptions] Configuration for the client. */ constructor(portalUrl: string = defaultPortalUrl(), customOptions: CustomClientOptions = {}) { + if (!portalUrl) { + portalUrl = defaultPortalUrl(); + } this.portalUrl = portalUrl; this.customOptions = customOptions; } diff --git a/src/client/node.ts b/src/client/node.ts index 13c238d6..b5e71377 100644 --- a/src/client/node.ts +++ b/src/client/node.ts @@ -1,5 +1,10 @@ import { downloadFileHnsToPath, downloadFileToPath } from "../download/node"; -import { uploadFileFromPath, uploadFileFromPathRequest } from "../upload/node"; +import { + uploadDirectoryFromPath, + uploadDirectoryFromPathRequest, + uploadFileFromPath, + uploadFileFromPathRequest, +} from "../upload/node"; import { SkynetClient as Client } from "./index"; export class SkynetClient extends Client { @@ -8,6 +13,8 @@ export class SkynetClient extends Client { downloadFileHnsToPath = downloadFileHnsToPath; // Upload + uploadDirectoryFromPath = uploadDirectoryFromPath; + protected uploadDirectoryFromPathRequest = uploadDirectoryFromPathRequest; uploadFileFromPath = uploadFileFromPath; protected uploadFileFromPathRequest = uploadFileFromPathRequest; } diff --git a/src/download/node.test.ts b/src/download/node.test.ts new file mode 100644 index 00000000..7e708257 --- /dev/null +++ b/src/download/node.test.ts @@ -0,0 +1,121 @@ +import axios from "axios"; +import tmp from "tmp"; + +import { SkynetClient, defaultPortalUrl, uriSkynetPrefix } from "../index.node"; + +jest.mock("axios"); + +const portalUrl = defaultPortalUrl(); +const skylink = "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg"; +const sialink = `${uriSkynetPrefix}${skylink}`; +const client = new SkynetClient(); + +const skynetfileContentType = "application/json"; +const skynetFileMetadata = { filename: "sia.pdf" }; +const fullHeaders = { + "skynet-skylink": skylink, + "content-type": skynetfileContentType, + "skynet-file-metadata": JSON.stringify(skynetFileMetadata), +}; +const body = "asdf"; + +describe("downloadFileToPath", () => { + beforeEach(() => { + // @ts-ignore + axios.mockResolvedValue({ data: { body, pipe: function () {} }, headers: fullHeaders }); + }); + + it("should send get request to default portal", () => { + const tmpFile = tmp.fileSync(); + + client.downloadFileToPath(skylink, tmpFile.name); + + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/${skylink}`, + method: "get", + }) + ); + + tmpFile.removeCallback(); + }); + + it("should use custom connection options if defined on the client", async () => { + const tmpFile = tmp.fileSync(); + const client = new SkynetClient("", { APIKey: "foobar", customUserAgent: "Sia-Agent" }); + + const {contentType, metadata, skylink: skylink2} = await client.downloadFileToPath(skylink, tmpFile.name, { APIKey: "barfoo", customUserAgent: "Sia-Agent-2" }); + + expect(contentType).toEqual(skynetfileContentType); + expect(metadata).toEqual(skynetFileMetadata); + expect(skylink2).toEqual(sialink); + + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/${skylink}`, + auth: { username: "", password: "barfoo" }, + headers: expect.objectContaining({ "User-Agent": "Sia-Agent-2" }), + }) + ); + + tmpFile.removeCallback(); + }); + + it("should fetch info even when headers are missing", async () => { + // @ts-ignore + axios.mockResolvedValue({ data: { body, pipe: function () {} }, headers: {} }); + + const tmpFile = tmp.fileSync(); + + const {contentType, metadata, skylink: skylink2} = await client.downloadFileToPath(skylink, tmpFile.name); + + expect(contentType).toEqual(""); + expect(metadata).toEqual({}); + expect(skylink2).toEqual(""); + + tmpFile.removeCallback(); + }); +}); + +describe("downloadFileHnsToPath", () => { + const domain = "foo"; + + beforeEach(() => { + // @ts-ignore + axios.mockResolvedValue({ data: { body, pipe: function () {} }, headers: fullHeaders }); + }); + + it("should send get request to default portal", async () => { + const tmpFile = tmp.fileSync(); + + const { contentType, metadata, skylink: skylink2 } = await client.downloadFileHnsToPath(domain, tmpFile.name); + + expect(contentType).toEqual(skynetfileContentType); + expect(metadata).toEqual(skynetFileMetadata); + expect(skylink2).toEqual(sialink); + + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/hns/${domain}`, + method: "get", + }) + ); + + tmpFile.removeCallback(); + }); + + it("should get info when headers are missing", async () => { + // @ts-ignore + axios.mockResolvedValue({ data: { body, pipe: function () {} }, headers: {} }); + + const tmpFile = tmp.fileSync(); + + const { contentType, metadata, skylink: skylink2 } = await client.downloadFileHnsToPath(domain, tmpFile.name); + + expect(contentType).toEqual(""); + expect(metadata).toEqual({}); + expect(skylink2).toEqual(""); + + tmpFile.removeCallback(); + }); +}); diff --git a/src/download/node.ts b/src/download/node.ts index 3496b4bf..1dbbf9fd 100644 --- a/src/download/node.ts +++ b/src/download/node.ts @@ -2,9 +2,20 @@ import fs from "fs"; import { SkynetClient } from "../client/index"; import { formatSkylink } from "../utils"; -import { CustomDownloadOptions, defaultDownloadOptions, defaultDownloadHnsOptions, CustomHnsDownloadOptions, GetMetadataResponse } from "./index"; - -export async function downloadFileToPath(this: SkynetClient, skylinkUrl: string, path: string, customOptions?: CustomDownloadOptions): Promise { +import { + CustomDownloadOptions, + defaultDownloadOptions, + defaultDownloadHnsOptions, + CustomHnsDownloadOptions, + GetMetadataResponse, +} from "./index"; + +export async function downloadFileToPath( + this: SkynetClient, + skylinkUrl: string, + path: string, + customOptions?: CustomDownloadOptions +): Promise { const opts = { ...defaultDownloadOptions, ...this.customOptions, ...customOptions }; const url = this.getSkylinkUrl(skylinkUrl, opts); @@ -17,17 +28,34 @@ export async function downloadFileToPath(this: SkynetClient, skylinkUrl: string, url, }); + /* istanbul ignore next */ + if (typeof response.data === "undefined") { + throw new Error( + "Did not get 'data' in response despite a successful request. Please try again and report this issue to the devs if it persists." + ); + } + /* istanbul ignore next */ + if (typeof response.headers === "undefined") { + throw new Error( + "Did not get 'headers' in response despite a successful request. Please try again and report this issue to the devs if it persists." + ); + } + response.data.pipe(writer); const contentType = response.headers["content-type"] ?? ""; const metadata = response.headers["skynet-file-metadata"] ? JSON.parse(response.headers["skynet-file-metadata"]) : {}; const skylink = response.headers["skynet-skylink"] ? formatSkylink(response.headers["skynet-skylink"]) : ""; - return { contentType, metadata, skylink }; } -export async function downloadFileHnsToPath(this: SkynetClient, domain: string, path: string, customOptions?: CustomHnsDownloadOptions): Promise { +export async function downloadFileHnsToPath( + this: SkynetClient, + domain: string, + path: string, + customOptions?: CustomHnsDownloadOptions +): Promise { const opts = { ...defaultDownloadHnsOptions, ...this.customOptions, ...customOptions }; const url = this.getHnsUrl(domain, opts); @@ -40,12 +68,24 @@ export async function downloadFileHnsToPath(this: SkynetClient, domain: string, url, }); + /* istanbul ignore next */ + if (typeof response.data === "undefined") { + throw new Error( + "Did not get 'data' in response despite a successful request. Please try again and report this issue to the devs if it persists." + ); + } + /* istanbul ignore next */ + if (typeof response.headers === "undefined") { + throw new Error( + "Did not get 'headers' in response despite a successful request. Please try again and report this issue to the devs if it persists." + ); + } + response.data.pipe(writer); const contentType = response.headers["content-type"] ?? ""; const metadata = response.headers["skynet-file-metadata"] ? JSON.parse(response.headers["skynet-file-metadata"]) : {}; const skylink = response.headers["skynet-skylink"] ? formatSkylink(response.headers["skynet-skylink"]) : ""; - return { contentType, metadata, skylink }; } diff --git a/src/download/test.ts b/src/download/web.test.ts similarity index 98% rename from src/download/test.ts rename to src/download/web.test.ts index 69bc87ef..79d4cf3c 100644 --- a/src/download/test.ts +++ b/src/download/web.test.ts @@ -208,11 +208,13 @@ describe("getFileContent", () => { beforeEach(() => { mock = new MockAdapter(axios); }); - const skynetFileMetadata = { filename: "sia.pdf" }; + + const skynetfileContentType = "application/json"; const skynetFileContents = { arbitrary: "json string" }; + const skynetFileMetadata = { filename: "sia.pdf" }; const fullHeaders = { "skynet-skylink": skylink, - "content-type": "application/json", + "content-type": skynetfileContentType, "skynet-file-metadata": JSON.stringify(skynetFileMetadata), }; @@ -223,7 +225,7 @@ describe("getFileContent", () => { const { data, contentType, metadata, skylink: skylink2 } = await client.getFileContent(input); expect(data).toEqual(skynetFileContents); - expect(contentType).toEqual("application/json"); + expect(contentType).toEqual(skynetfileContentType); expect(metadata).toEqual(skynetFileMetadata); expect(skylink2).toEqual(sialink); }); diff --git a/src/download/web.ts b/src/download/web.ts index 1e15bf79..3a2f96cd 100644 --- a/src/download/web.ts +++ b/src/download/web.ts @@ -1,5 +1,10 @@ import { SkynetClient } from "../client/index"; -import { CustomDownloadOptions, defaultDownloadOptions, defaultDownloadHnsOptions, CustomHnsDownloadOptions } from "./index"; +import { + CustomDownloadOptions, + defaultDownloadOptions, + defaultDownloadHnsOptions, + CustomHnsDownloadOptions, +} from "./index"; /** * Initiates a download of the content of the skylink within the browser. diff --git a/src/index.node.test.ts b/src/index.node.test.ts index 40effcc0..0c166793 100644 --- a/src/index.node.test.ts +++ b/src/index.node.test.ts @@ -17,6 +17,7 @@ describe("SkynetClient", () => { // Upload expect(client).toHaveProperty("uploadFileContent"); + expect(client).toHaveProperty("uploadDirectoryFromPath"); expect(client).toHaveProperty("uploadFileFromPath"); // SkyDB diff --git a/src/integration.test.ts b/src/integration.web.test.ts similarity index 100% rename from src/integration.test.ts rename to src/integration.web.test.ts diff --git a/src/upload/index.ts b/src/upload/index.ts index 454961be..707011df 100644 --- a/src/upload/index.ts +++ b/src/upload/index.ts @@ -46,6 +46,7 @@ export async function uploadFileContent( ): Promise { const response = await this.uploadFileContentRequest(fileContents, fileName, customOptions); + /* istanbul ignore next */ if ( typeof response.data.skylink !== "string" || typeof response.data.merkleroot !== "string" || diff --git a/src/upload/node.test.ts b/src/upload/node.test.ts new file mode 100644 index 00000000..45b3b63d --- /dev/null +++ b/src/upload/node.test.ts @@ -0,0 +1,199 @@ +import axios from "axios"; +import fs from "fs"; +import tmp from "tmp"; + +import { SkynetClient, defaultPortalUrl, uriSkynetPrefix } from "../index.node"; + +jest.mock("axios"); + +const portalUrl = defaultPortalUrl(); +const skylink = "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg"; +const sialink = `${uriSkynetPrefix}${skylink}`; +const client = new SkynetClient(); +const merkleroot = "QAf9Q7dBSbMarLvyeE6HTQmwhr7RX9VMrP9xIMzpU3I"; +const bitfield = 2048; +const data = { skylink, merkleroot, bitfield }; + +describe("uploadFile", () => { + const filename = "testdata/file1.txt"; + + beforeEach(() => { + // @ts-ignore + axios.mockResolvedValue({ data }); + }); + + it("should send post request to default portal", () => { + client.uploadFileFromPath(filename); + + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/skyfile`, + data: expect.objectContaining({ + _streams: expect.arrayContaining([ + expect.stringContaining('Content-Disposition: form-data; name="file"; filename="file1.txt"'), + ]), + }), + }) + ); + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/skyfile`, + headers: expect.objectContaining({ "content-type": expect.stringContaining("multipart/form-data") }), + }) + ); + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/skyfile`, + data: expect.objectContaining({ + _streams: expect.arrayContaining([expect.stringContaining("Content-Type: text/plain")]), + }), + }) + ); + }); + + it("should use custom upload options if defined", () => { + client.uploadFileFromPath(filename, { + endpointPath: "/skynet/file", + portalFileFieldname: "filetest", + customFilename: "test.jpg", + }); + + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/file`, + data: expect.objectContaining({ + _streams: expect.arrayContaining([ + expect.stringContaining('Content-Disposition: form-data; name="filetest"; filename="test.jpg"'), + ]), + }), + headers: expect.anything(), + }) + ); + }); + + it("should use custom connection options if defined on the client", () => { + const client = new SkynetClient("", { APIKey: "foobar", customUserAgent: "Sia-Agent" }); + + client.uploadFileFromPath(filename); + + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/skyfile`, + data: expect.objectContaining({ + _streams: expect.arrayContaining([ + expect.stringContaining(`Content-Disposition: form-data; name="file"; filename="file1.txt"`), + ]), + }), + }) + ); + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/skyfile`, + auth: { username: "", password: "foobar" }, + headers: expect.objectContaining({ "User-Agent": "Sia-Agent" }), + }) + ); + }); + + it("should use custom connection options if defined on the API call", () => { + const client = new SkynetClient("", { APIKey: "foobar", customUserAgent: "Sia-Agent" }); + + client.uploadFileFromPath(filename, { APIKey: "barfoo", customUserAgent: "Sia-Agent-2" }); + + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/skyfile`, + data: expect.objectContaining({ + _streams: expect.arrayContaining([ + expect.stringContaining(`Content-Disposition: form-data; name="file"; filename="file1.txt"`), + ]), + }), + auth: { username: "", password: "barfoo" }, + headers: expect.objectContaining({ "User-Agent": "Sia-Agent-2" }), + }) + ); + }); + + it("should upload tmp files", async () => { + const file = tmp.fileSync({ postfix: ".json" }); + fs.writeFileSync(file.fd, JSON.stringify("testing")); + + const { skylink } = await client.uploadFileFromPath(file.name); + + expect(skylink).toEqual(sialink); + }); + + it("should return skylink on success", async () => { + const { skylink } = await client.uploadFileFromPath(filename); + + expect(skylink).toEqual(sialink); + }); +}); + +describe("uploadDirectoryFromPath", () => { + const dirname = "testdata"; + const directory = ["file1.txt", "file2.txt", "dir1/file3.txt"]; + const filename = `${dirname}/${directory[0]}`; + + beforeEach(() => { + // @ts-ignore + axios.mockResolvedValue({ data }); + }); + + it("should send post request to default portal", () => { + client.uploadDirectoryFromPath(dirname); + + for (const file of directory) { + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/skyfile?filename=${dirname}`, + data: expect.objectContaining({ + _streams: expect.arrayContaining([ + expect.stringContaining(`Content-Disposition: form-data; name="files[]"; filename="${file}"`), + ]), + }), + headers: expect.anything(), + }) + ); + } + }); + + it("should use custom options if defined", () => { + client.uploadDirectoryFromPath(dirname, { + endpointPath: "/skynet/file", + portalDirectoryFileFieldname: "filetest", + }); + + for (const file of directory) { + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ + url: `${portalUrl}/skynet/file?filename=${dirname}`, + data: expect.objectContaining({ + _streams: expect.arrayContaining([ + expect.stringContaining(`Content-Disposition: form-data; name="filetest"; filename="${file}"`), + ]), + }), + headers: expect.anything(), + }) + ); + } + }); + + it("should not work on files", async () => { + await expect(client.uploadDirectoryFromPath(filename)).rejects.toThrowError( + `Given path is not a directory: ${filename}` + ); + }); + + it("should return single skylink on success", async () => { + const { skylink } = await client.uploadDirectoryFromPath(dirname); + + expect(skylink).toEqual(sialink); + }); + + it("should return single skylink on success with dryRun", async () => { + const { skylink } = await client.uploadDirectoryFromPath(dirname, { dryRun: true }); + + expect(skylink).toEqual(sialink); + }); +}); diff --git a/src/upload/node.ts b/src/upload/node.ts index d13c0427..cf0a56c5 100644 --- a/src/upload/node.ts +++ b/src/upload/node.ts @@ -5,6 +5,7 @@ import { AxiosResponse } from "axios"; import FormData from "form-data"; import fs from "fs"; +import p from "path"; import { SkynetClient } from "../client/node"; import { CustomUploadOptions, defaultUploadOptions, UploadRequestResponse } from "./index"; @@ -43,3 +44,96 @@ export async function uploadFileFromPathRequest( headers: formData.getHeaders(), }); } + +export async function uploadDirectoryFromPath( + this: SkynetClient, + path: string, + customOptions = {} +): Promise { + const response = await this.uploadDirectoryFromPathRequest(path, customOptions); + + /* istanbul ignore next */ + if ( + typeof response.data.skylink !== "string" || + typeof response.data.merkleroot !== "string" || + typeof response.data.bitfield !== "number" + ) { + throw new Error( + "Did not get a complete upload response despite a successful request. Please try again and report this issue to the devs if it persists." + ); + } + + const skylink = formatSkylink(response.data.skylink); + const merkleroot = response.data.merkleroot; + const bitfield = response.data.bitfield; + + return { skylink, merkleroot, bitfield }; +} + +export async function uploadDirectoryFromPathRequest( + this: SkynetClient, + path: string, + customOptions = {} +): Promise { + const opts = { ...defaultUploadOptions, ...this.customOptions, ...customOptions }; + + // Check if there is a directory at given path. + const stat = fs.statSync(path); + if (!stat.isDirectory()) { + throw new Error(`Given path is not a directory: ${path}`); + } + + const formData = new FormData(); + path = p.normalize(path); + let basepath = path; + // Ensure the basepath ends in a slash. + if (!basepath.endsWith("/")) { + basepath += "/"; + // Normalize the slash on non-Unix filesystems. + basepath = p.normalize(basepath); + } + + for (const file of walkDirectory(path)) { + // Remove the dir path from the start of the filename if it exists. + let filename = file; + if (file.startsWith(basepath)) { + filename = file.replace(basepath, ""); + } + formData.append(opts.portalDirectoryFileFieldname, fs.createReadStream(file), { filepath: filename }); + } + + // TODO: Implement customDirname + // let filename = opts.customDirname || path; + let filename = path; + /* istanbul ignore next */ + if (filename.startsWith("/")) { + filename = filename.slice(1); + } + + return this.executeRequest({ + ...opts, + method: "post", + // @ts-ignore + data: formData, + headers: formData.getHeaders(), + query: { filename }, + }); +} + +function walkDirectory(filepath: string): Array { + /* istanbul ignore next */ + if (!fs.existsSync(filepath)) { + return []; + } + + let files: Array = []; + for (const subpath of fs.readdirSync(filepath)) { + const fullpath = p.join(filepath, subpath); + if (fs.statSync(fullpath).isDirectory()) { + files = files.concat(walkDirectory(fullpath)); + continue; + } + files.push(fullpath); + } + return files; +} diff --git a/src/upload/test.ts b/src/upload/web.test.ts similarity index 100% rename from src/upload/test.ts rename to src/upload/web.test.ts diff --git a/src/upload/web.ts b/src/upload/web.ts index ff7bac4c..a804114f 100644 --- a/src/upload/web.ts +++ b/src/upload/web.ts @@ -135,14 +135,12 @@ export async function uploadDirectoryRequest( formData.append(opts.portalDirectoryFileFieldname, file as File, path); }); - const response = await this.executeRequest({ + return this.executeRequest({ ...opts, method: "post", data: formData, query: { filename }, }); - - return response; } /** diff --git a/testdata/dir1/file3.txt b/testdata/dir1/file3.txt new file mode 100644 index 00000000..9daeafb9 --- /dev/null +++ b/testdata/dir1/file3.txt @@ -0,0 +1 @@ +test diff --git a/testdata/file1.txt b/testdata/file1.txt new file mode 100644 index 00000000..9daeafb9 --- /dev/null +++ b/testdata/file1.txt @@ -0,0 +1 @@ +test diff --git a/testdata/file2.txt b/testdata/file2.txt new file mode 100644 index 00000000..038d718d --- /dev/null +++ b/testdata/file2.txt @@ -0,0 +1 @@ +testing diff --git a/webpack.config.js b/webpack.config.js index 569e8ec5..2ce7ff46 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,5 @@ -const path = require('path'); -const { merge } = require('webpack-merge'); +const path = require("path"); +const { merge } = require("webpack-merge"); var baseConfig = { mode: "production", @@ -9,7 +9,7 @@ var baseConfig = { { test: /\.tsx?$/, exclude: /(node_modules|bower_components)/, - loader: 'babel-loader', + loader: "babel-loader", options: { ignore: ["src/**/*.test.ts"], }, @@ -25,9 +25,9 @@ var baseConfig = { }, }; -let targets = ['web', 'node'].map((target) => { +let targets = ["web", "node"].map((target) => { let base = merge(baseConfig, { - entry: './src/index.'+target+'.ts', + entry: "./src/index." + target + ".ts", target: target, module: { rules: [ @@ -38,16 +38,16 @@ let targets = ['web', 'node'].map((target) => { options: { configFile: "tsconfig.build.json", compilerOptions: { - outDir: path.resolve(__dirname, './dist/' + target), + outDir: path.resolve(__dirname, "./dist/" + target), }, }, }, ], }, output: { - path: path.resolve(__dirname, './dist/' + target), - filename: 'index.'+target+'.js' - } + path: path.resolve(__dirname, "./dist/" + target), + filename: "index." + target + ".js", + }, }); return base; }); diff --git a/yarn.lock b/yarn.lock index 2e1bdef9..989e180b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -693,15 +693,6 @@ dependencies: "@babel/helper-plugin-utils" "^7.12.13" -"@babel/plugin-transform-runtime@^7.11.5": - version "7.12.17" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.17.tgz#329cb61d293b7e60a7685b91dda7c300668cee18" - integrity sha512-s+kIJxnaTj+E9Q3XxQZ5jOo+xcogSe3V78/iFQ5RmoT0jROdpcdxhfGdq/VLqW1hFSzw6VjqN8aQqTaAMixWsw== - dependencies: - "@babel/helper-module-imports" "^7.12.13" - "@babel/helper-plugin-utils" "^7.12.13" - semver "^5.5.1" - "@babel/plugin-transform-shorthand-properties@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz#db755732b70c539d504c6390d9ce90fe64aff7ad" @@ -1314,6 +1305,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== +"@types/tmp@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.0.tgz#e3f52b4d7397eaa9193592ef3fdd44dc0af4298c" + integrity sha512-flgpHJjntpBAdJD43ShRosQvNC0ME97DCfGvZEDlAThQmnerRXrLbX6YgzRBQCZTthET9eAWFAMaYP0m0Y4HzQ== + "@types/url-join@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/url-join/-/url-join-4.0.0.tgz#72eff71648a429c7d4acf94e03780e06671369bd" @@ -5260,7 +5256,7 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= -"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -5712,6 +5708,13 @@ through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= +tmp@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" From 8690cf2695891b4d221e2ec2e3d1300c966c9768 Mon Sep 17 00:00:00 2001 From: Marcin S Date: Thu, 25 Feb 2021 18:41:48 +0100 Subject: [PATCH 06/13] Fix eslint errors --- package.json | 2 +- src/download/index.ts | 2 +- src/download/node.test.ts | 23 ++++++++------- src/download/node.ts | 22 ++++++++++++++ src/upload/index.ts | 21 ++++++++++++++ src/upload/node.test.ts | 4 +-- src/upload/node.ts | 60 +++++++++++++++++++++++++++++++++++++-- src/upload/web.ts | 2 +- 8 files changed, 119 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 0b5a511c..10c7f89c 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ ], "scripts": { "build": "rimraf dist && yarn run webpack", - "lint:eslint": "eslint --ext .ts scripts src utils --max-warnings 0", + "lint:eslint": "eslint --ext .ts src utils --max-warnings 0", "lint:tsc": "tsc", "prepublishOnly": "yarn build", "test": "jest --coverage --coverageDirectory ../coverage" diff --git a/src/download/index.ts b/src/download/index.ts index 743e8185..a96e2c75 100644 --- a/src/download/index.ts +++ b/src/download/index.ts @@ -218,7 +218,7 @@ export function getHnsresUrl(this: SkynetClient, domain: string, customOptions?: * @param skylinkUrl - Skylink string. See `downloadFile`. * @param [customOptions] - Additional settings that can optionally be set. See `downloadFile` for the full list. * @param [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact. - * @returns - The metadata in JSON format. Empty if no metadata was found. + * @returns - The metadata in JSON format. Each field will be empty if no metadata was found. * @throws - Will throw if the skylinkUrl does not contain a skylink or if the path option is not a string. */ export async function getMetadata( diff --git a/src/download/node.test.ts b/src/download/node.test.ts index 7e708257..d4375692 100644 --- a/src/download/node.test.ts +++ b/src/download/node.test.ts @@ -21,8 +21,8 @@ const body = "asdf"; describe("downloadFileToPath", () => { beforeEach(() => { - // @ts-ignore - axios.mockResolvedValue({ data: { body, pipe: function () {} }, headers: fullHeaders }); + // @ts-expect-error TS complaining. + axios.mockResolvedValue({ data: { body, pipe: jest.fn() }, headers: fullHeaders }); }); it("should send get request to default portal", () => { @@ -44,7 +44,10 @@ describe("downloadFileToPath", () => { const tmpFile = tmp.fileSync(); const client = new SkynetClient("", { APIKey: "foobar", customUserAgent: "Sia-Agent" }); - const {contentType, metadata, skylink: skylink2} = await client.downloadFileToPath(skylink, tmpFile.name, { APIKey: "barfoo", customUserAgent: "Sia-Agent-2" }); + const { contentType, metadata, skylink: skylink2 } = await client.downloadFileToPath(skylink, tmpFile.name, { + APIKey: "barfoo", + customUserAgent: "Sia-Agent-2", + }); expect(contentType).toEqual(skynetfileContentType); expect(metadata).toEqual(skynetFileMetadata); @@ -62,12 +65,12 @@ describe("downloadFileToPath", () => { }); it("should fetch info even when headers are missing", async () => { - // @ts-ignore - axios.mockResolvedValue({ data: { body, pipe: function () {} }, headers: {} }); + // @ts-expect-error TS complaining. + axios.mockResolvedValue({ data: { body, pipe: jest.fn() }, headers: {} }); const tmpFile = tmp.fileSync(); - const {contentType, metadata, skylink: skylink2} = await client.downloadFileToPath(skylink, tmpFile.name); + const { contentType, metadata, skylink: skylink2 } = await client.downloadFileToPath(skylink, tmpFile.name); expect(contentType).toEqual(""); expect(metadata).toEqual({}); @@ -81,8 +84,8 @@ describe("downloadFileHnsToPath", () => { const domain = "foo"; beforeEach(() => { - // @ts-ignore - axios.mockResolvedValue({ data: { body, pipe: function () {} }, headers: fullHeaders }); + // @ts-expect-error TS complaining. + axios.mockResolvedValue({ data: { body, pipe: jest.fn() }, headers: fullHeaders }); }); it("should send get request to default portal", async () => { @@ -105,8 +108,8 @@ describe("downloadFileHnsToPath", () => { }); it("should get info when headers are missing", async () => { - // @ts-ignore - axios.mockResolvedValue({ data: { body, pipe: function () {} }, headers: {} }); + // @ts-expect-error TS complaining. + axios.mockResolvedValue({ data: { body, pipe: jest.fn() }, headers: {} }); const tmpFile = tmp.fileSync(); diff --git a/src/download/node.ts b/src/download/node.ts index 1dbbf9fd..1f8a527d 100644 --- a/src/download/node.ts +++ b/src/download/node.ts @@ -10,6 +10,17 @@ import { GetMetadataResponse, } from "./index"; +/** + * Initiates a download of the content of the skylink to the given file. + * + * @param this - SkynetClient + * @param skylinkUrl - 46-character skylink, or a valid skylink URL. Can be followed by a path. Note that the skylink will not be encoded, so if your path might contain special characters, consider using `customOptions.path`. + * @param path - Path to create the local file at. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact. + * @returns - The metadata in JSON format. Each field will be empty if no metadata was found. + * @throws - Will throw if the skylinkUrl does not contain a skylink or if the path custom option is not a string. + */ export async function downloadFileToPath( this: SkynetClient, skylinkUrl: string, @@ -50,6 +61,17 @@ export async function downloadFileToPath( return { contentType, metadata, skylink }; } +/** + * Initiates a download of the content of the skylink to the given file. + * + * @param this - SkynetClient + * @param domain - Handshake domain. + * @param path - Path to create the local file at. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/"] - The relative URL path of the portal endpoint to contact. + * @returns - The metadata in JSON format. Each field will be empty if no metadata was found. + * @throws - Will throw if the skylinkUrl does not contain a skylink or if the path custom option is not a string. + */ export async function downloadFileHnsToPath( this: SkynetClient, domain: string, diff --git a/src/upload/index.ts b/src/upload/index.ts index 707011df..5e338a68 100644 --- a/src/upload/index.ts +++ b/src/upload/index.ts @@ -38,6 +38,17 @@ export const defaultUploadOptions = { customFilename: "", }; +/** + * Uploads a file to Skynet. + * + * @param this - SkynetClient + * @param fileContents - The file contents to upload. + * @param fileName - The desired name for the file. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The returned skyfile information including skylink, merkleroot and bitfield. + * @throws - Will throw if the request is successful but the upload response does not contain a complete response. + */ export async function uploadFileContent( this: SkynetClient, fileContents: string, @@ -64,6 +75,16 @@ export async function uploadFileContent( return { skylink, merkleroot, bitfield }; } +/** + * Makes a request to upload a file to Skynet. + * + * @param this - SkynetClient + * @param fileContents - The file contents to upload. + * @param fileName - The desired name for the file. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The upload response. + */ export async function uploadFileContentRequest( this: SkynetClient, fileContents: string, diff --git a/src/upload/node.test.ts b/src/upload/node.test.ts index 45b3b63d..0f50003f 100644 --- a/src/upload/node.test.ts +++ b/src/upload/node.test.ts @@ -18,7 +18,7 @@ describe("uploadFile", () => { const filename = "testdata/file1.txt"; beforeEach(() => { - // @ts-ignore + // @ts-expect-error TS complaining. axios.mockResolvedValue({ data }); }); @@ -136,7 +136,7 @@ describe("uploadDirectoryFromPath", () => { const filename = `${dirname}/${directory[0]}`; beforeEach(() => { - // @ts-ignore + // @ts-expect-error TS complaining. axios.mockResolvedValue({ data }); }); diff --git a/src/upload/node.ts b/src/upload/node.ts index cf0a56c5..bbe5ff34 100644 --- a/src/upload/node.ts +++ b/src/upload/node.ts @@ -11,6 +11,16 @@ import { SkynetClient } from "../client/node"; import { CustomUploadOptions, defaultUploadOptions, UploadRequestResponse } from "./index"; import { formatSkylink } from "../utils"; +/** + * Uploads a file from the given local path to Skynet. + * + * @param this - SkynetClient + * @param path - The path to the local file to upload. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The returned skyfile information including skylink, merkleroot and bitfield. + * @throws - Will throw if the request is successful but the upload response does not contain a complete response. + */ export async function uploadFileFromPath( this: SkynetClient, path: string, @@ -18,6 +28,17 @@ export async function uploadFileFromPath( ): Promise { const response = await this.uploadFileFromPathRequest(path, customOptions); + /* istanbul ignore next */ + if ( + typeof response.data.skylink !== "string" || + typeof response.data.merkleroot !== "string" || + typeof response.data.bitfield !== "number" + ) { + throw new Error( + "Did not get a complete upload response despite a successful request. Please try again and report this issue to the devs if it persists." + ); + } + const skylink = formatSkylink(response.data.skylink); const merkleroot = response.data.merkleroot; const bitfield = response.data.bitfield; @@ -25,6 +46,15 @@ export async function uploadFileFromPath( return { skylink, merkleroot, bitfield }; } +/** + * Makes a request upload a file from the given local path to Skynet. + * + * @param this - SkynetClient + * @param path - The path to the local file to upload. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The upload response. + */ export async function uploadFileFromPathRequest( this: SkynetClient, path: string, @@ -39,12 +69,22 @@ export async function uploadFileFromPathRequest( return this.executeRequest({ ...opts, method: "post", - // @ts-ignore + // @ts-expect-error TS doesn't recognize this external FormData. data: formData, headers: formData.getHeaders(), }); } +/** + * Uploads a directory from the given local path to Skynet. + * + * @param this - SkynetClient + * @param path - The path to the local directory to upload. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The returned skyfile information including skylink, merkleroot and bitfield. + * @throws - Will throw if the request is successful but the upload response does not contain a complete response, or if the directory is invalid. + */ export async function uploadDirectoryFromPath( this: SkynetClient, path: string, @@ -70,6 +110,16 @@ export async function uploadDirectoryFromPath( return { skylink, merkleroot, bitfield }; } +/** + * Makes a request upload a directory from the given local path to Skynet. + * + * @param this - SkynetClient + * @param path - The path to the local directory to upload. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The upload response. + * @throws - Will throw if the directory is invalid. + */ export async function uploadDirectoryFromPathRequest( this: SkynetClient, path: string, @@ -113,13 +163,19 @@ export async function uploadDirectoryFromPathRequest( return this.executeRequest({ ...opts, method: "post", - // @ts-ignore + // @ts-expect-error TS doesn't recognize this external FormData. data: formData, headers: formData.getHeaders(), query: { filename }, }); } +/** + * Returns the full recursive list of files inside a directory. + * + * @param filepath - The directory path. + * @returns - The full list of files. + */ function walkDirectory(filepath: string): Array { /* istanbul ignore next */ if (!fs.existsSync(filepath)) { diff --git a/src/upload/web.ts b/src/upload/web.ts index a804114f..6d50ea24 100644 --- a/src/upload/web.ts +++ b/src/upload/web.ts @@ -11,7 +11,7 @@ import { CustomUploadOptions, defaultUploadOptions, UploadRequestResponse } from * @param file - The file to upload. * @param [customOptions] - Additional settings that can optionally be set. * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. - * @returns - The returned skylink. + * @returns - The returned skyfile information including skylink, merkleroot and bitfield. * @throws - Will throw if the request is successful but the upload response does not contain a complete response. */ export async function uploadFile( From f9ac04c5cb38f760829ce8e6e43174d86cb489c2 Mon Sep 17 00:00:00 2001 From: Marcin S Date: Fri, 26 Feb 2021 14:51:44 +0100 Subject: [PATCH 07/13] Fix issues in node + move 'query' option from upload to base options --- package.json | 2 +- src/client/index.ts | 22 +++++++++++--- src/client/node.ts | 4 +++ src/client/web.ts | 11 ++++++- src/download/index.ts | 2 +- src/download/node.ts | 16 ++++++++-- src/download/web.ts | 2 +- src/registry.ts | 2 +- src/skydb.ts | 6 ++-- src/upload/index.ts | 71 +------------------------------------------ src/upload/node.ts | 67 ++++++++++++++++++++++++++++++++++++++++ src/upload/web.ts | 65 +++++++++++++++++++++++++++++++++++++++ src/utils.ts | 3 +- 13 files changed, 186 insertions(+), 87 deletions(-) diff --git a/package.json b/package.json index 10c7f89c..b2c8e44a 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "lint-staged": { "*.{js,jsx,ts,tsx}": [ "eslint --ext .ts --fix", - "tsc --esModuleInterop --noemit", + "tsc", "prettier --write" ], "*.{json,yml,md}": [ diff --git a/src/client/index.ts b/src/client/index.ts index 0735e14e..0ef87ae1 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -1,4 +1,4 @@ -import axios, { AxiosResponse } from "axios"; +import axios, { AxiosResponse, ResponseType } from "axios"; import type { Method } from "axios"; import { @@ -13,9 +13,9 @@ import { } from "../download/index"; import { getJSON, setJSON } from "../skydb"; import { getEntry, getEntryUrl, setEntry } from "../registry"; -import { uploadFileContent, uploadFileContentRequest } from "../upload/index"; import { defaultPortalUrl } from "../utils"; import { addUrlQuery, makeUrl } from "../utils"; +import { CustomUploadOptions, UploadRequestResponse } from "../upload"; /** * Custom client options. @@ -51,6 +51,7 @@ export type RequestConfig = CustomClientOptions & { skykeyName?: string; skykeyId?: string; headers?: Record; + responseType?: ResponseType; transformRequest?: (data: unknown) => string; transformResponse?: (data: string) => Record; }; @@ -58,7 +59,7 @@ export type RequestConfig = CustomClientOptions & { /** * The base Skynet Client which can be used to access Skynet. */ -export class SkynetClient { +export abstract class SkynetClient { portalUrl: string; customOptions: CustomClientOptions; @@ -75,8 +76,18 @@ export class SkynetClient { resolveHns = resolveHns; // Upload - uploadFileContent = uploadFileContent; - protected uploadFileContentRequest = uploadFileContentRequest; + abstract uploadFileContent( + this: SkynetClient, + fileContents: string, + fileName: string, + customOptions?: CustomUploadOptions + ): Promise; + protected abstract uploadFileContentRequest( + this: SkynetClient, + fileContents: string, + fileName: string, + customOptions?: CustomUploadOptions + ): Promise; // SkyDB db = { @@ -152,6 +163,7 @@ export class SkynetClient { headers, auth, onUploadProgress, + responseType: config.responseType, transformRequest: config.transformRequest, transformResponse: config.transformResponse, diff --git a/src/client/node.ts b/src/client/node.ts index b5e71377..3b2305e9 100644 --- a/src/client/node.ts +++ b/src/client/node.ts @@ -4,6 +4,8 @@ import { uploadDirectoryFromPathRequest, uploadFileFromPath, uploadFileFromPathRequest, + uploadFileContent, + uploadFileContentRequest, } from "../upload/node"; import { SkynetClient as Client } from "./index"; @@ -17,4 +19,6 @@ export class SkynetClient extends Client { protected uploadDirectoryFromPathRequest = uploadDirectoryFromPathRequest; uploadFileFromPath = uploadFileFromPath; protected uploadFileFromPathRequest = uploadFileFromPathRequest; + uploadFileContent = uploadFileContent; + protected uploadFileContentRequest = uploadFileContentRequest; } diff --git a/src/client/web.ts b/src/client/web.ts index 309bbc01..f45b28bd 100644 --- a/src/client/web.ts +++ b/src/client/web.ts @@ -1,5 +1,12 @@ import { downloadFile, downloadFileHns, openFile, openFileHns } from "../download/web"; -import { uploadFile, uploadDirectory, uploadDirectoryRequest, uploadFileRequest } from "../upload/web"; +import { + uploadFile, + uploadDirectory, + uploadDirectoryRequest, + uploadFileRequest, + uploadFileContent, + uploadFileContentRequest, +} from "../upload/web"; import { SkynetClient as Client } from "./index"; export class SkynetClient extends Client { @@ -14,4 +21,6 @@ export class SkynetClient extends Client { protected uploadFileRequest = uploadFileRequest; uploadDirectory = uploadDirectory; protected uploadDirectoryRequest = uploadDirectoryRequest; + uploadFileContent = uploadFileContent; + protected uploadFileContentRequest = uploadFileContentRequest; } diff --git a/src/download/index.ts b/src/download/index.ts index a96e2c75..c0b5dd6f 100644 --- a/src/download/index.ts +++ b/src/download/index.ts @@ -1,4 +1,4 @@ -import { SkynetClient } from "../client/index"; +import { SkynetClient } from "../client"; import { addSubdomain, addUrlQuery, diff --git a/src/download/node.ts b/src/download/node.ts index 1f8a527d..29d40a6d 100644 --- a/src/download/node.ts +++ b/src/download/node.ts @@ -1,6 +1,6 @@ import fs from "fs"; -import { SkynetClient } from "../client/index"; +import { SkynetClient } from "../client"; import { formatSkylink } from "../utils"; import { CustomDownloadOptions, @@ -37,6 +37,7 @@ export async function downloadFileToPath( ...opts, method: "get", url, + responseType: "stream", }); /* istanbul ignore next */ @@ -52,7 +53,11 @@ export async function downloadFileToPath( ); } - response.data.pipe(writer); + await new Promise((resolve, reject) => { + response.data.pipe(writer); + writer.on("finish", resolve); + writer.on("error", reject); + }); const contentType = response.headers["content-type"] ?? ""; const metadata = response.headers["skynet-file-metadata"] ? JSON.parse(response.headers["skynet-file-metadata"]) : {}; @@ -88,6 +93,7 @@ export async function downloadFileHnsToPath( ...opts, method: "get", url, + responseType: "stream", }); /* istanbul ignore next */ @@ -103,7 +109,11 @@ export async function downloadFileHnsToPath( ); } - response.data.pipe(writer); + await new Promise((resolve, reject) => { + response.data.pipe(writer); + writer.on("finish", resolve); + writer.on("error", reject); + }); const contentType = response.headers["content-type"] ?? ""; const metadata = response.headers["skynet-file-metadata"] ? JSON.parse(response.headers["skynet-file-metadata"]) : {}; diff --git a/src/download/web.ts b/src/download/web.ts index 3a2f96cd..bc22dfbf 100644 --- a/src/download/web.ts +++ b/src/download/web.ts @@ -1,4 +1,4 @@ -import { SkynetClient } from "../client/index"; +import { SkynetClient } from "../client"; import { CustomDownloadOptions, defaultDownloadOptions, diff --git a/src/registry.ts b/src/registry.ts index b2121e94..c91e5ac2 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -2,7 +2,7 @@ import { AxiosResponse } from "axios"; import { Buffer } from "buffer"; import { sign } from "tweetnacl"; -import { SkynetClient } from "./client/index"; +import { SkynetClient } from "./client"; import { addUrlQuery, BaseCustomOptions, diff --git a/src/skydb.ts b/src/skydb.ts index 2c397d51..e7e2eeaa 100644 --- a/src/skydb.ts +++ b/src/skydb.ts @@ -1,6 +1,6 @@ import { sign } from "tweetnacl"; -import { SkynetClient } from "./client/index"; +import { SkynetClient } from "./client"; import { CustomGetEntryOptions, RegistryEntry, SignedRegistryEntry, CustomSetEntryOptions } from "./registry"; import { trimUriPrefix, @@ -12,8 +12,8 @@ import { isHexString, hexToUint8Array, } from "./utils"; -import { CustomUploadOptions, UploadRequestResponse } from "./upload/index"; -import { CustomDownloadOptions } from "./download/index"; +import { CustomUploadOptions, UploadRequestResponse } from "./upload"; +import { CustomDownloadOptions } from "./download"; /** * Custom get JSON options. diff --git a/src/upload/index.ts b/src/upload/index.ts index 5e338a68..149690b4 100644 --- a/src/upload/index.ts +++ b/src/upload/index.ts @@ -1,7 +1,4 @@ -import { AxiosResponse } from "axios"; - -import { SkynetClient } from "../client/index"; -import { defaultOptions, BaseCustomOptions, formatSkylink } from "../utils"; +import { defaultOptions, BaseCustomOptions } from "../utils"; /** * Custom upload options. @@ -15,7 +12,6 @@ export type CustomUploadOptions = BaseCustomOptions & { portalFileFieldname?: string; portalDirectoryFileFieldname?: string; customFilename?: string; - query?: Record; }; /** @@ -37,68 +33,3 @@ export const defaultUploadOptions = { portalDirectoryFileFieldname: "files[]", customFilename: "", }; - -/** - * Uploads a file to Skynet. - * - * @param this - SkynetClient - * @param fileContents - The file contents to upload. - * @param fileName - The desired name for the file. - * @param [customOptions] - Additional settings that can optionally be set. - * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. - * @returns - The returned skyfile information including skylink, merkleroot and bitfield. - * @throws - Will throw if the request is successful but the upload response does not contain a complete response. - */ -export async function uploadFileContent( - this: SkynetClient, - fileContents: string, - fileName: string, - customOptions?: CustomUploadOptions -): Promise { - const response = await this.uploadFileContentRequest(fileContents, fileName, customOptions); - - /* istanbul ignore next */ - if ( - typeof response.data.skylink !== "string" || - typeof response.data.merkleroot !== "string" || - typeof response.data.bitfield !== "number" - ) { - throw new Error( - "Did not get a complete upload response despite a successful request. Please try again and report this issue to the devs if it persists." - ); - } - - const skylink = formatSkylink(response.data.skylink); - const merkleroot = response.data.merkleroot; - const bitfield = response.data.bitfield; - - return { skylink, merkleroot, bitfield }; -} - -/** - * Makes a request to upload a file to Skynet. - * - * @param this - SkynetClient - * @param fileContents - The file contents to upload. - * @param fileName - The desired name for the file. - * @param [customOptions] - Additional settings that can optionally be set. - * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. - * @returns - The upload response. - */ -export async function uploadFileContentRequest( - this: SkynetClient, - fileContents: string, - fileName: string, - customOptions?: CustomUploadOptions -): Promise { - const opts = { ...defaultUploadOptions, ...this.customOptions, ...customOptions }; - - const formData = new FormData(); - formData.append(opts.portalFileFieldname, new Blob([fileContents]), fileName); - - return this.executeRequest({ - ...opts, - method: "post", - data: formData, - }); -} diff --git a/src/upload/node.ts b/src/upload/node.ts index bbe5ff34..6c87d062 100644 --- a/src/upload/node.ts +++ b/src/upload/node.ts @@ -170,6 +170,73 @@ export async function uploadDirectoryFromPathRequest( }); } +/** + * Uploads a file to Skynet. + * + * @param this - SkynetClient + * @param fileContents - The file contents to upload. + * @param fileName - The desired name for the file. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The returned skyfile information including skylink, merkleroot and bitfield. + * @throws - Will throw if the request is successful but the upload response does not contain a complete response. + */ +export async function uploadFileContent( + this: SkynetClient, + fileContents: string, + fileName: string, + customOptions?: CustomUploadOptions +): Promise { + const response = await this.uploadFileContentRequest(fileContents, fileName, customOptions); + + /* istanbul ignore next */ + if ( + typeof response.data.skylink !== "string" || + typeof response.data.merkleroot !== "string" || + typeof response.data.bitfield !== "number" + ) { + throw new Error( + "Did not get a complete upload response despite a successful request. Please try again and report this issue to the devs if it persists." + ); + } + + const skylink = formatSkylink(response.data.skylink); + const merkleroot = response.data.merkleroot; + const bitfield = response.data.bitfield; + + return { skylink, merkleroot, bitfield }; +} + +/** + * Makes a request to upload a file to Skynet. + * + * @param this - SkynetClient + * @param fileContents - The file contents to upload. + * @param fileName - The desired name for the file. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The upload response. + */ +export async function uploadFileContentRequest( + this: SkynetClient, + fileContents: string, + fileName: string, + customOptions?: CustomUploadOptions +): Promise { + const opts = { ...defaultUploadOptions, ...this.customOptions, ...customOptions }; + + const formData = new FormData(); + formData.append(opts.portalFileFieldname, fileContents, fileName); + + return this.executeRequest({ + ...opts, + method: "post", + // @ts-expect-error TS doesn't recognize this external FormData. + data: formData, + headers: formData.getHeaders(), + }); +} + /** * Returns the full recursive list of files inside a directory. * diff --git a/src/upload/web.ts b/src/upload/web.ts index 6d50ea24..66133751 100644 --- a/src/upload/web.ts +++ b/src/upload/web.ts @@ -143,6 +143,71 @@ export async function uploadDirectoryRequest( }); } +/** + * Uploads a file to Skynet. + * + * @param this - SkynetClient + * @param fileContents - The file contents to upload. + * @param fileName - The desired name for the file. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The returned skyfile information including skylink, merkleroot and bitfield. + * @throws - Will throw if the request is successful but the upload response does not contain a complete response. + */ +export async function uploadFileContent( + this: SkynetClient, + fileContents: string, + fileName: string, + customOptions?: CustomUploadOptions +): Promise { + const response = await this.uploadFileContentRequest(fileContents, fileName, customOptions); + + /* istanbul ignore next */ + if ( + typeof response.data.skylink !== "string" || + typeof response.data.merkleroot !== "string" || + typeof response.data.bitfield !== "number" + ) { + throw new Error( + "Did not get a complete upload response despite a successful request. Please try again and report this issue to the devs if it persists." + ); + } + + const skylink = formatSkylink(response.data.skylink); + const merkleroot = response.data.merkleroot; + const bitfield = response.data.bitfield; + + return { skylink, merkleroot, bitfield }; +} + +/** + * Makes a request to upload a file to Skynet. + * + * @param this - SkynetClient + * @param fileContents - The file contents to upload. + * @param fileName - The desired name for the file. + * @param [customOptions] - Additional settings that can optionally be set. + * @param [customOptions.endpointPath="/skynet/skyfile"] - The relative URL path of the portal endpoint to contact. + * @returns - The upload response. + */ +export async function uploadFileContentRequest( + this: SkynetClient, + fileContents: string, + fileName: string, + customOptions?: CustomUploadOptions +): Promise { + const opts = { ...defaultUploadOptions, ...this.customOptions, ...customOptions }; + + const formData = new FormData(); + formData.append(opts.portalFileFieldname, new Blob([fileContents]), fileName); + + return this.executeRequest({ + ...opts, + method: "post", + data: formData, + }); +} + /** * Sometimes file object might have had the type property defined manually with * Object.defineProperty and some browsers (namely firefox) can have problems diff --git a/src/utils.ts b/src/utils.ts index 9fb3c1a5..0bd7d9ee 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,7 +6,7 @@ import parse from "url-parse"; import urljoin from "url-join"; import { Buffer } from "buffer"; -import { CustomClientOptions } from "./client/index"; +import { CustomClientOptions } from "./client"; /** * Base custom options for methods hitting the API. @@ -15,6 +15,7 @@ import { CustomClientOptions } from "./client/index"; */ export type BaseCustomOptions = CustomClientOptions & { endpointPath?: string; + query?: Record; }; /** From 918a4f06e4f79b79967d5966da7ccf5a511a53b2 Mon Sep 17 00:00:00 2001 From: Marcin S Date: Fri, 26 Feb 2021 16:48:57 +0100 Subject: [PATCH 08/13] [BREAKING] Change skylink prefix to sia:// --- src/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 0bd7d9ee..5ccbe278 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -39,7 +39,7 @@ export const defaultSkynetPortalUrl = "https://siasky.net"; export const uriHandshakePrefix = "hns:"; export const uriHandshakeResolverPrefix = "hnsres:"; -export const uriSkynetPrefix = "sia:"; +export const uriSkynetPrefix = "sia://"; /** * The maximum allowed value for an entry revision. Setting an entry revision to this value prevents it from being updated further. @@ -136,7 +136,7 @@ export function defaultPortalUrl(): string { } /** - * Formats the skylink by adding the sia: prefix. + * Formats the skylink by adding the sia:// prefix. * * @param skylink - The skylink. * @returns - The formatted skylink. From 943a4872c4505cd49f0aff3686b841b105680bb6 Mon Sep 17 00:00:00 2001 From: Marcin S Date: Fri, 26 Feb 2021 19:10:27 +0100 Subject: [PATCH 09/13] Don't bundle node and web targets, only for third "bundle" target --- babel.config.json | 3 ++- package.json | 7 +++++-- tsconfig.build.json | 2 +- webpack.config.js | 39 ++++++--------------------------------- 4 files changed, 14 insertions(+), 37 deletions(-) diff --git a/babel.config.json b/babel.config.json index 1063c48d..08c27522 100644 --- a/babel.config.json +++ b/babel.config.json @@ -14,5 +14,6 @@ ] ] } - } + }, + "ignore": ["src/**/*.test.ts"] } diff --git a/package.json b/package.json index b2c8e44a..24f16460 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Sia Skynet Javascript Client", "browser": "dist/web/index.web.js", "main": "dist/node/index.node.js", - "module": "src/index.web.js", + "module": "dist/web/index.web.js", "files": [ "dist/*" ], @@ -14,7 +14,10 @@ "not OperaMini all" ], "scripts": { - "build": "rimraf dist && yarn run webpack", + "build": "rimraf dist && yarn build-bundle && yarn build-node && yarn build-web", + "build-bundle": "yarn run webpack", + "build-node": "babel src --out-dir dist/node --extensions .ts && tsc --project tsconfig.build.json --outDir dist/node && rimraf dist/node/**/web* && rimraf dist/node/**/*web*", + "build-web": "babel src --out-dir dist/web --extensions .ts && tsc --project tsconfig.build.json --outDir dist/web && rimraf dist/web/**/node* && rimraf dist/web/**/*node*", "lint:eslint": "eslint --ext .ts src utils --max-warnings 0", "lint:tsc": "tsc", "prepublishOnly": "yarn build", diff --git a/tsconfig.build.json b/tsconfig.build.json index 6e459966..5b03407f 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -9,6 +9,6 @@ "types": ["node", "jest"], "typeRoots": ["./types", "./node_modules/@types"] }, - "include": ["types/**/*.d.ts"], + "include": ["src", "types/**/*.d.ts"], "exclude": ["src/**/*.test.ts"] } diff --git a/webpack.config.js b/webpack.config.js index 2ce7ff46..332e5f50 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,7 +1,9 @@ const path = require("path"); const { merge } = require("webpack-merge"); -var baseConfig = { +module.exports = { + entry: "./src/index.web.ts", + target: "web", mode: "production", module: { @@ -10,9 +12,6 @@ var baseConfig = { test: /\.tsx?$/, exclude: /(node_modules|bower_components)/, loader: "babel-loader", - options: { - ignore: ["src/**/*.test.ts"], - }, }, ], }, @@ -20,36 +19,10 @@ var baseConfig = { extensions: [".tsx", ".ts", ".js"], }, output: { + path: path.resolve(__dirname, "./dist/bundle"), + // The filename needs to match the index.web.d.ts declarations file. + filename: "index.js", library: "skynet", libraryTarget: "umd", }, }; - -let targets = ["web", "node"].map((target) => { - let base = merge(baseConfig, { - entry: "./src/index." + target + ".ts", - target: target, - module: { - rules: [ - { - test: /\.tsx?$/, - loader: "ts-loader", - exclude: /node_modules/, - options: { - configFile: "tsconfig.build.json", - compilerOptions: { - outDir: path.resolve(__dirname, "./dist/" + target), - }, - }, - }, - ], - }, - output: { - path: path.resolve(__dirname, "./dist/" + target), - filename: "index." + target + ".js", - }, - }); - return base; -}); - -module.exports = targets; From 56b1427f0e03765aaf558278b45aff61aa4f192b Mon Sep 17 00:00:00 2001 From: Marcin S Date: Tue, 2 Mar 2021 14:37:58 +0100 Subject: [PATCH 10/13] Fix tests --- babel.config.json | 3 +-- package.json | 16 ++++++------- src/download/node.test.ts | 50 ++++++++++++++++++--------------------- src/upload/node.test.ts | 2 ++ src/utils.test.ts | 2 +- webpack.config.js | 3 +++ yarn.lock | 10 ++++---- 7 files changed, 43 insertions(+), 43 deletions(-) diff --git a/babel.config.json b/babel.config.json index 08c27522..1063c48d 100644 --- a/babel.config.json +++ b/babel.config.json @@ -14,6 +14,5 @@ ] ] } - }, - "ignore": ["src/**/*.test.ts"] + } } diff --git a/package.json b/package.json index 24f16460..31520b52 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "scripts": { "build": "rimraf dist && yarn build-bundle && yarn build-node && yarn build-web", "build-bundle": "yarn run webpack", - "build-node": "babel src --out-dir dist/node --extensions .ts && tsc --project tsconfig.build.json --outDir dist/node && rimraf dist/node/**/web* && rimraf dist/node/**/*web*", - "build-web": "babel src --out-dir dist/web --extensions .ts && tsc --project tsconfig.build.json --outDir dist/web && rimraf dist/web/**/node* && rimraf dist/web/**/*node*", + "build-node": "babel src --out-dir dist/node --extensions .ts --ignore 'src/**/*.test.ts' && tsc --project tsconfig.build.json --outDir dist/node && rimraf dist/node/**/web* && rimraf dist/node/**/*web*", + "build-web": "babel src --out-dir dist/web --extensions .ts --ignore 'src/**/*.test.ts' && tsc --project tsconfig.build.json --outDir dist/web && rimraf dist/web/**/node* && rimraf dist/web/**/*node*", "lint:eslint": "eslint --ext .ts src utils --max-warnings 0", "lint:tsc": "tsc", "prepublishOnly": "yarn build", @@ -31,10 +31,10 @@ ], "coverageThreshold": { "global": { - "branches": 98, - "functions": 98, - "lines": 98, - "statements": 98 + "branches": 97, + "functions": 97, + "lines": 97, + "statements": 97 } }, "rootDir": "src" @@ -82,7 +82,7 @@ "sjcl": "^1.0.8", "tweetnacl": "^1.0.3", "url-join": "^4.0.1", - "url-parse": "^1.4.7" + "url-parse": "1.4.7" }, "devDependencies": { "@babel/cli": "^7.11.6", @@ -107,7 +107,7 @@ "eslint": "^7.11.0", "eslint-plugin-compat": "^3.8.0", "eslint-plugin-jsdoc": "^32.0.0", - "husky": "^5.0.9", + "husky": "^5.1.3", "jest": "^26.4.2", "lint-staged": "^10.3.0", "prettier": "^2.1.1", diff --git a/src/download/node.test.ts b/src/download/node.test.ts index d4375692..2ec2cb43 100644 --- a/src/download/node.test.ts +++ b/src/download/node.test.ts @@ -1,9 +1,11 @@ +import { PassThrough } from "stream"; import axios from "axios"; -import tmp from "tmp"; +import fs from "fs"; import { SkynetClient, defaultPortalUrl, uriSkynetPrefix } from "../index.node"; jest.mock("axios"); +jest.mock("fs"); const portalUrl = defaultPortalUrl(); const skylink = "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg"; @@ -18,17 +20,22 @@ const fullHeaders = { "skynet-file-metadata": JSON.stringify(skynetFileMetadata), }; const body = "asdf"; +const filename = "foo"; describe("downloadFileToPath", () => { beforeEach(() => { // @ts-expect-error TS complaining. axios.mockResolvedValue({ data: { body, pipe: jest.fn() }, headers: fullHeaders }); + const mockWriteable = new PassThrough(); + // @ts-expect-error TS complaining. + fs.createWriteStream.mockReturnValueOnce(mockWriteable); + setTimeout(() => { + mockWriteable.emit("finish"); + }, 100); }); - it("should send get request to default portal", () => { - const tmpFile = tmp.fileSync(); - - client.downloadFileToPath(skylink, tmpFile.name); + it("should send get request to default portal", async () => { + await client.downloadFileToPath(skylink, filename); expect(axios).toHaveBeenCalledWith( expect.objectContaining({ @@ -36,15 +43,12 @@ describe("downloadFileToPath", () => { method: "get", }) ); - - tmpFile.removeCallback(); }); it("should use custom connection options if defined on the client", async () => { - const tmpFile = tmp.fileSync(); const client = new SkynetClient("", { APIKey: "foobar", customUserAgent: "Sia-Agent" }); - const { contentType, metadata, skylink: skylink2 } = await client.downloadFileToPath(skylink, tmpFile.name, { + const { contentType, metadata, skylink: skylink2 } = await client.downloadFileToPath(skylink, filename, { APIKey: "barfoo", customUserAgent: "Sia-Agent-2", }); @@ -60,23 +64,17 @@ describe("downloadFileToPath", () => { headers: expect.objectContaining({ "User-Agent": "Sia-Agent-2" }), }) ); - - tmpFile.removeCallback(); }); it("should fetch info even when headers are missing", async () => { // @ts-expect-error TS complaining. axios.mockResolvedValue({ data: { body, pipe: jest.fn() }, headers: {} }); - const tmpFile = tmp.fileSync(); - - const { contentType, metadata, skylink: skylink2 } = await client.downloadFileToPath(skylink, tmpFile.name); + const { contentType, metadata, skylink: skylink2 } = await client.downloadFileToPath(skylink, filename); expect(contentType).toEqual(""); expect(metadata).toEqual({}); expect(skylink2).toEqual(""); - - tmpFile.removeCallback(); }); }); @@ -86,12 +84,16 @@ describe("downloadFileHnsToPath", () => { beforeEach(() => { // @ts-expect-error TS complaining. axios.mockResolvedValue({ data: { body, pipe: jest.fn() }, headers: fullHeaders }); + const mockWriteable = new PassThrough(); + // @ts-expect-error TS complaining. + fs.createWriteStream.mockReturnValueOnce(mockWriteable); + setTimeout(() => { + mockWriteable.emit("finish"); + }, 100); }); - it("should send get request to default portal", async () => { - const tmpFile = tmp.fileSync(); - - const { contentType, metadata, skylink: skylink2 } = await client.downloadFileHnsToPath(domain, tmpFile.name); + it("should send get request for HNS to default portal", async () => { + const { contentType, metadata, skylink: skylink2 } = await client.downloadFileHnsToPath(domain, filename); expect(contentType).toEqual(skynetfileContentType); expect(metadata).toEqual(skynetFileMetadata); @@ -103,22 +105,16 @@ describe("downloadFileHnsToPath", () => { method: "get", }) ); - - tmpFile.removeCallback(); }); it("should get info when headers are missing", async () => { // @ts-expect-error TS complaining. axios.mockResolvedValue({ data: { body, pipe: jest.fn() }, headers: {} }); - const tmpFile = tmp.fileSync(); - - const { contentType, metadata, skylink: skylink2 } = await client.downloadFileHnsToPath(domain, tmpFile.name); + const { contentType, metadata, skylink: skylink2 } = await client.downloadFileHnsToPath(domain, filename); expect(contentType).toEqual(""); expect(metadata).toEqual({}); expect(skylink2).toEqual(""); - - tmpFile.removeCallback(); }); }); diff --git a/src/upload/node.test.ts b/src/upload/node.test.ts index 0f50003f..83163b5f 100644 --- a/src/upload/node.test.ts +++ b/src/upload/node.test.ts @@ -121,6 +121,8 @@ describe("uploadFile", () => { const { skylink } = await client.uploadFileFromPath(file.name); expect(skylink).toEqual(sialink); + + file.removeCallback(); }); it("should return skylink on success", async () => { diff --git a/src/utils.test.ts b/src/utils.test.ts index 0bb077d6..f3b9fcef 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -81,7 +81,7 @@ describe("convertSkylinkToBase32", () => { describe("formatSkylink", () => { it("should ensure the skylink starts with the prefix", () => { - const prefixedSkylink = `sia:${skylink}`; + const prefixedSkylink = `sia://${skylink}`; expect(formatSkylink(skylink)).toEqual(prefixedSkylink); expect(formatSkylink(prefixedSkylink)).toEqual(prefixedSkylink); diff --git a/webpack.config.js b/webpack.config.js index 332e5f50..f015a73b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -12,6 +12,9 @@ module.exports = { test: /\.tsx?$/, exclude: /(node_modules|bower_components)/, loader: "babel-loader", + options: { + ignore: ["src/**/*.test.ts"], + }, }, ], }, diff --git a/yarn.lock b/yarn.lock index 60908a91..a7adc570 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3353,7 +3353,7 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -husky@^5.0.9: +husky@^5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/husky/-/husky-5.1.3.tgz#1a0645a4fe3ffc006c4d0d8bd0bcb4c98787cc9d" integrity sha512-fbNJ+Gz5wx2LIBtMweJNY1D7Uc8p1XERi5KNRMccwfQA+rXlxWNSdUxswo0gT8XqxywTIw7Ywm/F4v/O35RdMg== @@ -5953,10 +5953,10 @@ url-join@^4.0.1: resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== -url-parse@^1.4.7: - version "1.5.1" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b" - integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q== +url-parse@1.4.7: + version "1.4.7" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" + integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" From 02d828b75cacac46eaede47d54f4c6c5d1be7b8c Mon Sep 17 00:00:00 2001 From: Marcin S Date: Thu, 4 Mar 2021 10:46:27 +0100 Subject: [PATCH 11/13] Update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 39809afe..c179e83c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# skynet-js - Javascript Sia Skynet Client +# skynet-js - Javascript Sia Skynet SDK [![Version](https://img.shields.io/github/package-json/v/NebulousLabs/skynet-js)](https://www.npmjs.com/package/skynet-js) [![Build Status](https://img.shields.io/github/workflow/status/NebulousLabs/skynet-js/Node.js%20CI)](https://github.com/NebulousLabs/skynet-js/actions) [![Contributors](https://img.shields.io/github/contributors/NebulousLabs/skynet-js)](https://github.com/NebulousLabs/skynet-js/graphs/contributors) [![License](https://img.shields.io/github/license/NebulousLabs/skynet-js)](https://github.com/NebulousLabs/skynet-js) -A Javascript module made to simplify communication with Sia Skynet portals from the browser. +A Javascript module made to simplify communication with Sia Skynet portals from the browser. Now with support for Node.js! ## Updating to v3 from v2 From 8db8be8a3f16b970fc7ba88be7cd1051ba3d4fdd Mon Sep 17 00:00:00 2001 From: Marcin S Date: Sat, 27 Mar 2021 13:23:18 +0100 Subject: [PATCH 12/13] Make prefix sia:// again (lost in merge) --- src/utils/skylink.test.ts | 2 +- src/utils/skylink.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/skylink.test.ts b/src/utils/skylink.test.ts index 13c30fd0..b7f1c3ef 100644 --- a/src/utils/skylink.test.ts +++ b/src/utils/skylink.test.ts @@ -14,7 +14,7 @@ describe("convertSkylinkToBase32", () => { describe("formatSkylink", () => { it("should ensure the skylink starts with the prefix", () => { - const prefixedSkylink = `sia:${skylink}`; + const prefixedSkylink = `sia://${skylink}`; expect(formatSkylink(skylink)).toEqual(prefixedSkylink); expect(formatSkylink(prefixedSkylink)).toEqual(prefixedSkylink); diff --git a/src/utils/skylink.ts b/src/utils/skylink.ts index 02329cf2..dc37d68a 100644 --- a/src/utils/skylink.ts +++ b/src/utils/skylink.ts @@ -32,7 +32,7 @@ type ParseSkylinkBase32Options = { export const uriHandshakePrefix = "hns:"; export const uriHandshakeResolverPrefix = "hnsres:"; -export const uriSkynetPrefix = "sia:"; +export const uriSkynetPrefix = "sia://"; /** * Converts the given base64 skylink to base32. @@ -60,7 +60,7 @@ export function defaultOptions(endpointPath: string): CustomClientOptions & { en } /** - * Formats the skylink by adding the sia: prefix. + * Formats the skylink by adding the sia prefix. * * @param skylink - The skylink. * @returns - The formatted skylink. From 9152ff42a08e09cdcf8309c8eabbc0ff77860fde Mon Sep 17 00:00:00 2001 From: Marcin S Date: Sat, 27 Mar 2021 14:37:22 +0100 Subject: [PATCH 13/13] Refactor node download logic --- src/client/node.ts | 3 ++- src/download/node.ts | 23 ++++++++++++++++++++++- src/download/web.ts | 2 +- src/index.node.ts | 17 +++++------------ src/upload/index.ts | 3 +-- src/upload/node.ts | 2 +- src/utils/skylink.ts | 2 ++ 7 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/client/node.ts b/src/client/node.ts index 3b2305e9..1fd43c0c 100644 --- a/src/client/node.ts +++ b/src/client/node.ts @@ -1,4 +1,4 @@ -import { downloadFileHnsToPath, downloadFileToPath } from "../download/node"; +import { downloadFileHnsToPath, downloadFileToPath, downloadFileToPathRequest } from "../download/node"; import { uploadDirectoryFromPath, uploadDirectoryFromPathRequest, @@ -13,6 +13,7 @@ export class SkynetClient extends Client { // Download downloadFileToPath = downloadFileToPath; downloadFileHnsToPath = downloadFileHnsToPath; + protected downloadFileToPathRequest = downloadFileToPathRequest; // Upload uploadDirectoryFromPath = uploadDirectoryFromPath; diff --git a/src/download/node.ts b/src/download/node.ts index dc0ca959..51f3190f 100644 --- a/src/download/node.ts +++ b/src/download/node.ts @@ -1,6 +1,6 @@ import fs from "fs"; -import { SkynetClient } from "../client"; +import { SkynetClient } from "../client/node"; import { CustomDownloadOptions, defaultDownloadOptions, @@ -77,6 +77,27 @@ export async function downloadFileHnsToPath( const url = this.getHnsUrl(domain, opts); + return this.downloadFileToPathRequest(url, path, opts); +} + +/** + * Makes a request to download a file to a path from Skynet. + * + * @param this - SkynetClient + * @param url - URL. + * @param path - Path to create the local file at. + * @param [customOptions] - Additional settings that can optionally be set. + * @returns - The metadata in JSON format. Each field will be empty if no metadata was found. + * @throws - Will throw if the request does not succeed or the response is missing data. + */ +export async function downloadFileToPathRequest( + this: SkynetClient, + url: string, + path: string, + customOptions?: CustomDownloadOptions +): Promise { + const opts = { ...defaultDownloadHnsOptions, ...this.customOptions, ...customOptions }; + const writer = fs.createWriteStream(path); const response = await this.executeRequest({ diff --git a/src/download/web.ts b/src/download/web.ts index bc22dfbf..64211991 100644 --- a/src/download/web.ts +++ b/src/download/web.ts @@ -1,4 +1,4 @@ -import { SkynetClient } from "../client"; +import { SkynetClient } from "../client/web"; import { CustomDownloadOptions, defaultDownloadOptions, diff --git a/src/index.node.ts b/src/index.node.ts index 38b080e8..e52820fc 100644 --- a/src/index.node.ts +++ b/src/index.node.ts @@ -1,16 +1,9 @@ export { SkynetClient } from "./client/node"; export { deriveChildSeed, genKeyPairAndSeed, genKeyPairFromSeed } from "./crypto"; -export { - MAX_REVISION, - defaultPortalUrl, - defaultSkynetPortalUrl, - getRelativeFilePath, - getRootDirectory, - parseSkylink, - uriHandshakePrefix, - uriHandshakeResolverPrefix, - uriSkynetPrefix, -} from "./utils"; +export { parseSkylink, uriHandshakePrefix, uriHandshakeResolverPrefix, uriSkynetPrefix } from "./utils/skylink"; +export { MAX_REVISION } from "./utils/number"; +export { defaultPortalUrl, defaultSkynetPortalUrl } from "./utils/url"; +export { getRelativeFilePath, getRootDirectory } from "./utils/file"; // Export types. @@ -20,4 +13,4 @@ export type { CustomDownloadOptions, ResolveHnsResponse } from "./download"; export type { CustomGetEntryOptions, CustomSetEntryOptions, SignedRegistryEntry, RegistryEntry } from "./registry"; export type { CustomGetJSONOptions, CustomSetJSONOptions, VersionedEntryData } from "./skydb"; export type { CustomUploadOptions, UploadRequestResponse } from "./upload/index"; -export type { ParseSkylinkOptions } from "./utils"; +export type { ParseSkylinkOptions } from "./utils/skylink"; diff --git a/src/upload/index.ts b/src/upload/index.ts index 149690b4..e25c69a2 100644 --- a/src/upload/index.ts +++ b/src/upload/index.ts @@ -1,4 +1,4 @@ -import { defaultOptions, BaseCustomOptions } from "../utils"; +import { defaultOptions, BaseCustomOptions } from "../utils/skylink"; /** * Custom upload options. @@ -6,7 +6,6 @@ import { defaultOptions, BaseCustomOptions } from "../utils"; * @property [portalFileFieldname="file"] - The file fieldname for uploading files on this portal. * @property [portalDirectoryfilefieldname="files[]"] - The file fieldname for uploading directories on this portal. * @property [customFilename] - The custom filename to use when uploading files. - * @property [query] - Query parameters. */ export type CustomUploadOptions = BaseCustomOptions & { portalFileFieldname?: string; diff --git a/src/upload/node.ts b/src/upload/node.ts index 6c87d062..af5befcb 100644 --- a/src/upload/node.ts +++ b/src/upload/node.ts @@ -9,7 +9,7 @@ import p from "path"; import { SkynetClient } from "../client/node"; import { CustomUploadOptions, defaultUploadOptions, UploadRequestResponse } from "./index"; -import { formatSkylink } from "../utils"; +import { formatSkylink } from "../utils/skylink"; /** * Uploads a file from the given local path to Skynet. diff --git a/src/utils/skylink.ts b/src/utils/skylink.ts index dc37d68a..8a9d34d1 100644 --- a/src/utils/skylink.ts +++ b/src/utils/skylink.ts @@ -8,9 +8,11 @@ import { trimForwardSlash, trimSuffix, trimUriPrefix } from "./string"; * Base custom options for methods hitting the API. * * @property [endpointPath] - The relative URL path of the portal endpoint to contact. + * @property [query] - Query parameters. */ export type BaseCustomOptions = CustomClientOptions & { endpointPath?: string; + query?: Record; }; /**