Merge branch 'nuxt-plugins' into 'main'

changes in nuxt plugins

See merge request PronounsPage/PronounsPage!433
This commit is contained in:
Valentyne Stigloher 2024-03-19 17:39:48 +00:00
commit 49fffa6d91
14 changed files with 186 additions and 178 deletions

View File

@ -167,12 +167,12 @@ const nuxtConfig: NuxtConfig = {
],
plugins: [
{ src: '~/plugins/polyfill.ts', mode: 'client' },
{ src: '~/plugins/axios.js' },
{ src: '~/plugins/axios.ts' },
{ src: '~/plugins/globals.ts' },
{ src: '~/plugins/auth.ts' },
{ src: '~/plugins/datepicker.js', mode: 'client' },
{ src: '~/plugins/track.js', mode: 'client' },
{ src: '~/plugins/browserDetect.js' },
{ src: '~/plugins/datepicker.ts', mode: 'client' },
{ src: '~/plugins/track.ts', mode: 'client' },
{ src: '~/plugins/browserDetect.ts' },
],
components: true,
buildModules: [
@ -222,6 +222,9 @@ const nuxtConfig: NuxtConfig = {
sentry: {
tracing: {
tracesSampleRate: 0.1,
browserTracing: {
enableInp: true,
},
},
publishRelease: {
telemetry: false,
@ -269,6 +272,14 @@ const nuxtConfig: NuxtConfig = {
// do not send user information as Sentry somehow automatically detects username, email and user id
// https://docs.sentry.io/platforms/javascript/data-management/sensitive-data/
delete event.user;
// temporarily add the event data as breadcrumb to show original event information
event.breadcrumbs?.push({
category: 'sentry.troubleshoot',
level: 'debug',
message: JSON.stringify(event),
});
return event;
},
beforeSendTransaction(event) {

View File

@ -1,10 +1,9 @@
import Vue from 'vue';
import type { JwtPayload } from 'jsonwebtoken';
import type { Store } from 'vuex';
import type { Plugin } from '@nuxt/types';
import { isGranted, parseUserJwt } from '../src/helpers.ts';
import cookieSettings from '../src/cookieSettings.ts';
import type { User } from '../src/user.ts';
import type { RootState } from '../store/index.ts';
declare module 'vue/types/vue' {
interface Vue {
@ -16,7 +15,7 @@ declare module 'vue/types/vue' {
}
}
export default ({ app, store }: { app: Vue, store: Store<RootState> }): void => {
const plugin: Plugin = ({ app, store }) => {
const token = app.$cookies.get('token');
if (token) {
store.commit('setToken', token);
@ -95,3 +94,5 @@ export default ({ app, store }: { app: Vue, store: Store<RootState> }): void =>
saveAccounts(accounts);
};
};
export default plugin;

View File

@ -1,4 +1,6 @@
export default function ({ $axios, app }) {
import type { Plugin } from '@nuxt/types';
const plugin: Plugin = ({ $axios, app }) => {
$axios.onRequest((config) => {
const token = app.$csrfToken();
@ -8,4 +10,6 @@ export default function ({ $axios, app }) {
return config;
});
}
};
export default plugin;

View File

@ -1,9 +1,11 @@
import type { Plugin } from '@nuxt/types';
const SAFARI_REGEX = /^((?!chrome|android).)*safari/i;
export default ({ req }, inject) => {
const plugin: Plugin = ({ req }, inject) => {
inject('isSafari', () => {
if (process.server && req) {
return SAFARI_REGEX.test(req.headers['user-agent']);
return SAFARI_REGEX.test(req.headers['user-agent']!);
}
if (process.client) {
@ -13,3 +15,5 @@ export default ({ req }, inject) => {
return false;
});
};
export default plugin;

View File

@ -1,6 +1,9 @@
import Vue from 'vue';
import VuejsDatePicker from 'vuejs-datepicker';
import type { Plugin } from '@nuxt/types';
export default () => {
const plugin: Plugin = () => {
Vue.component('Datepicker', VuejsDatePicker);
};
export default plugin;

View File

@ -1,12 +1,11 @@
import Vue from 'vue';
import type { Store } from 'vuex';
import type { Plugin } from '@nuxt/types';
import { Translator } from '../src/translator.js';
import { buildDict } from '../src/helpers.ts';
import { DateTime, Settings } from 'luxon';
import { decodeTime } from 'ulid';
import type { Pronoun } from '../src/classes.ts';
import type { LocaleDescription } from '../locale/locales.ts';
import type { RootState } from '../store/index.ts';
import translations from '../data/translations.suml';
import baseTranslations from '../locale/_base/translations.suml';
@ -25,10 +24,6 @@ declare module '@nuxt/types/config/runtime' {
declare module 'vue/types/vue' {
interface Vue {
// properties from other dependencies
router: any;
$cookies: any;
$eventHub: Vue;
$base: string;
$translator: Translator;
@ -40,7 +35,7 @@ declare module 'vue/types/vue' {
}
}
export default ({ app, store }: { app: Vue, store: Store<RootState> }): void => {
const plugin: Plugin = ({ app, store }) => {
Vue.prototype.$eventHub = new Vue();
Vue.prototype.$base = process.env.BASE_URL;
@ -123,9 +118,11 @@ export default ({ app, store }: { app: Vue, store: Store<RootState> }): void =>
return decodeTime(ulid) / 1000;
};
app.router.afterEach(() => {
app.router?.afterEach(() => {
if (typeof window !== 'undefined' && window.fusetag && window.fusetag.pageInit) {
window.fusetag.pageInit();
}
});
};
export default plugin;

View File

@ -1,4 +1,6 @@
export default (): void => {
import type { Plugin } from '@nuxt/types';
const plugin: Plugin = () => {
if (!Object.prototype.hasOwnProperty.call(Object, 'hasOwn')) {
Object.defineProperty(Object, 'hasOwn', {
value: (object: object, property: PropertyKey) => {
@ -7,3 +9,5 @@ export default (): void => {
});
}
};
export default plugin;

View File

@ -1,77 +0,0 @@
import * as Sentry from '@sentry/vue';
function defaultHandler({ plausible, to }) {
console.debug('[analytics] Tracking default handler: %O', to);
plausible.trackPageview({
url: to.toString(),
});
}
/**
* @param {(value: URL) => URL} redactor
* @param {(ctx) => void} base
* @return {(ctx) => void}
*/
function redact(redactor, base = defaultHandler) {
return (ctx) => base({
...ctx,
to: redactor(ctx.to),
});
}
const USER_AT = /^\/@.+/;
const USER_SUBPAGE = /^\/(u|card)\/.*/;
const TRACKER_OVERRIDES = [
{
test(v) {
return USER_AT.test(v) || USER_SUBPAGE.test(v);
},
handling: redact((v) => {
let pathname = v.pathname;
if (USER_AT.test(pathname)) {
pathname = pathname.replace(USER_AT, '/@--redacted--');
}
if (USER_SUBPAGE.test(pathname)) {
pathname = pathname.replace(USER_SUBPAGE, '/$1/--redacted--');
}
v.pathname = pathname;
return v;
}),
},
];
export const plugin = function ({ app }) {
const plausible = app.$plausible;
app.router.afterEach((to, from) => {
let handler = defaultHandler;
for (const trackerOverride of TRACKER_OVERRIDES) {
if (!trackerOverride.test(to.fullPath)) {
continue;
}
if (trackerOverride.handling === false) {
// console.debug("[analytics] Page is blocked from tracking");
return;
} else if (typeof trackerOverride.handling === 'function') {
handler = trackerOverride.handling;
} else {
throw new Error('Tracking override handling is invalid');
}
break;
}
// console.log("[analytics] Tracking pageview")
try {
handler({
plausible,
to: new URL(to.fullPath, window.location.href),
from: new URL(from.fullPath, window.location.href),
});
} catch (error) {
Sentry.captureException(error);
}
});
};
export default plugin;

32
plugins/track.ts Normal file
View File

@ -0,0 +1,32 @@
import * as Sentry from '@sentry/vue';
import type { Plugin } from '@nuxt/types';
const USER_AT = /^\/@.+/;
const USER_SUBPAGE = /^\/(u|card)\/.*/;
export const normalizeUrl = (page: URL): URL => {
if (USER_AT.test(page.pathname)) {
page.pathname = page.pathname.replace(USER_AT, '/@--redacted--');
}
if (USER_SUBPAGE.test(page.pathname)) {
page.pathname = page.pathname.replace(USER_SUBPAGE, '/$1/--redacted--');
}
page.hash = '';
return page;
};
const plugin: Plugin = ({ app }) => {
app.router?.afterEach((to) => {
try {
const url = normalizeUrl(new URL(to.fullPath, window.location.href));
console.debug('[analytics] tracking page view:', url.toString());
app.$plausible.trackPageview({
url: url.toString(),
});
} catch (error) {
Sentry.captureException(error);
}
});
};
export default plugin;

3
plugins/vuejs-datepicker.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
declare module 'vuejs-datepicker' {
export = unknown;
}

View File

@ -0,0 +1,24 @@
import { describe, expect, test } from '@jest/globals';
import { normalizeUrl } from '../../plugins/track.ts';
describe('when tracking', () => {
const base = 'https://pronouns.page';
test('normal pages are tracked verbatim', () => {
expect(normalizeUrl(new URL('/pronouns', base))).toEqual(new URL('/pronouns', base));
});
test.each([
{ given: '/@example', expected: '/@--redacted--' },
{ given: '/u/example', expected: '/u/--redacted--' },
{ given: '/card/example', expected: '/card/--redacted--' },
])('pages containing user name are redacted', ({ given, expected }) => {
expect(normalizeUrl(new URL(given, base))).toEqual(new URL(expected, base));
});
test.each([
{ given: '/terminology#queer', expected: '/terminology' },
{ given: '/@example#they/them', expected: '/@--redacted--' },
])('hashes are stripped', ({ given, expected }) => {
expect(normalizeUrl(new URL(given, base))).toEqual(new URL(expected, base));
});
});

View File

@ -21,7 +21,9 @@
"@nuxt/typescript-build",
"@nuxtjs/axios",
"@nuxtjs/sentry",
"@types/node"
"@types/node",
"cookie-universal-nuxt",
"vue-plausible"
]
},
"exclude": [

158
yarn.lock
View File

@ -2453,51 +2453,51 @@
cookie-parser "^1.4.5"
csurf "^1.11.0"
"@sentry-internal/feedback@7.105.0":
version "7.105.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-7.105.0.tgz#f2a25b55e5368509cfd540c21e74503568492057"
integrity sha512-17doUQFKYgLfG7EmZXjZQ7HR/aBzuLDd+GVaCNthUPyiz/tltV7EFECDWwHpXqzQgYRgroSbY8PruMVujFGUUw==
"@sentry-internal/feedback@7.107.0":
version "7.107.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-7.107.0.tgz#144cf01b1c1739d61db3990519f59b49a356fef1"
integrity sha512-okF0B9AJHrpkwNMxNs/Lffw3N5ZNbGwz4uvCfyOfnMxc7E2VfDM18QzUvTBRvNr3bA9wl+InJ+EMG3aZhyPunA==
dependencies:
"@sentry/core" "7.105.0"
"@sentry/types" "7.105.0"
"@sentry/utils" "7.105.0"
"@sentry/core" "7.107.0"
"@sentry/types" "7.107.0"
"@sentry/utils" "7.107.0"
"@sentry-internal/replay-canvas@7.105.0":
version "7.105.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-7.105.0.tgz#fed7d67d976837ef7c1b72a6db461179703fa6f5"
integrity sha512-XMBdkjIDhap5Gwrub5wlUJhuUVJM4aL4lZV8KcxJZZSXgXsnyGYbEh9SPZOHO05jtbxTxVeL3Pik5qtYjdGnPA==
"@sentry-internal/replay-canvas@7.107.0":
version "7.107.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-7.107.0.tgz#ce2a8f6bf63ab962e696f26b509cfb87aa931302"
integrity sha512-dmDL9g3QDfo7axBOsVnpiKdJ/DXrdeuRv1AqsLgwzJKvItsv0ZizX0u+rj5b1UoxcwbXRMxJ0hit5a1yt3t/ow==
dependencies:
"@sentry/core" "7.105.0"
"@sentry/replay" "7.105.0"
"@sentry/types" "7.105.0"
"@sentry/utils" "7.105.0"
"@sentry/core" "7.107.0"
"@sentry/replay" "7.107.0"
"@sentry/types" "7.107.0"
"@sentry/utils" "7.107.0"
"@sentry-internal/tracing@7.105.0":
version "7.105.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.105.0.tgz#9cb06f8281454343215cfe4b119c8198f032ec72"
integrity sha512-b+AFYB7Bc9vmyxl2jbmuT4esX5G0oPfpz35A0sxFzmJIhvMg1YMDNio2c81BtKN+VSPORCnKMLhfk3kyKKvWMQ==
"@sentry-internal/tracing@7.107.0":
version "7.107.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.107.0.tgz#a10b4abcbc9e0d8da948e3a95029574387ca7b16"
integrity sha512-le9wM8+OHBbq7m/8P7JUJ1UhSPIty+Z/HmRXc5Z64ODZcOwFV6TmDpYx729IXDdz36XUKmeI+BeM7yQdTTZPfQ==
dependencies:
"@sentry/core" "7.105.0"
"@sentry/types" "7.105.0"
"@sentry/utils" "7.105.0"
"@sentry/core" "7.107.0"
"@sentry/types" "7.107.0"
"@sentry/utils" "7.107.0"
"@sentry/babel-plugin-component-annotate@2.14.2":
version "2.14.2"
resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.14.2.tgz#d756bed93495e97a5a2aad56e2a6dc5020305adc"
integrity sha512-mFBVnIZmdMrpxo61rG5yf0WFt5VrRpy8cpIpJtT3mYkX9vDmcUZaZaD1ctv73iZF3QwaieVdn05Na5mWzZ8h/A==
"@sentry/browser@7.105.0":
version "7.105.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.105.0.tgz#3eb56785cfc1cf58528b29ea954b73093e6f9481"
integrity sha512-OlYJzsZG109T1VpZ7O7KXf9IXCUUpp41lkkQM7ICBOBsfiHRUKmV5piTGCG5UgAvyb/gI/I1uQQtO4jthcHKEA==
"@sentry/browser@7.107.0":
version "7.107.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.107.0.tgz#a1caf4a3c39857862ba3314b9d4ed03f9259f338"
integrity sha512-KnqaQDhxv6w9dJ+mYLsNwPeGZfgbpM3vaismBNyJCKLgWn2V75kxkSq+bDX8LQT/13AyK7iFp317L6P8EuNa3g==
dependencies:
"@sentry-internal/feedback" "7.105.0"
"@sentry-internal/replay-canvas" "7.105.0"
"@sentry-internal/tracing" "7.105.0"
"@sentry/core" "7.105.0"
"@sentry/replay" "7.105.0"
"@sentry/types" "7.105.0"
"@sentry/utils" "7.105.0"
"@sentry-internal/feedback" "7.107.0"
"@sentry-internal/replay-canvas" "7.107.0"
"@sentry-internal/tracing" "7.107.0"
"@sentry/core" "7.107.0"
"@sentry/replay" "7.107.0"
"@sentry/types" "7.107.0"
"@sentry/utils" "7.107.0"
"@sentry/bundler-plugin-core@2.14.2":
version "2.14.2"
@ -2567,65 +2567,65 @@
"@sentry/cli-win32-i686" "2.29.1"
"@sentry/cli-win32-x64" "2.29.1"
"@sentry/core@7.105.0", "@sentry/core@^7.100.0":
version "7.105.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.105.0.tgz#89db519dd9aa7326de63a7eaccf861de3769ab1c"
integrity sha512-5xsaTG6jZincTeJUmZomlv20mVRZUEF1U/g89lmrSOybyk2+opEnB1JeBn4ODwnvmSik8r2QLr6/RiYlaxRJCg==
"@sentry/core@7.107.0", "@sentry/core@^7.100.0":
version "7.107.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.107.0.tgz#926838ba2c2861d6bd2bced0232e1f9d1ead6c75"
integrity sha512-C7ogye6+KPyBi8NVL0P8Rxx3Ur7Td8ufnjxosVy678lqY+dcYPk/HONROrzUFYW5fMKWL4/KYnwP+x9uHnkDmw==
dependencies:
"@sentry/types" "7.105.0"
"@sentry/utils" "7.105.0"
"@sentry/types" "7.107.0"
"@sentry/utils" "7.107.0"
"@sentry/integrations@^7.100.0":
version "7.105.0"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.105.0.tgz#8953bd310d8681f9a29f918269b98640ab302abe"
integrity sha512-AgzecTkF0o+C4svbroMGA+cW5LRnfFSoJnzF5ltUB67hnX906amlwbOvdkKD3MugYO02nRSjF/eEi26E1HACMA==
version "7.107.0"
resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-7.107.0.tgz#a46a82be885ef1482197ed7073d7982bd266c09a"
integrity sha512-0h2sZcjcdptS2pju1KSF4+sXaRaFTlmAN1ZokFfmfnVTs6cVtIFttUFxTYrwQUEE2knpAV05pz87zg1yfPAfYg==
dependencies:
"@sentry/core" "7.105.0"
"@sentry/types" "7.105.0"
"@sentry/utils" "7.105.0"
"@sentry/core" "7.107.0"
"@sentry/types" "7.107.0"
"@sentry/utils" "7.107.0"
localforage "^1.8.1"
"@sentry/node@^7.100.0":
version "7.105.0"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.105.0.tgz#cfe8e5602dff2cc754a95412f44c9ca8156422ff"
integrity sha512-b0QwZ7vT4hcJi6LmNRh3dcaYpLtXnkYXkL0rfhMb8hN8sUx8zuOWFMI7j0cfAloVThUeJVwGyv9dERfzGS2r2w==
version "7.107.0"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.107.0.tgz#d60c2e28953f2ba14d12ada9190f1fc577b2b280"
integrity sha512-UZXkG7uThT2YyPW8AOSKRXp1LbVcBHufa4r1XAwBukA2FKO6HHJPjMUgY6DYVQ6k+BmA56CNfVjYrdLbyjBYYA==
dependencies:
"@sentry-internal/tracing" "7.105.0"
"@sentry/core" "7.105.0"
"@sentry/types" "7.105.0"
"@sentry/utils" "7.105.0"
"@sentry-internal/tracing" "7.107.0"
"@sentry/core" "7.107.0"
"@sentry/types" "7.107.0"
"@sentry/utils" "7.107.0"
"@sentry/replay@7.105.0":
version "7.105.0"
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.105.0.tgz#61784e3e88afa66a0d9b1b9d222153ab54ea4bd0"
integrity sha512-hZD2m6fNL9gorUOaaEpqxeH7zNP4y2Ej0TdieM1HMQ2q9Zrm9yOzk9/7ALfbRLIZFRMFTqo9vvVztLs3E+Hx+g==
"@sentry/replay@7.107.0":
version "7.107.0"
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-7.107.0.tgz#d714f864ef8602e6d009b2fa8ff8e4ef63c3e9e4"
integrity sha512-BNJDEVaEwr/YnV22qnyVA1almx/3p615m3+KaF8lPo7YleYgJGSJv1auH64j1G8INkrJ0J0wFBujb1EFjMYkxA==
dependencies:
"@sentry-internal/tracing" "7.105.0"
"@sentry/core" "7.105.0"
"@sentry/types" "7.105.0"
"@sentry/utils" "7.105.0"
"@sentry-internal/tracing" "7.107.0"
"@sentry/core" "7.107.0"
"@sentry/types" "7.107.0"
"@sentry/utils" "7.107.0"
"@sentry/types@7.105.0":
version "7.105.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.105.0.tgz#51dadb7ad650e883459acf18df2ecbb5b4b6e5c2"
integrity sha512-80o0KMVM+X2Ym9hoQxvJetkJJwkpCg7o6tHHFXI+Rp7fawc2iCMTa0IRQMUiSkFvntQLYIdDoNNuKdzz2PbQGA==
"@sentry/types@7.107.0":
version "7.107.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.107.0.tgz#5ba4b472be6ccad9aecd58dbc0141a09dafb68c1"
integrity sha512-H7qcPjPSUWHE/Zf5bR1EE24G0pGVuJgrSx8Tvvl5nKEepswMYlbXHRVSDN0gTk/E5Z7cqf+hUBOpkQgZyps77w==
"@sentry/utils@7.105.0", "@sentry/utils@^7.100.0":
version "7.105.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.105.0.tgz#727187d252b97cb9e6c78bcdd0e9a1d14e60f313"
integrity sha512-YVAV0c2KLM8+VZCicQ/E/P2+J9Vs0hGhrXwV7w6ZEAtvxrg4oF270toL1WRhvcaf8JO4J1v4V+LuU6Txs4uEeQ==
"@sentry/utils@7.107.0", "@sentry/utils@^7.100.0":
version "7.107.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.107.0.tgz#b8524539d052a40f9c5f34a8347501f0f81a0751"
integrity sha512-C6PbN5gHh73MRHohnReeQ60N8rrLYa9LciHue3Ru2290eSThg4CzsPnx4SzkGpkSeVlhhptKtKZ+hp/ha3iVuw==
dependencies:
"@sentry/types" "7.105.0"
"@sentry/types" "7.107.0"
"@sentry/vue@^7.100.0":
version "7.105.0"
resolved "https://registry.yarnpkg.com/@sentry/vue/-/vue-7.105.0.tgz#37655b96db8acb65309d8462d8c3d26cf000932d"
integrity sha512-QWUWAwCFruw75aqsExHKZ9HWtFyFRo4+8UsCiXUWeMpcpQu+mZ4VzHbTxNBZKmSTA0jYa/tTpYXkfI5ozM+y4A==
version "7.107.0"
resolved "https://registry.yarnpkg.com/@sentry/vue/-/vue-7.107.0.tgz#40cb1589f18338ce1effda89794d567d1f40ee48"
integrity sha512-nUUaa5s2W7UmgjavoksoBlduc6mePZmo4k3y5lXPhSvZ92FcidLZItRUkwubaUBHgu8zvRxkTeXGhbqfWN4Ukg==
dependencies:
"@sentry/browser" "7.105.0"
"@sentry/core" "7.105.0"
"@sentry/types" "7.105.0"
"@sentry/utils" "7.105.0"
"@sentry/browser" "7.107.0"
"@sentry/core" "7.107.0"
"@sentry/types" "7.107.0"
"@sentry/utils" "7.107.0"
"@sentry/webpack-plugin@^2.14.2":
version "2.14.2"
@ -14311,9 +14311,9 @@ ufo@^1.3.1:
integrity sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==
ufo@^1.3.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.4.0.tgz#39845b31be81b4f319ab1d99fd20c56cac528d32"
integrity sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ==
version "1.5.1"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.1.tgz#ec42543a918def8d0ce185e498d080016f35daf6"
integrity sha512-HGyF79+/qZ4soRvM+nHERR2pJ3VXDZ/8sL1uLahdgEDf580NkgiWOxLk33FetExqOWp352JZRsgXbG/4MaGOSg==
uglify-js@^3.5.1:
version "3.17.4"