(core) give preliminary support in core for storing snapshots in S3-compatible stores via minio-js client

Summary:
This is a first pass at snapshot support using the MinIO client, suitable
for use against a MinIO server or other S3-compatible storage (including
the original AWS S3).

In Grist Labs monorepo tests, it is run against AWS S3. It can be manually
configured to run again a MinIO server, and these tests pass. There are no
core tests just yet.

Next step would be to move external storage tests to core, and configure
workflow to run tests against a transient MinIO server.

Test Plan: applied same tests as for Azure and S3 (via AWS client)

Reviewers: georgegevoian

Reviewed By: georgegevoian

Differential Revision: https://phab.getgrist.com/D3729
pull/383/head
Paul Fitzpatrick 1 year ago
parent 34b8dfa740
commit e564d31582

@ -1,4 +1,4 @@
import { isAffirmative } from 'app/common/gutil';
import { isAffirmative, isNumber } from 'app/common/gutil';
/**
* A bundle of settings for the application. May contain
@ -23,6 +23,22 @@ export class AppSettings {
return (this._value !== undefined) ? isAffirmative(this._value) : undefined;
}
/**
* Access the setting as an integer using parseInt. Undefined if not set.
* Throws an error if not numberlike.
*/
public getAsInt(): number|undefined {
if (this._value === undefined) { return undefined; }
const datum = this._value?.valueOf();
if (typeof datum === 'number') {
return datum;
}
if (isNumber(String(datum))) {
return parseInt(String(datum), 10);
}
throw new Error(`${datum} does not look like a number`);
}
/**
* Try to read the setting from the environment. Even if
* we fail, we record information about how we tried to

@ -57,6 +57,9 @@ export function makeSimpleCreator(opts: {
storage?: ICreateStorageOptions[],
billing?: ICreateBillingOptions,
notifier?: ICreateNotifierOptions,
sandboxFlavor?: string,
shell?: IShell,
getExtraHeadHtml?: () => string,
}): ICreate {
const {sessionSecret, storage, notifier, billing} = opts;
return {
@ -84,7 +87,7 @@ export function makeSimpleCreator(opts: {
return undefined;
},
NSandbox(options) {
return createSandbox('unsandboxed', options);
return createSandbox(opts.sandboxFlavor || 'unsandboxed', options);
},
sessionSecret() {
const secret = process.env.GRIST_SESSION_SECRET || sessionSecret;
@ -98,7 +101,15 @@ export function makeSimpleCreator(opts: {
if (s.check()) { break; }
}
},
...(opts.shell && {
Shell() {
return opts.shell as IShell;
},
}),
getExtraHeadHtml() {
if (opts.getExtraHeadHtml) {
return opts.getExtraHeadHtml();
}
const elements: string[] = [];
if (process.env.APP_STATIC_INCLUDE_CUSTOM_CSS === 'true') {
elements.push('<link rel="stylesheet" href="custom.css">');

@ -0,0 +1,164 @@
import {ApiError} from 'app/common/ApiError';
import {ObjMetadata, ObjSnapshotWithMetadata, toExternalMetadata, toGristMetadata} from 'app/common/DocSnapshot';
import {ExternalStorage} from 'app/server/lib/ExternalStorage';
import {IncomingMessage} from 'http';
import * as fse from 'fs-extra';
import * as minio from 'minio';
// The minio typings appear to be quite stale. Extend them here to avoid
// lots of casts to any.
type MinIOClient = minio.Client & {
statObject(bucket: string, key: string, options: {versionId?: string}): Promise<MinIOBucketItemStat>;
getObject(bucket: string, key: string, options: {versionId?: string}): Promise<IncomingMessage>;
listObjects(bucket: string, key: string, recursive: boolean,
options: {IncludeVersion?: boolean}): minio.BucketStream<minio.BucketItem>;
removeObjects(bucket: string, versions: Array<{name: string, versionId: string}>): Promise<void>;
};
type MinIOBucketItemStat = minio.BucketItemStat & {
versionId?: string;
metaData?: Record<string, string>;
};
/**
* An external store implemented using the MinIO client, which
* will work with MinIO and other S3-compatible storage.
*/
export class MinIOExternalStorage implements ExternalStorage {
private _s3: MinIOClient;
// Specify bucket to use, and optionally the max number of keys to request
// in any call to listObjectVersions (used for testing)
constructor(public bucket: string, public options: {
endPoint: string,
port?: number,
useSSL?: boolean,
accessKey: string,
secretKey: string,
}, private _batchSize?: number) {
this._s3 = new minio.Client(options) as MinIOClient;
}
public async exists(key: string, snapshotId?: string) {
return Boolean(await this.head(key, snapshotId));
}
public async head(key: string, snapshotId?: string): Promise<ObjSnapshotWithMetadata|null> {
try {
const head = await this._s3.statObject(
this.bucket, key,
snapshotId ? {versionId: snapshotId} : {},
);
if (!head.lastModified || !head.versionId) {
// AWS documentation says these fields will be present.
throw new Error('MinIOExternalStorage.head did not get expected fields');
}
return {
lastModified: head.lastModified.toISOString(),
snapshotId: head.versionId,
...head.metaData && { metadata: toGristMetadata(head.metaData) },
};
} catch (err) {
if (!this.isFatalError(err)) { return null; }
throw err;
}
}
public async upload(key: string, fname: string, metadata?: ObjMetadata) {
const stream = fse.createReadStream(fname);
const result = await this._s3.putObject(
this.bucket, key, stream,
metadata ? {Metadata: toExternalMetadata(metadata)} : undefined
);
// Empirically VersionId is available in result for buckets with versioning enabled.
return result.versionId || null;
}
public async download(key: string, fname: string, snapshotId?: string) {
const stream = fse.createWriteStream(fname);
const request = await this._s3.getObject(
this.bucket, key,
snapshotId ? {versionId: snapshotId} : {}
);
const statusCode = request.statusCode || 500;
if (statusCode >= 300) {
throw new ApiError('download error', statusCode);
}
// See https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/requests-using-stream-objects.html
// for an example of streaming data.
const headers = request.headers;
// For a versioned bucket, the header 'x-amz-version-id' contains a version id.
const downloadedSnapshotId = String(headers['x-amz-version-id'] || '');
return new Promise<string>((resolve, reject) => {
request
.on('error', reject) // handle errors on the read stream
.pipe(stream)
.on('error', reject) // handle errors on the write stream
.on('finish', () => resolve(downloadedSnapshotId));
});
}
public async remove(key: string, snapshotIds?: string[]) {
if (snapshotIds) {
await this._deleteBatch(key, snapshotIds);
} else {
await this._deleteAllVersions(key);
}
}
public async versions(key: string, options?: { includeDeleteMarkers?: boolean }) {
const results: minio.BucketItem[] = [];
await new Promise((resolve, reject) => {
const stream = this._s3.listObjects(this.bucket, key, false, {IncludeVersion: true});
stream
.on('error', reject)
.on('end', () => {
resolve(results);
})
.on('data', data => {
results.push(data);
});
});
return results
.filter(v => v.name === key &&
v.lastModified && (v as any).versionId &&
(options?.includeDeleteMarkers || !(v as any).isDeleteMarker))
.map(v => ({
lastModified: v.lastModified.toISOString(),
snapshotId: (v as any).versionId!,
}));
}
public url(key: string) {
return `minio://${this.bucket}/${key}`;
}
public isFatalError(err: any) {
return err.code !== 'NotFound' && err.code !== 'NoSuchKey';
}
public async close() {
// nothing to do
}
// Delete all versions of an object.
public async _deleteAllVersions(key: string) {
const vs = await this.versions(key, {includeDeleteMarkers: true});
await this._deleteBatch(key, vs.map(v => v.snapshotId));
}
// Delete a batch of versions for an object.
private async _deleteBatch(key: string, versions: Array<string | undefined>) {
// Max number of keys per request for AWS S3 is 1000, see:
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html
// Stick to this maximum in case we are using this client to talk to AWS.
const N = this._batchSize || 1000;
for (let i = 0; i < versions.length; i += N) {
const iVersions = versions.slice(i, i + N).filter(v => v) as string[];
if (iVersions.length === 0) { continue; }
await this._s3.removeObjects(this.bucket, iVersions.map(versionId => {
return { name: key, versionId };
}));
}
}
}

@ -0,0 +1,54 @@
import {wrapWithKeyMappedStorage} from 'app/server/lib/ExternalStorage';
import {appSettings} from 'app/server/lib/AppSettings';
import {MinIOExternalStorage} from 'app/server/lib/MinIOExternalStorage';
export function configureMinIOExternalStorage(purpose: 'doc'|'meta', extraPrefix: string) {
const options = checkMinIOExternalStorage();
if (!options?.bucket) { return undefined; }
return wrapWithKeyMappedStorage(new MinIOExternalStorage(options.bucket, options), {
basePrefix: options.prefix,
extraPrefix,
purpose,
});
}
export function checkMinIOExternalStorage() {
const settings = appSettings.section('externalStorage').section('minio');
const bucket = settings.flag('bucket').readString({
envVar: ['GRIST_DOCS_MINIO_BUCKET', 'TEST_MINIO_BUCKET'],
preferredEnvVar: 'GRIST_DOCS_MINIO_BUCKET',
});
if (!bucket) { return undefined; }
const prefix = settings.flag('prefix').requireString({
envVar: ['GRIST_DOCS_MINIO_PREFIX'],
preferredEnvVar: 'GRIST_DOCS_MINIO_PREFIX',
defaultValue: 'docs/',
});
const endPoint = settings.flag('endpoint').requireString({
envVar: ['GRIST_DOCS_MINIO_ENDPOINT'],
preferredEnvVar: 'GRIST_DOCS_MINIO_ENDPOINT',
});
const port = settings.flag('port').read({
envVar: ['GRIST_DOCS_MINIO_PORT'],
preferredEnvVar: 'GRIST_DOCS_MINIO_PORT',
}).getAsInt();
const useSSL = settings.flag('useSsl').read({
envVar: ['GRIST_DOCS_MINIO_USE_SSL'],
}).getAsBool();
const accessKey = settings.flag('accessKey').requireString({
envVar: ['GRIST_DOCS_MINIO_ACCESS_KEY'],
});
const secretKey = settings.flag('secretKey').requireString({
envVar: ['GRIST_DOCS_MINIO_SECRET_KEY'],
});
settings.flag('url').set(`minio://${bucket}/${prefix}`);
settings.flag('active').set(true);
return {
endPoint,
port,
bucket, prefix,
useSSL,
accessKey,
secretKey,
};
}

@ -53,6 +53,7 @@
"@types/lodash": "4.14.117",
"@types/lru-cache": "5.1.1",
"@types/mime-types": "2.1.0",
"@types/minio": "7.0.15",
"@types/mocha": "5.2.5",
"@types/moment-timezone": "0.5.9",
"@types/node": "^14",
@ -142,6 +143,7 @@
"knockout": "3.5.0",
"locale-currency": "0.0.2",
"lodash": "4.17.21",
"minio": "7.0.32",
"moment": "2.29.4",
"moment-timezone": "0.5.35",
"morgan": "1.9.1",

@ -1,5 +1,15 @@
import { checkMinIOExternalStorage,
configureMinIOExternalStorage } from 'app/server/lib/configureMinIOExternalStorage';
import { makeSimpleCreator } from 'app/server/lib/ICreate';
export const create = makeSimpleCreator({
sessionSecret: 'Phoo2ag1jaiz6Moo2Iese2xoaphahbai3oNg7diemohlah0ohtae9iengafieS2Hae7quungoCi9iaPh'
// This can and should be overridden by GRIST_SESSION_SECRET
// (or generated randomly per install, like grist-omnibus does).
sessionSecret: 'Phoo2ag1jaiz6Moo2Iese2xoaphahbai3oNg7diemohlah0ohtae9iengafieS2Hae7quungoCi9iaPh',
storage: [
{
check: () => checkMinIOExternalStorage() !== undefined,
create: configureMinIOExternalStorage,
},
],
});

@ -469,6 +469,13 @@
resolved "https://registry.npmjs.org/@types/mime/-/mime-2.0.2.tgz"
integrity sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q==
"@types/minio@7.0.15":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/minio/-/minio-7.0.15.tgz#6fbf2e17aeae172cbf181ea52b1faa05a601ce42"
integrity sha512-1VR05lWJDuxkn/C7d87MPAJs0p+onKnkUN3nyQ0xrrtaziZQmONy/nxXRaAVWheEyIb6sl0TTi77I/GAQDN5Lw==
dependencies:
"@types/node" "*"
"@types/mocha@5.2.5":
version "5.2.5"
resolved "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.5.tgz"
@ -770,6 +777,11 @@
resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz"
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
"@zxing/text-encoding@0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b"
integrity sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==
JSONStream@^1.0.3:
version "1.3.5"
resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz"
@ -1123,6 +1135,11 @@ async@^2.1.5, async@^2.5.0:
dependencies:
lodash "^4.17.14"
async@^3.1.0:
version "3.2.4"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c"
integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==
async@^3.2.0:
version "3.2.0"
resolved "https://registry.npmjs.org/async/-/async-3.2.0.tgz"
@ -1138,6 +1155,11 @@ asynckit@^0.4.0:
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
available-typed-arrays@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz"
@ -1223,6 +1245,13 @@ bl@^4.0.3:
inherits "^2.0.4"
readable-stream "^3.4.0"
block-stream2@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/block-stream2/-/block-stream2-2.1.0.tgz#ac0c5ef4298b3857796e05be8ebed72196fa054b"
integrity sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==
dependencies:
readable-stream "^3.4.0"
bluebird@3.7.2, bluebird@^3.3.3, bluebird@^3.5.0:
version "3.7.2"
resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz"
@ -1325,6 +1354,11 @@ brorand@^1.0.1, brorand@^1.1.0:
resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz"
integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==
browser-or-node@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/browser-or-node/-/browser-or-node-1.3.0.tgz#f2a4e8568f60263050a6714b2cc236bb976647a7"
integrity sha512-0F2z/VSnLbmEeBcUrSuDH5l0HxTXdQQzLjkmBR4cYfvg1zJrKSlmIZFqyFR8oX0NrwPhy3c3HQ6i3OxMbew4Tg==
browser-pack@^6.0.1:
version "6.1.0"
resolved "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz"
@ -2094,7 +2128,7 @@ cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
crypto-browserify@^3.0.0:
crypto-browserify@^3.0.0, crypto-browserify@^3.12.0:
version "3.12.0"
resolved "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz"
integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==
@ -2241,6 +2275,11 @@ decimal.js@^10.2.1:
resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz"
integrity sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==
decode-uri-component@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
decompress-response@^3.3.0:
version "3.3.0"
resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz"
@ -2962,6 +3001,13 @@ fast-text-encoding@^1.0.0:
resolved "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz"
integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==
fast-xml-parser@^3.17.5:
version "3.21.1"
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.21.1.tgz#152a1d51d445380f7046b304672dd55d15c9e736"
integrity sha512-FTFVjYoBOZTJekiUsawGsSYV9QL0A+zDYCRj7y34IO6Jg+2IMYEtQa+bbictpdpV8dHxXywqU7C0gRDEOFtBFg==
dependencies:
strnum "^1.0.4"
fastest-levenshtein@^1.0.12:
version "1.0.12"
resolved "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz"
@ -2990,6 +3036,11 @@ fill-range@^7.0.1:
dependencies:
to-regex-range "^5.0.1"
filter-obj@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b"
integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==
finalhandler@1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz"
@ -3035,6 +3086,13 @@ follow-redirects@^1.14.0:
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz"
integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==
for-each@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==
dependencies:
is-callable "^1.1.3"
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz"
@ -3207,6 +3265,15 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1:
has "^1.0.3"
has-symbols "^1.0.1"
get-intrinsic@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385"
integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==
dependencies:
function-bind "^1.1.1"
has "^1.0.3"
has-symbols "^1.0.3"
get-stream@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz"
@ -3387,6 +3454,13 @@ googleapis-common@^5.0.1:
url-template "^2.0.8"
uuid "^8.0.0"
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
dependencies:
get-intrinsic "^1.1.3"
got@5.6.0:
version "5.6.0"
resolved "https://registry.npmjs.org/got/-/got-5.6.0.tgz"
@ -3513,6 +3587,18 @@ has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2:
resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz"
integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
has-symbols@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
has-tostringtag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
dependencies:
has-symbols "^1.0.2"
has-unicode@^2.0.0:
version "2.0.1"
resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz"
@ -3816,6 +3902,19 @@ ipaddr.js@1.9.1:
resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz"
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
ipaddr.js@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0"
integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==
is-arguments@^1.0.4:
version "1.1.1"
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==
dependencies:
call-bind "^1.0.2"
has-tostringtag "^1.0.0"
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz"
@ -3850,6 +3949,11 @@ is-buffer@~2.0.3:
resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz"
integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
is-callable@^1.1.3:
version "1.2.7"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
is-callable@^1.1.4, is-callable@^1.2.3:
version "1.2.3"
resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz"
@ -3903,6 +4007,13 @@ is-fullwidth-code-point@^3.0.0:
resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
is-generator-function@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72"
integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==
dependencies:
has-tostringtag "^1.0.0"
is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.1"
resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz"
@ -4024,6 +4135,17 @@ is-symbol@^1.0.2, is-symbol@^1.0.3:
dependencies:
has-symbols "^1.0.1"
is-typed-array@^1.1.10, is-typed-array@^1.1.3:
version "1.1.10"
resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f"
integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==
dependencies:
available-typed-arrays "^1.0.5"
call-bind "^1.0.2"
for-each "^0.3.3"
gopd "^1.0.1"
has-tostringtag "^1.0.0"
is-typedarray@^1.0.0, is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz"
@ -4191,6 +4313,11 @@ json-stable-stringify@~0.0.0:
dependencies:
jsonify "~0.0.0"
json-stream@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-stream/-/json-stream-1.0.0.tgz#1a3854e28d2bbeeab31cc7ddf683d2ddc5652708"
integrity sha512-H/ZGY0nIAg3QcOwE1QN/rK/Fa7gJn7Ii5obwp6zyPO4xiPNwpIMjqy2gwjBEGqzkF/vSWEIBQCBuN19hYiL6Qg==
json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
@ -4506,7 +4633,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
lodash@4.17.21, lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.7.0:
lodash@4.17.21, lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -4625,9 +4752,9 @@ mime-types@^2.1.12, mime-types@~2.1.19:
dependencies:
mime-db "1.46.0"
mime-types@^2.1.27:
mime-types@^2.1.14, mime-types@^2.1.27:
version "2.1.35"
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
@ -4701,6 +4828,29 @@ minimist@~1.1.1:
resolved "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz"
integrity sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=
minio@7.0.32:
version "7.0.32"
resolved "https://registry.yarnpkg.com/minio/-/minio-7.0.32.tgz#fed6a4679c5954d3efc6df47f73f7e7124446e2c"
integrity sha512-txa7Vr0N24MKzeAybP/wY1jxbLnfGHXwZYyfFXuMW55HX2+HOcKEIgH4hU6Qj/kiMgyXs/ozHjAuLIDrR8nwLg==
dependencies:
async "^3.1.0"
block-stream2 "^2.0.0"
browser-or-node "^1.3.0"
buffer-crc32 "^0.2.13"
crypto-browserify "^3.12.0"
es6-error "^4.1.1"
fast-xml-parser "^3.17.5"
ipaddr.js "^2.0.1"
json-stream "^1.0.0"
lodash "^4.17.21"
mime-types "^2.1.14"
mkdirp "^0.5.1"
query-string "^7.1.1"
through2 "^3.0.1"
web-encoding "^1.1.5"
xml "^1.0.0"
xml2js "^0.4.15"
minipass@^2.6.0, minipass@^2.9.0:
version "2.9.0"
resolved "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz"
@ -5689,6 +5839,16 @@ qs@^6.7.0:
dependencies:
side-channel "^1.0.4"
query-string@^7.1.1:
version "7.1.3"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328"
integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==
dependencies:
decode-uri-component "^0.2.2"
filter-obj "^1.1.0"
split-on-first "^1.0.0"
strict-uri-encode "^2.0.0"
querystring-es3@~0.2.0:
version "0.2.1"
resolved "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz"
@ -5769,6 +5929,15 @@ read-only-stream@^2.0.0:
dependencies:
readable-stream "^2.0.2"
"readable-stream@2 || 3", readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
version "3.6.0"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.6, readable-stream@~2.3.6:
version "2.3.7"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz"
@ -5782,15 +5951,6 @@ readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
version "3.6.0"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
readable-stream@~2.0.0:
version "2.0.6"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz"
@ -6354,6 +6514,11 @@ source-map@~0.5.3:
resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
split-on-first@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f"
integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==
split2@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz"
@ -6456,6 +6621,11 @@ stream-transform@^1.0.7:
resolved "https://registry.npmjs.org/stream-transform/-/stream-transform-1.0.8.tgz"
integrity sha512-1q+dL790Ps0NV33rISMq9OLtfDA9KMJZdo1PHZXE85orrWsM4FAh8CVyAOTHO0rhyeM138KNPngBPrx33bFsxw==
strict-uri-encode@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==
string-width@^1.0.1:
version "1.0.2"
resolved "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz"
@ -6591,6 +6761,11 @@ strip-json-comments@2.0.1, strip-json-comments@~2.0.1:
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
strnum@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db"
integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==
strtok3@^6.2.4:
version "6.3.0"
resolved "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz"
@ -6767,6 +6942,14 @@ through2@^2.0.0:
readable-stream "~2.3.6"
xtend "~4.0.1"
through2@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.2.tgz#99f88931cfc761ec7678b41d5d7336b5b6a07bf4"
integrity sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==
dependencies:
inherits "^2.0.4"
readable-stream "2 || 3"
"through@>=2.2.7 <3", through@~2.3.4:
version "2.3.8"
resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
@ -7186,6 +7369,17 @@ util@0.10.3:
dependencies:
inherits "2.0.1"
util@^0.12.3:
version "0.12.5"
resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc"
integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==
dependencies:
inherits "^2.0.3"
is-arguments "^1.0.4"
is-generator-function "^1.0.7"
is-typed-array "^1.1.3"
which-typed-array "^1.1.2"
util@~0.10.1:
version "0.10.4"
resolved "https://registry.npmjs.org/util/-/util-0.10.4.tgz"
@ -7256,6 +7450,15 @@ watchpack@^2.3.1:
glob-to-regexp "^0.4.1"
graceful-fs "^4.1.2"
web-encoding@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/web-encoding/-/web-encoding-1.1.5.tgz#fc810cf7667364a6335c939913f5051d3e0c4864"
integrity sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==
dependencies:
util "^0.12.3"
optionalDependencies:
"@zxing/text-encoding" "0.9.0"
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
@ -7385,6 +7588,18 @@ which-module@^2.0.0:
resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
which-typed-array@^1.1.2:
version "1.1.9"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6"
integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==
dependencies:
available-typed-arrays "^1.0.5"
call-bind "^1.0.2"
for-each "^0.3.3"
gopd "^1.0.1"
has-tostringtag "^1.0.0"
is-typed-array "^1.1.10"
which@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz"
@ -7535,14 +7750,19 @@ xml-name-validator@^3.0.0:
resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz"
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
xml2js@^0.4.0, xml2js@^0.4.17, xml2js@^0.4.23:
xml2js@^0.4.0, xml2js@^0.4.15, xml2js@^0.4.17, xml2js@^0.4.23:
version "0.4.23"
resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
dependencies:
sax ">=0.6.0"
xmlbuilder "~11.0.0"
xml@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==
xmlbuilder@~11.0.0:
version "11.0.1"
resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz"

Loading…
Cancel
Save