From 3ced65b8a41e0021e24df2d03f3532754d4a8a9a Mon Sep 17 00:00:00 2001
From: eddie <66155195+just-toby@users.noreply.github.com>
Date: Thu, 5 Oct 2023 12:19:58 -0700
Subject: [PATCH] feat: add sitemap for app.uniswap.org (#7408)
* feat: add sitemap for app.uniswap.org
* feat: script to update lastmod
* fix: deps and snapshots
* fix: use xml2js
* fix: improve test and sitemap
---
package.json | 5 +-
public/sitemap.xml | 19 ++
scripts/generate-sitemap.js | 25 +++
src/pages/App.tsx | 169 ++--------------
src/pages/RouteDefinitions.tsx | 208 ++++++++++++++++++++
src/pages/__snapshots__/routes.test.ts.snap | 200 +++++++++++++++++++
src/pages/routes.test.ts | 26 +++
yarn.lock | 27 ++-
8 files changed, 518 insertions(+), 161 deletions(-)
create mode 100644 public/sitemap.xml
create mode 100644 scripts/generate-sitemap.js
create mode 100644 src/pages/RouteDefinitions.tsx
create mode 100644 src/pages/__snapshots__/routes.test.ts.snap
create mode 100644 src/pages/routes.test.ts
diff --git a/package.json b/package.json
index 4dfaa26909..e28b5c6ad3 100644
--- a/package.json
+++ b/package.json
@@ -13,10 +13,11 @@
"graphql:generate:thegraph": "graphql-codegen --config graphql.thegraph.codegen.config.ts",
"graphql:generate": "yarn graphql:generate:data && yarn graphql:generate:thegraph",
"graphql": "yarn graphql:fetch && yarn graphql:generate",
+ "sitemap:generate": "node scripts/generate-sitemap.js",
"i18n:extract": "lingui extract --locale en-US",
"i18n:compile": "lingui compile",
"i18n": "yarn i18n:extract --clean && yarn i18n:compile",
- "prepare": "concurrently \"npm:ajv\" \"npm:contracts\" \"npm:graphql\" \"npm:i18n\"",
+ "prepare": "concurrently \"npm:ajv\" \"npm:contracts\" \"npm:graphql\" \"npm:i18n\" \"npm:sitemap:generate\"",
"start": "craco start",
"start:cloud": "NODE_OPTIONS=--dns-result-order=ipv4first PORT=3001 npx wrangler pages dev --compatibility-flags=nodejs_compat --compatibility-date=2023-08-01 --proxy=3001 --port=3000 -- yarn start",
"build": "craco build",
@@ -114,6 +115,7 @@
"@types/ua-parser-js": "^0.7.36",
"@types/uuid": "^8.3.4",
"@types/wcag-contrast": "^3.0.0",
+ "@types/xml2js": "^0.4.12",
"@uniswap/default-token-list": "^11.2.0",
"@uniswap/eslint-config": "^1.2.0",
"@vanilla-extract/jest-transform": "^1.1.1",
@@ -293,6 +295,7 @@
"workbox-navigation-preload": "^6.1.0",
"workbox-precaching": "^6.1.0",
"workbox-routing": "^6.1.0",
+ "xml2js": "^0.6.2",
"zustand": "^4.3.6"
},
"engines": {
diff --git a/public/sitemap.xml b/public/sitemap.xml
new file mode 100644
index 0000000000..ca9f947bee
--- /dev/null
+++ b/public/sitemap.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/scripts/generate-sitemap.js b/scripts/generate-sitemap.js
new file mode 100644
index 0000000000..64889262d9
--- /dev/null
+++ b/scripts/generate-sitemap.js
@@ -0,0 +1,25 @@
+/* eslint-env node */
+
+const fs = require('fs')
+const { parseStringPromise, Builder } = require('xml2js')
+
+fs.readFile('./public/sitemap.xml', 'utf8', async (err, data) => {
+ try {
+ const sitemap = await parseStringPromise(data)
+
+ const lastmodDate = new Date().toISOString()
+ if (sitemap.urlset.url) {
+ sitemap.urlset.url.forEach((url) => {
+ url['$'].lastmod = lastmodDate
+ })
+ }
+ const builder = new Builder()
+ const xml = builder.buildObject(sitemap)
+ fs.writeFile('./public/sitemap.xml', xml, (error) => {
+ if (error) throw error
+ console.log('Sitemap updated')
+ })
+ } catch {
+ throw new Error('Error parsing sitemap.xml')
+ }
+})
diff --git a/src/pages/App.tsx b/src/pages/App.tsx
index d7f18e7009..17d3023a05 100644
--- a/src/pages/App.tsx
+++ b/src/pages/App.tsx
@@ -5,51 +5,27 @@ import ErrorBoundary from 'components/ErrorBoundary'
import Loader from 'components/Icons/LoadingSpinner'
import NavBar, { PageTabs } from 'components/NavBar'
import { useFeatureFlagsIsLoaded } from 'featureFlags'
-import { useInfoPoolPageEnabled } from 'featureFlags/flags/infoPoolPage'
import { useAtom } from 'jotai'
import { useBag } from 'nft/hooks/useBag'
import { lazy, Suspense, useEffect, useLayoutEffect, useMemo, useState } from 'react'
-import { Navigate, Route, Routes, useLocation, useSearchParams } from 'react-router-dom'
+import { Route, Routes, useLocation, useSearchParams } from 'react-router-dom'
import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
import { useRouterPreference } from 'state/user/hooks'
import { StatsigProvider, StatsigUser } from 'statsig-react'
import styled from 'styled-components'
-import { SpinnerSVG } from 'theme/components'
import DarkModeQueryParamReader from 'theme/components/DarkModeQueryParamReader'
import { useIsDarkMode } from 'theme/components/ThemeToggle'
import { flexRowNoWrap } from 'theme/styles'
import { Z_INDEX } from 'theme/zIndex'
import { STATSIG_DUMMY_KEY } from 'tracing'
-import { getEnvName, isBrowserRouterEnabled } from 'utils/env'
+import { getEnvName } from 'utils/env'
import { getDownloadAppLink } from 'utils/openDownloadApp'
import { getCurrentPageFromLocation } from 'utils/urlRoutes'
import { getCLS, getFCP, getFID, getLCP, Metric } from 'web-vitals'
-// High-traffic pages (index and /swap) should not be lazy-loaded.
-import Landing from './Landing'
-import Swap from './Swap'
+import { RouteDefinition, routes, useRouterConfig } from './RouteDefinitions'
const AppChrome = lazy(() => import('./AppChrome'))
-const NftExplore = lazy(() => import('nft/pages/explore'))
-const Collection = lazy(() => import('nft/pages/collection'))
-const Profile = lazy(() => import('nft/pages/profile'))
-const Asset = lazy(() => import('nft/pages/asset/Asset'))
-const AddLiquidity = lazy(() => import('pages/AddLiquidity'))
-const RedirectDuplicateTokenIds = lazy(() => import('pages/AddLiquidity/redirects'))
-const RedirectDuplicateTokenIdsV2 = lazy(() => import('pages/AddLiquidityV2/redirects'))
-const MigrateV2 = lazy(() => import('pages/MigrateV2'))
-const MigrateV2Pair = lazy(() => import('pages/MigrateV2/MigrateV2Pair'))
-const NotFound = lazy(() => import('pages/NotFound'))
-const Pool = lazy(() => import('pages/Pool'))
-const PositionPage = lazy(() => import('pages/Pool/PositionPage'))
-const PoolV2 = lazy(() => import('pages/Pool/v2'))
-const PoolDetails = lazy(() => import('pages/PoolDetails'))
-const PoolFinder = lazy(() => import('pages/PoolFinder'))
-const RemoveLiquidity = lazy(() => import('pages/RemoveLiquidity'))
-const RemoveLiquidityV3 = lazy(() => import('pages/RemoveLiquidity/V3'))
-const TokenDetails = lazy(() => import('pages/TokenDetails'))
-const Tokens = lazy(() => import('pages/Tokens'))
-const Vote = lazy(() => import('pages/Vote'))
const BodyWrapper = styled.div`
display: flex;
@@ -93,32 +69,18 @@ const HeaderWrapper = styled.div<{ transparent?: boolean }>`
z-index: ${Z_INDEX.dropdown};
`
-// this is the same svg defined in assets/images/blue-loader.svg
-// it is defined here because the remote asset may not have had time to load when this file is executing
-const LazyLoadSpinner = () => (
-
-
-
-)
-
export default function App() {
const isLoaded = useFeatureFlagsIsLoaded()
- const [shouldDisableNFTRoutes, setShouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom)
+ const [, setShouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom)
- const browserRouterEnabled = isBrowserRouterEnabled()
const location = useLocation()
- const { hash, pathname } = location
+ const { pathname } = location
const currentPage = getCurrentPageFromLocation(pathname)
const isDarkMode = useIsDarkMode()
const [routerPreference] = useRouterPreference()
const [scrolledState, setScrolledState] = useState(false)
- const infoPoolPageEnabled = useInfoPoolPageEnabled()
+
+ const routerConfig = useRouterConfig()
useEffect(() => {
window.scrollTo(0, 0)
@@ -224,116 +186,15 @@ export default function App() {
}>
{isLoaded ? (
- :
- }
- />
-
- }>
-
-
- } />
- {infoPoolPageEnabled && } />}
- }>
-
-
- }
- />
- } />
- } />
- } />
-
- } />
- } />
- } />
- } />
-
- } />
- } />
- } />
- } />
-
- }>
-
-
-
- }>
- {/* this is workaround since react-router-dom v6 doesn't support optional parameters any more */}
-
-
-
-
-
- }>
-
-
-
-
-
-
- } />
- } />
-
- } />
- } />
-
- {!shouldDisableNFTRoutes && (
- <>
-
-
-
- }
- />
-
-
-
-
- }
- />
-
-
-
-
- }
- />
-
-
-
-
- }
- />
-
-
-
-
- }
- />
- >
+ {routes.map((route: RouteDefinition) =>
+ route.enabled(routerConfig) ? (
+
+ {route.nestedPaths.map((nestedPath) => (
+
+ ))}
+
+ ) : null
)}
-
- } />
- } />
) : (
diff --git a/src/pages/RouteDefinitions.tsx b/src/pages/RouteDefinitions.tsx
new file mode 100644
index 0000000000..b87d31a53f
--- /dev/null
+++ b/src/pages/RouteDefinitions.tsx
@@ -0,0 +1,208 @@
+import { useInfoPoolPageEnabled } from 'featureFlags/flags/infoPoolPage'
+import { useAtom } from 'jotai'
+import { lazy, ReactNode, Suspense, useMemo } from 'react'
+import { Navigate, useLocation } from 'react-router-dom'
+import { shouldDisableNFTRoutesAtom } from 'state/application/atoms'
+import { SpinnerSVG } from 'theme/components'
+import { isBrowserRouterEnabled } from 'utils/env'
+
+// High-traffic pages (index and /swap) should not be lazy-loaded.
+import Landing from './Landing'
+import Swap from './Swap'
+
+const NftExplore = lazy(() => import('nft/pages/explore'))
+const Collection = lazy(() => import('nft/pages/collection'))
+const Profile = lazy(() => import('nft/pages/profile'))
+const Asset = lazy(() => import('nft/pages/asset/Asset'))
+const AddLiquidity = lazy(() => import('pages/AddLiquidity'))
+const RedirectDuplicateTokenIds = lazy(() => import('pages/AddLiquidity/redirects'))
+const RedirectDuplicateTokenIdsV2 = lazy(() => import('pages/AddLiquidityV2/redirects'))
+const MigrateV2 = lazy(() => import('pages/MigrateV2'))
+const MigrateV2Pair = lazy(() => import('pages/MigrateV2/MigrateV2Pair'))
+const NotFound = lazy(() => import('pages/NotFound'))
+const Pool = lazy(() => import('pages/Pool'))
+const PositionPage = lazy(() => import('pages/Pool/PositionPage'))
+const PoolV2 = lazy(() => import('pages/Pool/v2'))
+const PoolDetails = lazy(() => import('pages/PoolDetails'))
+const PoolFinder = lazy(() => import('pages/PoolFinder'))
+const RemoveLiquidity = lazy(() => import('pages/RemoveLiquidity'))
+const RemoveLiquidityV3 = lazy(() => import('pages/RemoveLiquidity/V3'))
+const TokenDetails = lazy(() => import('pages/TokenDetails'))
+const Tokens = lazy(() => import('pages/Tokens'))
+const Vote = lazy(() => import('pages/Vote'))
+
+// this is the same svg defined in assets/images/blue-loader.svg
+// it is defined here because the remote asset may not have had time to load when this file is executing
+const LazyLoadSpinner = () => (
+
+
+
+)
+
+interface RouterConfig {
+ browserRouterEnabled?: boolean
+ hash?: string
+ infoPoolPageEnabled?: boolean
+ shouldDisableNFTRoutes?: boolean
+}
+
+/**
+ * Convenience hook which organizes the router configuration into a single object.
+ */
+export function useRouterConfig(): RouterConfig {
+ const browserRouterEnabled = isBrowserRouterEnabled()
+ const { hash } = useLocation()
+ const infoPoolPageEnabled = useInfoPoolPageEnabled()
+ const [shouldDisableNFTRoutes] = useAtom(shouldDisableNFTRoutesAtom)
+ return useMemo(
+ () => ({
+ browserRouterEnabled,
+ hash,
+ infoPoolPageEnabled,
+ shouldDisableNFTRoutes: Boolean(shouldDisableNFTRoutes),
+ }),
+ [browserRouterEnabled, hash, infoPoolPageEnabled, shouldDisableNFTRoutes]
+ )
+}
+
+export interface RouteDefinition {
+ path: string
+ nestedPaths: string[]
+ enabled: (args: RouterConfig) => boolean
+ getElement: (args: RouterConfig) => ReactNode
+}
+
+// Assigns the defaults to the route definition.
+function createRouteDefinition(route: Partial): RouteDefinition {
+ return {
+ getElement: () => null,
+ enabled: () => true,
+ path: '/',
+ nestedPaths: [],
+ // overwrite the defaults
+ ...route,
+ }
+}
+
+export const routes: RouteDefinition[] = [
+ createRouteDefinition({
+ path: '/',
+ getElement: (args) => {
+ return args.browserRouterEnabled && args.hash ? :
+ },
+ }),
+ createRouteDefinition({
+ path: '/tokens',
+ nestedPaths: [':chainName'],
+ getElement: () => ,
+ }),
+ createRouteDefinition({ path: '/tokens/:chainName/:tokenAddress', getElement: () => }),
+ createRouteDefinition({
+ path: '/pools/:chainName/:poolAddress',
+ getElement: () => ,
+ enabled: (args) => Boolean(args.infoPoolPageEnabled),
+ }),
+ createRouteDefinition({
+ path: '/vote/*',
+ getElement: () => (
+ }>
+
+
+ ),
+ }),
+ createRouteDefinition({
+ path: '/create-proposal',
+ getElement: () => ,
+ }),
+ createRouteDefinition({
+ path: '/send',
+ getElement: () => ,
+ }),
+ createRouteDefinition({ path: '/swap', getElement: () => }),
+ createRouteDefinition({ path: '/pool/v2/find', getElement: () => }),
+ createRouteDefinition({ path: '/pool/v2', getElement: () => }),
+ createRouteDefinition({ path: '/pool', getElement: () => }),
+ createRouteDefinition({ path: '/pool/:tokenId', getElement: () => }),
+ createRouteDefinition({ path: '/pools/v2/find', getElement: () => }),
+ createRouteDefinition({ path: '/pools/v2', getElement: () => }),
+ createRouteDefinition({ path: '/pools', getElement: () => }),
+ createRouteDefinition({ path: '/pools/:tokenId', getElement: () => }),
+ createRouteDefinition({
+ path: '/add/v2',
+ nestedPaths: [':currencyIdA', ':currencyIdA/:currencyIdB'],
+ getElement: () => ,
+ }),
+ createRouteDefinition({
+ path: '/add',
+ nestedPaths: [':currencyIdA', ':currencyIdA/:currencyIdB', ':currencyIdA/:currencyIdB/:feeAmount'],
+ getElement: () => ,
+ }),
+
+ createRouteDefinition({
+ path: '/increase',
+ nestedPaths: [
+ ':currencyIdA',
+ ':currencyIdA/:currencyIdB',
+ ':currencyIdA/:currencyIdB/:feeAmount',
+ ':currencyIdA/:currencyIdB/:feeAmount/:tokenId',
+ ],
+ getElement: () => ,
+ }),
+ createRouteDefinition({ path: '/remove/v2/:currencyIdA/:currencyIdB', getElement: () => }),
+ createRouteDefinition({ path: '/remove/:tokenId', getElement: () => }),
+ createRouteDefinition({ path: '/migrate/v2', getElement: () => }),
+ createRouteDefinition({ path: '/migrate/v2/:address', getElement: () => }),
+ createRouteDefinition({
+ path: '/nfts',
+ getElement: () => (
+
+
+
+ ),
+ enabled: (args) => !args.shouldDisableNFTRoutes,
+ }),
+ createRouteDefinition({
+ path: '/nfts/asset/:contractAddress/:tokenId',
+ getElement: () => (
+
+
+
+ ),
+ enabled: (args) => !args.shouldDisableNFTRoutes,
+ }),
+ createRouteDefinition({
+ path: '/nfts/profile',
+ getElement: () => (
+
+
+
+ ),
+ enabled: (args) => !args.shouldDisableNFTRoutes,
+ }),
+ createRouteDefinition({
+ path: '/nfts/collection/:contractAddress',
+ getElement: () => (
+
+
+
+ ),
+ enabled: (args) => !args.shouldDisableNFTRoutes,
+ }),
+ createRouteDefinition({
+ path: '/nfts/collection/:contractAddress/activity',
+ getElement: () => (
+
+
+
+ ),
+ enabled: (args) => !args.shouldDisableNFTRoutes,
+ }),
+ createRouteDefinition({ path: '*', getElement: () => }),
+ createRouteDefinition({ path: '/not-found', getElement: () => }),
+]
diff --git a/src/pages/__snapshots__/routes.test.ts.snap b/src/pages/__snapshots__/routes.test.ts.snap
new file mode 100644
index 0000000000..1e87906873
--- /dev/null
+++ b/src/pages/__snapshots__/routes.test.ts.snap
@@ -0,0 +1,200 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Routes router definition should match snapshot 1`] = `
+Array [
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [
+ ":chainName",
+ ],
+ "path": "/tokens",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/tokens/:chainName/:tokenAddress",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/pools/:chainName/:poolAddress",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/vote/*",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/create-proposal",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/send",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/swap",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/pool/v2/find",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/pool/v2",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/pool",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/pool/:tokenId",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/pools/v2/find",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/pools/v2",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/pools",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/pools/:tokenId",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [
+ ":currencyIdA",
+ ":currencyIdA/:currencyIdB",
+ ],
+ "path": "/add/v2",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [
+ ":currencyIdA",
+ ":currencyIdA/:currencyIdB",
+ ":currencyIdA/:currencyIdB/:feeAmount",
+ ],
+ "path": "/add",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [
+ ":currencyIdA",
+ ":currencyIdA/:currencyIdB",
+ ":currencyIdA/:currencyIdB/:feeAmount",
+ ":currencyIdA/:currencyIdB/:feeAmount/:tokenId",
+ ],
+ "path": "/increase",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/remove/v2/:currencyIdA/:currencyIdB",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/remove/:tokenId",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/migrate/v2",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/migrate/v2/:address",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/nfts",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/nfts/asset/:contractAddress/:tokenId",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/nfts/profile",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/nfts/collection/:contractAddress",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/nfts/collection/:contractAddress/activity",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "*",
+ },
+ Object {
+ "enabled": [Function],
+ "getElement": [Function],
+ "nestedPaths": Array [],
+ "path": "/not-found",
+ },
+]
+`;
diff --git a/src/pages/routes.test.ts b/src/pages/routes.test.ts
new file mode 100644
index 0000000000..927925b92b
--- /dev/null
+++ b/src/pages/routes.test.ts
@@ -0,0 +1,26 @@
+import fs from 'fs'
+import { parseStringPromise } from 'xml2js'
+
+import { routes } from './RouteDefinitions'
+
+describe('Routes', () => {
+ it('sitemap URLs should exist as Router paths', async () => {
+ const pathNames: string[] = routes.map((routeDef) => routeDef.path)
+ const contents = fs.readFileSync('./public/sitemap.xml', 'utf8')
+ const sitemap = await parseStringPromise(contents)
+
+ const sitemapPaths = sitemap.urlset.url.map((url: any) => new URL(url['$'].loc).pathname)
+
+ sitemapPaths.forEach((path: string) => {
+ expect(pathNames).toContain(path)
+ })
+ })
+
+ /**
+ * If you are updating the app routes, consider if you need to make a
+ * corresponding update to the sitemap.xml file.
+ */
+ it('router definition should match snapshot', () => {
+ expect(routes).toMatchSnapshot()
+ })
+})
diff --git a/yarn.lock b/yarn.lock
index e8435e0e72..97c884f672 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5921,6 +5921,13 @@
dependencies:
"@types/node" "*"
+"@types/xml2js@^0.4.12":
+ version "0.4.12"
+ resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.12.tgz#d9aae03295476fd5cbc74e0b572816208dbec6d1"
+ integrity sha512-CZPpQKBZ8db66EP5hCjwvYrLThgZvnyZrPXK2W+UI1oOaWezGt34iOaUCX4Jah2X8+rQqjvl9VKEIT8TR1I0rA==
+ dependencies:
+ "@types/node" "*"
+
"@types/yargs-parser@*":
version "20.2.1"
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129"
@@ -15542,9 +15549,9 @@ mz@^2.7.0:
thenify-all "^1.0.0"
nan@^2.14.0:
- version "2.14.2"
- resolved "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz"
- integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
+ version "2.18.0"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554"
+ integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==
nano-time@1.0.0:
version "1.0.0"
@@ -17227,9 +17234,9 @@ punycode@1.3.2, punycode@^1.3.2:
integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
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==
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
+ integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
pure-rand@^6.0.0:
version "6.0.2"
@@ -21101,6 +21108,14 @@ xml2js@^0.4.5:
sax ">=0.6.0"
xmlbuilder "~11.0.0"
+xml2js@^0.6.2:
+ version "0.6.2"
+ resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499"
+ integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==
+ dependencies:
+ sax ">=0.6.0"
+ xmlbuilder "~11.0.0"
+
xmlbuilder@~11.0.0:
version "11.0.1"
resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz"