Added 406 errors when Accept-Version header is missmatched

refs https://github.com/TryGhost/Toolbox/issues/280

- This change covers two use cases:
  -  The accept-version > current version + the request cannot be served: ERROR CASE 1
    - The accept-version < current version + the request cannot be served: ERROR CASE 2
- Along with 406 status there's additional information about the probable cause and action to be taken by the Ghost site owner or an integration talking to the Ghost API.
- These errors is designed to allow introducing breaking API changes gradually and have meaningful information when the requests cannot be server any longer
This commit is contained in:
Naz 2022-04-08 14:56:53 +08:00 committed by naz
parent 76aa2479f8
commit 4959dd83b8
4 changed files with 248 additions and 45 deletions

View File

@ -69,7 +69,7 @@
"@tryghost/domain-events": "0.1.9",
"@tryghost/email-analytics-provider-mailgun": "1.0.8",
"@tryghost/email-analytics-service": "1.0.6",
"@tryghost/errors": "1.2.8",
"@tryghost/errors": "1.2.10",
"@tryghost/express-dynamic-redirects": "0.2.7",
"@tryghost/helpers": "1.1.62",
"@tryghost/image-transform": "1.0.29",
@ -91,7 +91,7 @@
"@tryghost/members-stripe-service": "0.9.4",
"@tryghost/metrics": "1.0.8",
"@tryghost/minifier": "0.1.12",
"@tryghost/mw-error-handler": "0.1.8",
"@tryghost/mw-error-handler": "0.2.0",
"@tryghost/mw-session-from-token": "0.1.28",
"@tryghost/nodemailer": "0.3.17",
"@tryghost/nql": "0.9.1",

View File

@ -1,5 +1,59 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`API Versioning responds with current content version header when requested version is AHEAD and CAN respond 1: [body] 1`] = `
Object {
"site": Object {
"accent_color": "#FF1A75",
"description": "Thoughts, stories and ideas",
"icon": null,
"logo": null,
"title": "Ghost",
"url": "http://127.0.0.1:2369/",
"version": StringMatching /\\\\d\\+\\\\\\.\\\\d\\+/,
},
}
`;
exports[`API Versioning responds with current content version header when requested version is AHEAD and CAN respond 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "167",
"content-type": "application/json; charset=utf-8",
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": Any<String>,
"vary": "Origin, Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`API Versioning responds with current content version header when requested version is BEHIND current version and CAN respond 1: [body] 1`] = `
Object {
"site": Object {
"accent_color": "#FF1A75",
"description": "Thoughts, stories and ideas",
"icon": null,
"logo": null,
"title": "Ghost",
"url": "http://127.0.0.1:2369/",
"version": StringMatching /\\\\d\\+\\\\\\.\\\\d\\+/,
},
}
`;
exports[`API Versioning responds with current content version header when requested version is BEHIND current version and CAN respond 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "167",
"content-type": "application/json; charset=utf-8",
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
"etag": Any<String>,
"vary": "Origin, Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`API Versioning responds with current content version header when requested version is behind current version with no known changes 1: [headers] 1`] = `
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
@ -13,6 +67,90 @@ Object {
}
`;
exports[`API Versioning responds with error current content version header when requested version is AHEAD and CANNOT respond 1: [body] 1`] = `
Object {
"errors": Array [
Object {
"code": null,
"context": "Provided client version v999.1 is ahead of current Ghost instance version v4.42.",
"details": null,
"help": "Upgrade your Ghost instance.",
"id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"message": "Request not acceptable for provided Accept-Version header.",
"property": null,
"type": "RequestNotAcceptableError",
},
],
}
`;
exports[`API Versioning responds with error current content version header when requested version is AHEAD and CANNOT respond 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "338",
"content-type": "application/json; charset=utf-8",
"etag": Any<String>,
"vary": "Origin, Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`API Versioning responds with error current content version header when requested version is BEHIND and CANNOT respond 1: [body] 1`] = `
Object {
"errors": Array [
Object {
"code": null,
"context": "Provided client version v3.1 is outdated and is behind current Ghost version v4.42.",
"details": null,
"help": "Upgrade your Ghost API client.",
"id": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
"message": "Request not acceptable for provided Accept-Version header.",
"property": null,
"type": "RequestNotAcceptableError",
},
],
}
`;
exports[`API Versioning responds with error current content version header when requested version is BEHIND and CANNOT respond 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "343",
"content-type": "application/json; charset=utf-8",
"etag": Any<String>,
"vary": "Origin, Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`API Versioning responds with no content version header when accept version header is NOT PRESENT 1: [body] 1`] = `
Object {
"site": Object {
"accent_color": "#FF1A75",
"description": "Thoughts, stories and ideas",
"icon": null,
"logo": null,
"title": "Ghost",
"url": "http://127.0.0.1:2369/",
"version": StringMatching /\\\\d\\+\\\\\\.\\\\d\\+/,
},
}
`;
exports[`API Versioning responds with no content version header when accept version header is NOT PRESENT 2: [headers] 1`] = `
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
"content-length": "167",
"content-type": "application/json; charset=utf-8",
"etag": Any<String>,
"vary": "Origin, Accept-Encoding",
"x-powered-by": "Express",
}
`;
exports[`API Versioning responds with no content version header when accept version header is not present 1: [headers] 1`] = `
Object {
"access-control-allow-origin": "http://127.0.0.1:2369",

View File

@ -1,5 +1,5 @@
const {agentProvider, matchers} = require('../../utils/e2e-framework');
const {anyString, stringMatching} = matchers;
const {anyErrorId, anyString, stringMatching} = matchers;
describe('API Versioning', function () {
let agent;
@ -11,18 +11,73 @@ describe('API Versioning', function () {
it('responds with no content version header when accept version header is NOT PRESENT', async function () {
await agent
.get('site/')
.matchBodySnapshot({
site: {
version: stringMatching(/\d+\.\d+/)
}
})
.matchHeaderSnapshot({
etag: anyString
});
});
it('responds with current content version header when requested version is behind current version with no known changes', async function () {
it('responds with current content version header when requested version is BEHIND current version and CAN respond', async function () {
await agent
.get('site/')
.header('Accept-Version', 'v3.0')
.matchBodySnapshot({
site: {
version: stringMatching(/\d+\.\d+/)
}
})
.matchHeaderSnapshot({
etag: anyString,
'content-version': stringMatching(/v\d+\.\d+/)
});
});
it('responds with current content version header when requested version is AHEAD and CAN respond', async function () {
await agent
.get('site/')
.header('Accept-Version', 'v999.5')
.matchBodySnapshot({
site: {
version: stringMatching(/\d+\.\d+/)
}
})
.matchHeaderSnapshot({
etag: anyString,
'content-version': stringMatching(/v\d+\.\d+/)
});
});
it('responds with error current content version header when requested version is AHEAD and CANNOT respond', async function () {
// CASE 2: If accept-version is behind, send a 406 & tell them the client needs updating.
await agent
.get('removed_endpoint')
.header('Accept-Version', 'v999.1')
.matchHeaderSnapshot({
etag: anyString
})
.matchBodySnapshot({
errors: [{
id: anyErrorId
}]
});
});
it('responds with error current content version header when requested version is BEHIND and CANNOT respond', async function () {
// CASE 2: If accept-version is behind, send a 406 & tell them the client needs updating.
await agent
.get('removed_endpoint')
.header('Accept-Version', 'v3.1')
.matchHeaderSnapshot({
etag: anyString
})
.matchBodySnapshot({
errors: [{
id: anyErrorId
}]
});
});
});

View File

@ -1859,6 +1859,15 @@
"@tryghost/debug" "^0.1.9"
lodash "^4.17.20"
"@tryghost/errors@1.2.10":
version "1.2.10"
resolved "https://registry.yarnpkg.com/@tryghost/errors/-/errors-1.2.10.tgz#b01b16744986005784ffd6916eec48a3e93976a9"
integrity sha512-v5r5JzhQTkNLJBk66B75x+jyOE/WxzEBfeAtA3y1YaFtl9SQMaN1qi/7+sQzt0VrLk1qtqAOReWc/1Cncy9gOg==
dependencies:
lodash "^4.17.21"
utils-copy-error "^1.0.1"
uuid "^8.3.2"
"@tryghost/errors@1.2.7":
version "1.2.7"
resolved "https://registry.yarnpkg.com/@tryghost/errors/-/errors-1.2.7.tgz#f2da9deadb6139167a9e967e1212a66064c5abce"
@ -1868,7 +1877,7 @@
utils-copy-error "^1.0.1"
uuid "^8.3.2"
"@tryghost/errors@1.2.8", "@tryghost/errors@^1.0.0", "@tryghost/errors@^1.1.0", "@tryghost/errors@^1.1.1", "@tryghost/errors@^1.2.1", "@tryghost/errors@^1.2.3", "@tryghost/errors@^1.2.5", "@tryghost/errors@^1.2.7":
"@tryghost/errors@^1.0.0", "@tryghost/errors@^1.1.0", "@tryghost/errors@^1.1.1", "@tryghost/errors@^1.2.1", "@tryghost/errors@^1.2.3", "@tryghost/errors@^1.2.5", "@tryghost/errors@^1.2.7":
version "1.2.8"
resolved "https://registry.yarnpkg.com/@tryghost/errors/-/errors-1.2.8.tgz#b4ba1c31831216222be332ee7a37d9c4a376ac2a"
integrity sha512-O8CUS4SHsBYFc6/7yUDJ5yk0vhcdzPxFiISgrFhzptOd79ZdXwk9c8Pz5uSDUXPaCqKaWMYLTjSof30IxVvvAA==
@ -2217,15 +2226,16 @@
dependencies:
lodash "^4.17.11"
"@tryghost/mw-error-handler@0.1.8":
version "0.1.8"
resolved "https://registry.yarnpkg.com/@tryghost/mw-error-handler/-/mw-error-handler-0.1.8.tgz#03861566314e2d3e3bbd9d8f77b0db83813a0d45"
integrity sha512-wUXcQ2T/iX2pOGuBr8uvNb2EtbvAAis7l0tgwzJt5Kx4K4fzUzASsYVrqmXvv3b+vhMfBWesD7FlTafLi/euHg==
"@tryghost/mw-error-handler@0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@tryghost/mw-error-handler/-/mw-error-handler-0.2.0.tgz#236c207a0c5b62953445623c105bca00f730f45f"
integrity sha512-b+PcBqzTK/ekJ9SeKlMSq874FLjPrWiF4jw73gFilITmTSMDVcaEconaJj54dJ7/zOMQLDuNqDTLJV/WODPywQ==
dependencies:
"@tryghost/debug" "^0.1.9"
"@tryghost/errors" "1.2.8"
"@tryghost/errors" "1.2.10"
"@tryghost/tpl" "^0.1.8"
lodash "^4.17.21"
semver "^7.3.6"
"@tryghost/mw-session-from-token@0.1.28":
version "0.1.28"
@ -8285,9 +8295,9 @@ lru-cache@^6.0.0:
yallist "^4.0.0"
lru-cache@^7.4.0:
version "7.7.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.7.3.tgz#98cd19eef89ce6a4a3c4502c17c833888677c252"
integrity sha512-WY9wjJNQt9+PZilnLbuFKM+SwDull9+6IAguOrarOMoOHTcJ9GnXSO11+Gw6c7xtDkBkthR57OZMtZKYr+1CEw==
version "7.8.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.8.0.tgz#649aaeb294a56297b5cbc5d70f198dcc5ebe5747"
integrity sha512-AmXqneQZL3KZMIgBpaPTeI6pfwh+xQ2vutMsyqOu1TBdEXFZgpG/80wuJ531w2ZN7TI0/oc8CPxzh/DKQudZqg==
lru-cache@~4.0.0:
version "4.0.2"
@ -8434,9 +8444,9 @@ markdown-table@^1.1.0:
integrity sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==
marked@>=0.1.4:
version "4.0.12"
resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.12.tgz#2262a4e6fd1afd2f13557726238b69a48b982f7d"
integrity sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ==
version "4.0.13"
resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.13.tgz#4fd46ca93da46448f3d83f054d938c4f905a258d"
integrity sha512-lS/ZCa4X0gsRcfWs1eoh6dLnHr9kVH3K1t2X4M/tTtNouhZ7anS1Csb6464VGLQHv8b2Tw1cLeZQs58Jav8Rzw==
maxmin@^3.0.0:
version "3.0.0"
@ -9072,9 +9082,9 @@ node-fetch@2.6.7, node-fetch@^2.6.0:
whatwg-url "^5.0.0"
node-forge@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.0.tgz#37a874ea723855f37db091e6c186e5b67a01d4b2"
integrity sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==
version "1.3.1"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
node-gyp@^8.4.1:
version "8.4.1"
@ -10020,9 +10030,9 @@ postcss-reduce-transforms@^5.1.0:
postcss-value-parser "^4.2.0"
postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9:
version "6.0.9"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz#ee71c3b9ff63d9cd130838876c13a2ec1a992b2f"
integrity sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==
version "6.0.10"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
@ -10270,16 +10280,16 @@ pump@^3.0.0:
once "^1.3.1"
punycode2@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/punycode2/-/punycode2-1.0.0.tgz#e2b4b9a9a8ff157d0b84438e203181ee7892dfd8"
integrity sha1-4rS5qaj/FX0LhEOOIDGB7niS39g=
version "1.0.1"
resolved "https://registry.yarnpkg.com/punycode2/-/punycode2-1.0.1.tgz#7190290a2f672a97c18bda38ad9f402787b4eded"
integrity sha512-+TXpd9YRW4YUZZPoRHJ3DILtWwootGc2DsgvfHmklQ8It1skINAuqSdqizt5nlTaBmwrYACHkHApCXjc9gHk2Q==
punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
qs@6.10.3, qs@^6.10.1, qs@^6.4.0, qs@^6.6.0:
qs@6.10.3, qs@^6.10.1, qs@^6.10.3, qs@^6.4.0:
version "6.10.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==
@ -10861,7 +10871,7 @@ semver@7.3.5:
dependencies:
lru-cache "^6.0.0"
semver@7.3.6, semver@^7.2.1, semver@^7.3.2, semver@^7.3.5:
semver@7.3.6, semver@^7.2.1, semver@^7.3.2, semver@^7.3.5, semver@^7.3.6:
version "7.3.6"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.6.tgz#5d73886fb9c0c6602e79440b97165c29581cbb2b"
integrity sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==
@ -11473,12 +11483,12 @@ strip-json-comments@~2.0.1:
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
stripe@^8.174.0:
version "8.212.0"
resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.212.0.tgz#78dc8d2be770580be23a8e597641a9ace6fd1035"
integrity sha512-xQ2uPMRAmRyOiMZktw3hY8jZ8LFR9lEQRPEaQ5WcDcn51kMyn46GeikOikxiFTHEN8PeKRdwtpz4yNArAvu/Kg==
version "8.215.0"
resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.215.0.tgz#bb464e256fb83da9ea2f514711fd0f6f7ae7dc9a"
integrity sha512-M+7iTZ9bzTkU1Ms+Zsuh0mTQfEzOjMoqyEaVBpuUmdbWTvshavzpAihsOkfabEu+sNY0vdbQxxHZ4kI3W8pKHQ==
dependencies:
"@types/node" ">=8.1.0"
qs "^6.6.0"
qs "^6.10.3"
stylehacks@^5.1.0:
version "5.1.0"
@ -11489,9 +11499,9 @@ stylehacks@^5.1.0:
postcss-selector-parser "^6.0.4"
superagent@^7.1.0:
version "7.1.1"
resolved "https://registry.yarnpkg.com/superagent/-/superagent-7.1.1.tgz#2ab187d38c3078c31c3771c0b751f10163a27136"
integrity sha512-CQ2weSS6M+doIwwYFoMatklhRbx6sVNdB99OEJ5czcP3cng76Ljqus694knFWgOj3RkrtxZqIgpe6vhe0J7QWQ==
version "7.1.2"
resolved "https://registry.yarnpkg.com/superagent/-/superagent-7.1.2.tgz#71393141edd086ccf2544a29a4a609e46b7911f3"
integrity sha512-o9/fP6dww7a4xmEF5a484o2rG34UUGo8ztDlv7vbCWuqPhpndMi0f7eXxdlryk5U12Kzy46nh8eNpLAJ93Alsg==
dependencies:
component-emitter "^1.3.0"
cookiejar "^2.1.3"
@ -11676,21 +11686,21 @@ tiny-lr@^1.1.1:
qs "^6.4.0"
tlds@^1.228.0:
version "1.230.0"
resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.230.0.tgz#9c836528c4062e565b3b58bfaef83d99e09c113d"
integrity sha512-QFuY6JBWZt2bZXlapjqsojul5dv9xfo7Uc8wTUlctJOuF+BS/ICni2f4x7MFiT7muUVmcKC1LvGnU4GWhYO0PQ==
version "1.231.0"
resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.231.0.tgz#93880175cd0a06fdf7b5b5b9bcadff9d94813e39"
integrity sha512-L7UQwueHSkGxZHQBXHVmXW64oi+uqNtzFt2x6Ssk7NVnpIbw16CRs4eb/jmKOZ9t2JnqZ/b3Cfvo97lnXqKrhw==
tldts-core@^5.7.73:
version "5.7.73"
resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-5.7.73.tgz#c5e279da1aea8ed990dd08e9becd1ac9c0d8a7de"
integrity sha512-rTT318D4+HEw0WR3gYbmc2DGqgkLjvfwwD4M1K8hALW1dnnEvG3aGxfVMRv3V7QMTHBuhFoSFlWfbcvKYWtcUQ==
tldts-core@^5.7.74:
version "5.7.74"
resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-5.7.74.tgz#c80d0b18e8e86a9ffabc94867ab8121244b594c2"
integrity sha512-NCv3WFmdZucNELOjNN/yVsqJLHVxMPOPKaXMCKswcoJgXr83OKXoUUIEULE+CNmzNBEdoETQ2bpm6dG6AW+Yrw==
tldts@~5.7.73:
version "5.7.73"
resolved "https://registry.yarnpkg.com/tldts/-/tldts-5.7.73.tgz#c1bd385fa951d1dff7df2fd26f96f147ada1cb87"
integrity sha512-+xhcsIsf0RZYSVxnr6iP43WP4pAqYugEBXA+cpHeatuo6UIUXgE/X+Dakxa+nsO8xhyWtD7eKP7HjNBiFhdTvA==
version "5.7.74"
resolved "https://registry.yarnpkg.com/tldts/-/tldts-5.7.74.tgz#9114d2858b40c6b12b4d635ea2cffa92c75650bf"
integrity sha512-uwyKgyKBbqdvh2vLPQ6UXEOC1Hn4+/Hkn4Cakw8eptizvJnpucVRfVwrzEFKiRa2QMZm2LceQORZMeOJb1OLxg==
dependencies:
tldts-core "^5.7.73"
tldts-core "^5.7.74"
tmp@0.2.1:
version "0.2.1"