[chore] upgrade wreck (#36527)

wreck has been deprecated and will receive no further security updates
in favor of @hapi/wreck
This commit is contained in:
Todd Kennedy 2019-05-13 15:36:22 -07:00 committed by GitHub
parent e2222ce7c3
commit 5f56c30ffd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 209 additions and 161 deletions

View file

@ -110,6 +110,7 @@
"@elastic/good": "8.1.1-kibana2",
"@elastic/numeral": "2.3.3",
"@elastic/ui-ace": "0.2.3",
"@hapi/wreck": "^15.0.1",
"@kbn/babel-code-parser": "1.0.0",
"@kbn/babel-preset": "1.0.0",
"@kbn/config-schema": "1.0.0",
@ -254,7 +255,6 @@
"webpack": "4.23.1",
"webpack-merge": "4.1.4",
"whatwg-fetch": "^3.0.0",
"wreck": "^14.0.2",
"yauzl": "2.7.0"
},
"devDependencies": {

View file

@ -17,7 +17,7 @@
* under the License.
*/
import Wreck from 'wreck';
import Wreck from '@hapi/wreck';
import Progress from '../progress';
import { createWriteStream } from 'fs';
import HttpProxyAgent from 'http-proxy-agent';

View file

@ -24,7 +24,7 @@ import { readFileSync } from 'fs';
import del from 'del';
import sinon from 'sinon';
import expect from '@kbn/expect';
import Wreck from 'wreck';
import Wreck from '@hapi/wreck';
import { ToolingLog } from '@kbn/dev-utils';
import { download } from '../download';
@ -45,12 +45,12 @@ describe('src/dev/build/tasks/nodejs/download', () => {
const log = new ToolingLog({
level: 'verbose',
writeTo: {
write: onLogLine
}
write: onLogLine,
},
});
const FOO_SHA256 = '2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae';
const createSendHandler = (send) => (req, res) => {
const createSendHandler = send => (req, res) => {
res.statusCode = 200;
res.end(send);
};
@ -62,7 +62,7 @@ describe('src/dev/build/tasks/nodejs/download', () => {
let server;
let serverUrl;
let nextHandler;
afterEach(() => nextHandler = null);
afterEach(() => (nextHandler = null));
before(async () => {
server = createServer((req, res) => {
@ -79,9 +79,9 @@ describe('src/dev/build/tasks/nodejs/download', () => {
new Promise((resolve, reject) => {
server.once('error', reject);
}),
new Promise((resolve) => {
new Promise(resolve => {
server.listen(resolve);
})
}),
]);
serverUrl = `http://localhost:${server.address().port}/`;
@ -98,7 +98,7 @@ describe('src/dev/build/tasks/nodejs/download', () => {
log,
url: serverUrl,
destination: TMP_DESTINATION,
sha256: FOO_SHA256
sha256: FOO_SHA256,
});
expect(readFileSync(TMP_DESTINATION, 'utf8')).to.be('foo');
});
@ -111,11 +111,13 @@ describe('src/dev/build/tasks/nodejs/download', () => {
log,
url: serverUrl,
destination: TMP_DESTINATION,
sha256: 'bar'
sha256: 'bar',
});
throw new Error('Expected download() to reject');
} catch (error) {
expect(error).to.have.property('message').contain('does not match the expected sha256 checksum');
expect(error)
.to.have.property('message')
.contain('does not match the expected sha256 checksum');
}
try {
@ -192,7 +194,9 @@ describe('src/dev/build/tasks/nodejs/download', () => {
});
throw new Error('Expected download() to reject');
} catch (error) {
expect(error).to.have.property('message').contain('Unexpected status code 500');
expect(error)
.to.have.property('message')
.contain('Unexpected status code 500');
expect(reqCount).to.be(6);
}
});
@ -211,12 +215,14 @@ describe('src/dev/build/tasks/nodejs/download', () => {
await download({
log,
url: 'http://google.com',
destination: TMP_DESTINATION
destination: TMP_DESTINATION,
});
throw new Error('expected download() to reject');
} catch (error) {
expect(error).to.have.property('message').contain('refusing to download');
expect(error)
.to.have.property('message')
.contain('refusing to download');
}
});
});

View file

@ -22,7 +22,7 @@ import { dirname } from 'path';
import chalk from 'chalk';
import { createHash } from 'crypto';
import wreck from 'wreck';
import wreck from '@hapi/wreck';
import mkdirp from 'mkdirp';
function tryUnlink(path) {
@ -39,9 +39,7 @@ export async function download(options) {
const { log, url, destination, sha256, retries = 0 } = options;
if (!sha256) {
throw new Error(
`sha256 checksum of ${url} not provided, refusing to download.`
);
throw new Error(`sha256 checksum of ${url} not provided, refusing to download.`);
}
// mkdirp and open file outside of try/catch, we don't retry for those errors
@ -55,9 +53,7 @@ export async function download(options) {
const response = await wreck.request('GET', url);
if (response.statusCode !== 200) {
throw new Error(
`Unexpected status code ${response.statusCode} when downloading ${url}`
);
throw new Error(`Unexpected status code ${response.statusCode} when downloading ${url}`);
}
const hash = createHash('sha256');

View file

@ -17,7 +17,7 @@
* under the License.
*/
import wreck from 'wreck';
import wreck from '@hapi/wreck';
export async function getNodeShasums(nodeVersion) {
const url = `https://nodejs.org/dist/v${nodeVersion}/SHASUMS256.txt`;

View file

@ -20,7 +20,7 @@
import { scanCopy, untar, deleteAll } from '../lib';
import { createWriteStream } from 'fs';
import { binaryInfo } from '../../../../x-pack/plugins/code/tasks/nodegit_info';
import wreck from 'wreck';
import wreck from '@hapi/wreck';
import mkdirp from 'mkdirp';
import { dirname, join, basename } from 'path';
import { createPromiseFromStreams } from '../../../legacy/utils/streams';
@ -29,15 +29,10 @@ async function download(url, destination, log) {
const response = await wreck.request('GET', url);
if (response.statusCode !== 200) {
throw new Error(
`Unexpected status code ${response.statusCode} when downloading ${url}`
);
throw new Error(`Unexpected status code ${response.statusCode} when downloading ${url}`);
}
mkdirp.sync(dirname(destination));
await createPromiseFromStreams([
response,
createWriteStream(destination)
]);
await createPromiseFromStreams([response, createWriteStream(destination)]);
log.debug('Downloaded ', url);
}
@ -46,7 +41,7 @@ async function downloadAndExtractTarball(url, dest, log, retry) {
await download(url, dest, log);
const extractDir = join(dirname(dest), basename(dest, '.tar.gz'));
await untar(dest, extractDir, {
strip: 1
strip: 1,
});
return extractDir;
} catch (e) {
@ -66,7 +61,10 @@ async function patchNodeGit(config, log, build, platform) {
const downloadPath = build.resolvePathForPlatform(platform, '.nodegit_binaries', packageName);
const extractDir = await downloadAndExtractTarball(downloadUrl, downloadPath, log, 3);
const destination = build.resolvePathForPlatform(platform, 'node_modules/@elastic/nodegit/build/Release');
const destination = build.resolvePathForPlatform(
platform,
'node_modules/@elastic/nodegit/build/Release'
);
log.debug('Replacing nodegit binaries from ', extractDir);
await deleteAll([destination], log);
await scanCopy({
@ -77,15 +75,15 @@ async function patchNodeGit(config, log, build, platform) {
await deleteAll([extractDir, downloadPath], log);
}
export const PatchNativeModulesTask = {
description: 'Patching platform-specific native modules directories',
async run(config, log, build) {
await Promise.all(config.getTargetPlatforms().map(async platform => {
if (!build.isOss()) {
await patchNodeGit(config, log, build, platform);
}
}));
}
await Promise.all(
config.getTargetPlatforms().map(async platform => {
if (!build.isOss()) {
await patchNodeGit(config, log, build, platform);
}
})
);
},
};

View file

@ -1,28 +1,28 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import _ from 'lodash';
import fs from 'fs';
import path from 'path';
import { promisify } from 'util';
import { toArray } from 'rxjs/operators';
import wreck from 'wreck';
import wreck from '@hapi/wreck';
import { deleteIndex } from './delete_index';
import { collectUiExports } from '../../../legacy/ui/ui_exports';
@ -33,10 +33,10 @@ import { findPluginSpecs } from '../../../legacy/plugin_discovery';
* Load the uiExports for a Kibana instance, only load uiExports from xpack if
* it is enabled in the Kibana server.
*/
const getUiExports = async (kibanaUrl) => {
const getUiExports = async kibanaUrl => {
const xpackEnabled = await getKibanaPluginEnabled({
kibanaUrl,
pluginId: 'xpack_main'
pluginId: 'xpack_main',
});
const { spec$ } = await findPluginSpecs({
@ -98,7 +98,7 @@ export async function migrateKibanaIndex({ client, log, kibanaUrl }) {
const server = {
log: ([logType, messageType], ...args) => log[logType](`[${messageType}] ${args.join(' ')}`),
config: () => ({ get: (path) => config[path] }),
config: () => ({ get: path => config[path] }),
plugins: { elasticsearch },
};
@ -121,7 +121,7 @@ async function loadElasticVersion() {
export async function isSpacesEnabled({ kibanaUrl }) {
return await getKibanaPluginEnabled({
kibanaUrl,
pluginId: 'spaces'
pluginId: 'spaces',
});
}
@ -129,13 +129,14 @@ async function getKibanaPluginEnabled({ pluginId, kibanaUrl }) {
try {
const { payload } = await wreck.get('/api/status', {
baseUrl: kibanaUrl,
json: true
json: true,
});
return payload.status.statuses
.some(({ id }) => id.includes(`plugin:${pluginId}@`));
return payload.status.statuses.some(({ id }) => id.includes(`plugin:${pluginId}@`));
} catch (error) {
throw new Error(`Unable to fetch Kibana status API response from Kibana at ${kibanaUrl}: ${error}`);
throw new Error(
`Unable to fetch Kibana status API response from Kibana at ${kibanaUrl}: ${error}`
);
}
}
@ -151,9 +152,9 @@ export async function createDefaultSpace({ index, client }) {
name: 'Default Space',
description: 'This is the default space',
disabledFeatures: [],
_reserved: true
}
}
_reserved: true,
},
},
});
}
@ -167,12 +168,12 @@ export async function createDefaultSpace({ index, client }) {
*/
async function fetchKibanaIndices(client) {
const kibanaIndices = await client.cat.indices({ index: '.kibana*', format: 'json' });
const isKibanaIndex = (index) => (/^\.kibana(:?_\d*)?$/).test(index);
const isKibanaIndex = index => /^\.kibana(:?_\d*)?$/.test(index);
return kibanaIndices.map(x => x.index).filter(isKibanaIndex);
}
export async function cleanKibanaIndices({ client, stats, log, kibanaUrl }) {
if (!await isSpacesEnabled({ kibanaUrl })) {
if (!(await isSpacesEnabled({ kibanaUrl }))) {
return await deleteKibanaIndices({
client,
stats,
@ -188,17 +189,17 @@ export async function cleanKibanaIndices({ client, stats, log, kibanaUrl }) {
must_not: {
ids: {
type: '_doc',
values: ['space:default']
}
}
}
}
}
values: ['space:default'],
},
},
},
},
},
});
log.warning(
`since spaces are enabled, all objects other than the default space were deleted from ` +
`.kibana rather than deleting the whole index`
`.kibana rather than deleting the whole index`
);
stats.deletedIndex('.kibana');

View file

@ -18,7 +18,7 @@
*/
import sinon from 'sinon';
import Wreck from 'wreck';
import Wreck from '@hapi/wreck';
import expect from '@kbn/expect';
import { Server } from 'hapi';
@ -36,9 +36,11 @@ describe('Console Proxy Route', () => {
sandbox.stub(Wreck, 'request').callsFake(createWreckResponseStub(response));
const server = new Server();
server.route(createProxyRoute({
baseUrl: 'http://localhost:9200'
}));
server.route(
createProxyRoute({
baseUrl: 'http://localhost:9200',
})
);
teardowns.push(() => server.stop());

View file

@ -20,7 +20,7 @@
import { request } from 'http';
import sinon from 'sinon';
import Wreck from 'wreck';
import Wreck from '@hapi/wreck';
import expect from '@kbn/expect';
import { Server } from 'hapi';
@ -38,9 +38,11 @@ describe('Console Proxy Route', () => {
setup = () => {
const server = new Server();
server.route(createProxyRoute({
baseUrl: 'http://localhost:9200'
}));
server.route(
createProxyRoute({
baseUrl: 'http://localhost:9200',
})
);
teardowns.push(() => server.stop());
@ -61,23 +63,34 @@ describe('Console Proxy Route', () => {
await server.start();
const resp = await new Promise(resolve => {
request({
protocol: server.info.protocol + ':',
host: server.info.address,
port: server.info.port,
method: 'POST',
path: '/api/console/proxy?method=GET&path=/'
}, resolve).end();
request(
{
protocol: server.info.protocol + ':',
host: server.info.address,
port: server.info.port,
method: 'POST',
path: '/api/console/proxy?method=GET&path=/',
},
resolve
).end();
});
resp.destroy();
sinon.assert.calledOnce(Wreck.request);
const { headers } = Wreck.request.getCall(0).args[2];
expect(headers).to.have.property('x-forwarded-for').and.not.be('');
expect(headers).to.have.property('x-forwarded-port').and.not.be('');
expect(headers).to.have.property('x-forwarded-proto').and.not.be('');
expect(headers).to.have.property('x-forwarded-host').and.not.be('');
expect(headers)
.to.have.property('x-forwarded-for')
.and.not.be('');
expect(headers)
.to.have.property('x-forwarded-port')
.and.not.be('');
expect(headers)
.to.have.property('x-forwarded-proto')
.and.not.be('');
expect(headers)
.to.have.property('x-forwarded-host')
.and.not.be('');
});
});
});

View file

@ -20,7 +20,7 @@
import { Agent } from 'http';
import sinon from 'sinon';
import Wreck from 'wreck';
import Wreck from '@hapi/wreck';
import expect from '@kbn/expect';
import { Server } from 'hapi';
@ -53,12 +53,11 @@ describe('Console Proxy Route', () => {
describe('no matches', () => {
it('rejects with 403', async () => {
const { server } = setup();
server.route(createProxyRoute({
pathFilters: [
/^\/foo\//,
/^\/bar\//,
]
}));
server.route(
createProxyRoute({
pathFilters: [/^\/foo\//, /^\/bar\//],
})
);
const { statusCode } = await server.inject({
method: 'POST',
@ -71,12 +70,11 @@ describe('Console Proxy Route', () => {
describe('one match', () => {
it('allows the request', async () => {
const { server } = setup();
server.route(createProxyRoute({
pathFilters: [
/^\/foo\//,
/^\/bar\//,
]
}));
server.route(
createProxyRoute({
pathFilters: [/^\/foo\//, /^\/bar\//],
})
);
const { statusCode } = await server.inject({
method: 'POST',
@ -90,12 +88,11 @@ describe('Console Proxy Route', () => {
describe('all match', () => {
it('allows the request', async () => {
const { server } = setup();
server.route(createProxyRoute({
pathFilters: [
/^\/foo\//,
/^\/bar\//,
]
}));
server.route(
createProxyRoute({
pathFilters: [/^\/foo\//, /^\/bar\//],
})
);
const { statusCode } = await server.inject({
method: 'POST',
@ -124,7 +121,9 @@ describe('Console Proxy Route', () => {
const args = getConfigForReq.getCall(0).args;
expect(args[0]).to.have.property('path', '/api/console/proxy');
expect(args[0]).to.have.property('method', 'post');
expect(args[0]).to.have.property('query').eql({ method: 'HEAD', path: '/index/type/id' });
expect(args[0])
.to.have.property('query')
.eql({ method: 'HEAD', path: '/index/type/id' });
expect(args[1]).to.be('/index/type/id?pretty');
});
@ -136,17 +135,19 @@ describe('Console Proxy Route', () => {
const rejectUnauthorized = !!Math.round(Math.random());
const headers = {
foo: 'bar',
baz: 'bop'
baz: 'bop',
};
server.route(createProxyRoute({
getConfigForReq: () => ({
timeout,
agent,
rejectUnauthorized,
headers
server.route(
createProxyRoute({
getConfigForReq: () => ({
timeout,
agent,
rejectUnauthorized,
headers,
}),
})
}));
);
await server.inject({
method: 'POST',

View file

@ -18,7 +18,7 @@
*/
import sinon from 'sinon';
import Wreck from 'wreck';
import Wreck from '@hapi/wreck';
import expect from '@kbn/expect';
import { Server } from 'hapi';
@ -36,9 +36,11 @@ describe('Console Proxy Route', () => {
request = async (method, path) => {
const server = new Server();
server.route(createProxyRoute({
baseUrl: 'http://localhost:9200'
}));
server.route(
createProxyRoute({
baseUrl: 'http://localhost:9200',
})
);
teardowns.push(() => server.stop());
@ -85,14 +87,18 @@ describe('Console Proxy Route', () => {
it('combines well with the base url', async () => {
await request('GET', '/index/type/id');
sinon.assert.calledOnce(Wreck.request);
expect(Wreck.request.getCall(0).args[1]).to.be('http://localhost:9200/index/type/id?pretty');
expect(Wreck.request.getCall(0).args[1]).to.be(
'http://localhost:9200/index/type/id?pretty'
);
});
});
describe(`doesn't start with a slash`, () => {
it('combines well with the base url', async () => {
await request('GET', 'index/type/id');
sinon.assert.calledOnce(Wreck.request);
expect(Wreck.request.getCall(0).args[1]).to.be('http://localhost:9200/index/type/id?pretty');
expect(Wreck.request.getCall(0).args[1]).to.be(
'http://localhost:9200/index/type/id?pretty'
);
});
});
});

View file

@ -19,7 +19,7 @@
import Joi from 'joi';
import Boom from 'boom';
import Wreck from 'wreck';
import Wreck from '@hapi/wreck';
import { trimLeft, trimRight } from 'lodash';
function resolveUri(base, path) {
@ -70,17 +70,19 @@ export const createProxyRoute = ({
tags: ['access:console'],
payload: {
output: 'stream',
parse: false
parse: false,
},
validate: {
query: Joi.object().keys({
method: Joi.string()
.valid('HEAD', 'GET', 'POST', 'PUT', 'DELETE')
.insensitive()
.required(),
path: Joi.string().required()
}).unknown(true),
query: Joi.object()
.keys({
method: Joi.string()
.valid('HEAD', 'GET', 'POST', 'PUT', 'DELETE')
.insensitive()
.required(),
path: Joi.string().required(),
})
.unknown(true),
},
pre: [
@ -103,13 +105,8 @@ export const createProxyRoute = ({
const { path, method } = query;
const uri = resolveUri(baseUrl, path);
const {
timeout,
rejectUnauthorized,
agent,
headers,
} = getConfigForReq(req, uri);
const makeRequest = async (payloadToSend) => {
const { timeout, rejectUnauthorized, agent, headers } = getConfigForReq(req, uri);
const makeRequest = async payloadToSend => {
const wreckOptions = {
payload: payloadToSend,
timeout,
@ -117,19 +114,21 @@ export const createProxyRoute = ({
agent,
headers: {
...headers,
...getProxyHeaders(req)
...getProxyHeaders(req),
},
};
const esResponse = await Wreck.request(method, uri, wreckOptions);
if (method.toUpperCase() !== 'HEAD') {
return h.response(esResponse)
return h
.response(esResponse)
.code(esResponse.statusCode)
.header('warning', esResponse.headers.warning);
}
return h.response(`${esResponse.statusCode} - ${esResponse.statusMessage}`)
return h
.response(`${esResponse.statusCode} - ${esResponse.statusMessage}`)
.code(esResponse.statusCode)
.type('text/plain')
.header('warning', esResponse.headers.warning);
@ -142,6 +141,6 @@ export const createProxyRoute = ({
} else {
return await makeRequest(payload);
}
}
}
},
},
});

View file

@ -19,9 +19,9 @@
import { resolve as resolveUrl } from 'url';
import Wreck from 'wreck';
import Wreck from '@hapi/wreck';
const get = async (url) => {
const get = async url => {
const { payload } = await Wreck.get(url, { json: 'force' });
return payload;
};

View file

@ -17,7 +17,7 @@
* under the License.
*/
import Wreck from 'wreck';
import Wreck from '@hapi/wreck';
import { get } from 'lodash';
const MINUTE = 60 * 1000;
@ -84,8 +84,8 @@ export class KibanaServerUiSettings {
changes: {
...this._defaults,
...doc,
}
}
},
},
});
}
@ -97,8 +97,8 @@ export class KibanaServerUiSettings {
this._log.debug('applying update to kibana config: %j', updates);
await this._wreck.post('/api/kibana/settings', {
payload: {
changes: updates
}
changes: updates,
},
});
}
}

View file

@ -1699,6 +1699,32 @@
normalize-path "^2.0.1"
through2 "^2.0.3"
"@hapi/boom@7.x.x":
version "7.4.2"
resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-7.4.2.tgz#c16957cd09796f6c1bfb4031bdc39d66d6d750c3"
integrity sha512-T2CYcTI0AqSvC6YC7keu/fh9LVSMzfoMLharBnPbOwmc+Cexj9joIc5yNDKunaxYq9LPuOwMS0f2B3S1tFQUNw==
dependencies:
"@hapi/hoek" "6.x.x"
"@hapi/bourne@1.x.x":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-1.3.2.tgz#0a7095adea067243ce3283e1b56b8a8f453b242a"
integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==
"@hapi/hoek@6.x.x":
version "6.2.1"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-6.2.1.tgz#d3a66329159af879bfdf0b0cff2229c43c5a3451"
integrity sha512-+ryw4GU9pjr1uT6lBuErHJg3NYqzwJTvZ75nKuJijEzpd00Uqi6oiawTGDDf5Hl0zWmI7qHfOtaqB0kpQZJQzA==
"@hapi/wreck@^15.0.1":
version "15.0.1"
resolved "https://registry.yarnpkg.com/@hapi/wreck/-/wreck-15.0.1.tgz#b9f881965a7e649a8fffe6de25ba41973ed28415"
integrity sha512-ByXQna/W1FZk7dg8NEhL79u4QkhzszRz76VpgyGstSH8bLM01a0C8RsxmUBgi6Tjkag5jA9kaEIhF9dLpMrtBw==
dependencies:
"@hapi/boom" "7.x.x"
"@hapi/bourne" "1.x.x"
"@hapi/hoek" "6.x.x"
"@icons/material@^0.2.4":
version "0.2.4"
resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8"
@ -28058,7 +28084,7 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
wreck@14.x.x, wreck@^14.0.2:
wreck@14.x.x:
version "14.1.0"
resolved "https://registry.yarnpkg.com/wreck/-/wreck-14.1.0.tgz#b13e526b6a8318e5ebc6969c0b21075c06337067"
integrity sha512-y/iwFhwdGoM8Hk1t1I4LbuLhM3curVD8STd5NcFI0c/4b4cQAMLcnCRxXX9sLQAggDC8dXYSaQNsT64hga6lvA==