dev: moved geolocation utils from wccom to here (#38356)
This commit is contained in:
parent
5ba15f84cd
commit
06e6f5012f
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: dev
|
||||||
|
|
||||||
|
Moved geolocation country matching functions to @woocommerce/onboarding
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"rootDir": "./src",
|
||||||
|
"preset": "../node_modules/@woocommerce/internal-js-tests/jest-preset.js"
|
||||||
|
}
|
|
@ -37,14 +37,19 @@
|
||||||
"@wordpress/components": "wp-6.0",
|
"@wordpress/components": "wp-6.0",
|
||||||
"@wordpress/element": "wp-6.0",
|
"@wordpress/element": "wp-6.0",
|
||||||
"@wordpress/i18n": "wp-6.0",
|
"@wordpress/i18n": "wp-6.0",
|
||||||
|
"string-similarity": "4.0.4",
|
||||||
"gridicons": "^3.4.0"
|
"gridicons": "^3.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.17.5",
|
"@babel/core": "^7.17.5",
|
||||||
|
"@testing-library/react": "^12.1.3",
|
||||||
|
"@types/string-similarity" : "4.0.0",
|
||||||
"@types/wordpress__components": "^19.10.3",
|
"@types/wordpress__components": "^19.10.3",
|
||||||
"@types/wordpress__data": "^6.0.0",
|
"@types/wordpress__data": "^6.0.0",
|
||||||
|
"@types/jest": "^27.4.1",
|
||||||
"@woocommerce/eslint-plugin": "workspace:*",
|
"@woocommerce/eslint-plugin": "workspace:*",
|
||||||
"@woocommerce/internal-style-build": "workspace:*",
|
"@woocommerce/internal-style-build": "workspace:*",
|
||||||
|
"@woocommerce/internal-js-tests": "workspace:*",
|
||||||
"@wordpress/browserslist-config": "wp-6.0",
|
"@wordpress/browserslist-config": "wp-6.0",
|
||||||
"css-loader": "^3.6.0",
|
"css-loader": "^3.6.0",
|
||||||
"eslint": "^8.32.0",
|
"eslint": "^8.32.0",
|
||||||
|
@ -62,6 +67,7 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"turbo:build": "pnpm run build:js && pnpm run build:css",
|
"turbo:build": "pnpm run build:js && pnpm run build:css",
|
||||||
|
"turbo:test": "jest --config ./jest.config.json",
|
||||||
"prepare": "composer install",
|
"prepare": "composer install",
|
||||||
"changelog": "composer exec -- changelogger",
|
"changelog": "composer exec -- changelogger",
|
||||||
"clean": "pnpm exec rimraf tsconfig.tsbuildinfo build build-*",
|
"clean": "pnpm exec rimraf tsconfig.tsbuildinfo build build-*",
|
||||||
|
@ -70,12 +76,15 @@
|
||||||
"build:js": "tsc --project tsconfig.json && tsc --project tsconfig-cjs.json",
|
"build:js": "tsc --project tsconfig.json && tsc --project tsconfig-cjs.json",
|
||||||
"build:css": "webpack",
|
"build:css": "webpack",
|
||||||
"start": "concurrently \"tsc --project tsconfig.json --watch\" \"tsc --project tsconfig-cjs.json --watch\" \"webpack --watch\"",
|
"start": "concurrently \"tsc --project tsconfig.json --watch\" \"tsc --project tsconfig-cjs.json --watch\" \"webpack --watch\"",
|
||||||
|
"test": "pnpm -w exec turbo run turbo:test --filter=$npm_package_name",
|
||||||
"prepack": "pnpm run clean && pnpm run build",
|
"prepack": "pnpm run clean && pnpm run build",
|
||||||
"lint:fix": "eslint src --fix"
|
"lint:fix": "eslint src --fix",
|
||||||
|
"test-staged": "jest --bail --config ./jest.config.json --findRelatedTests"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.(t|j)s?(x)": [
|
"*.(t|j)s?(x)": [
|
||||||
"pnpm lint:fix"
|
"pnpm lint:fix",
|
||||||
|
"pnpm test-staged"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,3 +15,4 @@ export { WooPaymentGatewayConfigure } from './components/WooPaymentGatewayConfig
|
||||||
export { WooOnboardingTaskListItem } from './components/WooOnboardingTaskListItem';
|
export { WooOnboardingTaskListItem } from './components/WooOnboardingTaskListItem';
|
||||||
export { WooOnboardingTaskListHeader } from './components/WooOnboardingTaskListHeader';
|
export { WooOnboardingTaskListHeader } from './components/WooOnboardingTaskListHeader';
|
||||||
export { WooOnboardingTask } from './components/WooOnboardingTask';
|
export { WooOnboardingTask } from './components/WooOnboardingTask';
|
||||||
|
export * from './utils/countries';
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import stringSimilarity from 'string-similarity';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { getMappingRegion } from './location-mapping';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Country state option.
|
||||||
|
*/
|
||||||
|
export type CountryStateOption = {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Location = {
|
||||||
|
country_short?: string;
|
||||||
|
region?: string;
|
||||||
|
city?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a country option for the given location.
|
||||||
|
*/
|
||||||
|
export const findCountryOption = (
|
||||||
|
countryStateOptions: CountryStateOption[],
|
||||||
|
location: Location | undefined,
|
||||||
|
minimumSimilarity = 0.7
|
||||||
|
) => {
|
||||||
|
if ( ! location ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let match = null;
|
||||||
|
let matchSimilarity = minimumSimilarity;
|
||||||
|
// eslint-disable-next-line @wordpress/no-unused-vars-before-return -- don't want to put this inside the loop
|
||||||
|
const mappingRegion = getMappingRegion( location );
|
||||||
|
|
||||||
|
for ( const option of countryStateOptions ) {
|
||||||
|
// Country matches exactly.
|
||||||
|
if ( option.key === location.country_short ) {
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Countries have regions such as 'US:CA'.
|
||||||
|
const countryCode = option.key.split( ':' )[ 0 ];
|
||||||
|
if (
|
||||||
|
countryCode === location.country_short &&
|
||||||
|
option.label.includes( '—' )
|
||||||
|
) {
|
||||||
|
const wcRegion = option.label.split( '—' )[ 1 ].trim();
|
||||||
|
|
||||||
|
// Region matches exactly with mapping.
|
||||||
|
if ( mappingRegion === wcRegion ) {
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the region with the highest similarity.
|
||||||
|
const similarity = Math.max(
|
||||||
|
stringSimilarity.compareTwoStrings(
|
||||||
|
wcRegion,
|
||||||
|
location.region || ''
|
||||||
|
),
|
||||||
|
stringSimilarity.compareTwoStrings(
|
||||||
|
wcRegion,
|
||||||
|
location.city || ''
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if ( similarity >= matchSimilarity ) {
|
||||||
|
match = option;
|
||||||
|
matchSimilarity = similarity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
};
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { Location } from '.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file is used to map a location to a WC region label
|
||||||
|
* so that we can find the correct country option
|
||||||
|
* since WPCOM geolocation returns a different
|
||||||
|
* region or city name.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Key is the country code, value is an object with keys as region/city names and values as WC region labels.
|
||||||
|
const MAPPING: Record< string, Record< string, string > > = {
|
||||||
|
PH: {
|
||||||
|
'National Capital Region': __( 'Metro Manila', 'woocommerce' ),
|
||||||
|
},
|
||||||
|
IT: {
|
||||||
|
Rome: __( 'Roma', 'woocommerce' ),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a WC mapping region name for the given country, region and city.
|
||||||
|
*/
|
||||||
|
export const getMappingRegion = ( {
|
||||||
|
country_short: countryCode,
|
||||||
|
region = '',
|
||||||
|
city = '',
|
||||||
|
}: Location ) => {
|
||||||
|
if ( ! countryCode ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const countryMapping = MAPPING[ countryCode ];
|
||||||
|
if ( ! countryMapping ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const regionMapping = countryMapping[ region ];
|
||||||
|
if ( regionMapping ) {
|
||||||
|
return regionMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cityMapping = countryMapping[ city ];
|
||||||
|
if ( cityMapping ) {
|
||||||
|
return cityMapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MAPPING;
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,96 @@
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
/* eslint-disable no-undef */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { findCountryOption } from '../../';
|
||||||
|
import { countryStateOptions } from './country-options';
|
||||||
|
import { locations } from './locations';
|
||||||
|
|
||||||
|
describe( 'findCountryOption', () => {
|
||||||
|
it( 'should return null on undefined location', () => {
|
||||||
|
const location = undefined;
|
||||||
|
expect( findCountryOption( countryStateOptions, location ) ).toEqual(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should return null when not found', () => {
|
||||||
|
const location = { country_short: 'US' };
|
||||||
|
expect( findCountryOption( countryStateOptions, location ) ).toBeNull();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it.each( [
|
||||||
|
{
|
||||||
|
location: { country_short: 'TW' },
|
||||||
|
expected: {
|
||||||
|
key: 'TW',
|
||||||
|
label: 'Taiwan',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location: { country_short: 'US', region: 'California' },
|
||||||
|
expected: {
|
||||||
|
key: 'US:CA',
|
||||||
|
label: 'United States (US) — California',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location: { country_short: 'ES', region: 'Madrid, Comunidad de' },
|
||||||
|
expected: {
|
||||||
|
key: 'ES:M',
|
||||||
|
label: 'Spain — Madrid',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location: {
|
||||||
|
country_short: 'AR',
|
||||||
|
region: 'Ciudad Autonoma de Buenos Aires',
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
key: 'AR:C',
|
||||||
|
label: 'Argentina — Ciudad Autónoma de Buenos Aires',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location: {
|
||||||
|
country_short: 'IT',
|
||||||
|
region: 'Lazio',
|
||||||
|
city: 'Rome',
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
key: 'IT:RM',
|
||||||
|
label: 'Italy — Roma',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location: {
|
||||||
|
country_short: 'PH',
|
||||||
|
region: 'National Capital Region',
|
||||||
|
city: 'Makati',
|
||||||
|
},
|
||||||
|
expected: {
|
||||||
|
key: 'PH:00',
|
||||||
|
label: 'Philippines — Metro Manila',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
] )(
|
||||||
|
'should return the country option for location $expected',
|
||||||
|
( { location, expected } ) => {
|
||||||
|
expect(
|
||||||
|
findCountryOption( countryStateOptions, location, 0.4 )
|
||||||
|
).toEqual( expected );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
it( 'should return a country option for > 98% locations', () => {
|
||||||
|
let matchCount = 0;
|
||||||
|
locations.forEach( ( location ) => {
|
||||||
|
if ( findCountryOption( countryStateOptions, location, 0.4 ) ) {
|
||||||
|
matchCount++;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
expect( matchCount / locations.length ).toBeGreaterThan( 0.98 );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -0,0 +1,41 @@
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { getMappingRegion } from '../../location-mapping';
|
||||||
|
|
||||||
|
describe( 'getMappingRegion', () => {
|
||||||
|
it( 'should return null for an empty location', () => {
|
||||||
|
expect( getMappingRegion( {} ) ).toBeNull();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should return null for a location that is not in the mapping', () => {
|
||||||
|
expect(
|
||||||
|
getMappingRegion( { country_short: 'US', region: 'California' } )
|
||||||
|
).toBeNull();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should return null for a location with no region', () => {
|
||||||
|
expect( getMappingRegion( { country_short: 'PH' } ) ).toBeNull();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should return the region for a location that is in the mapping with a region', () => {
|
||||||
|
expect(
|
||||||
|
getMappingRegion( {
|
||||||
|
country_short: 'PH',
|
||||||
|
region: 'National Capital Region',
|
||||||
|
} )
|
||||||
|
).toBe( 'Metro Manila' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should return the region for a location that is in the mapping with a city', () => {
|
||||||
|
expect(
|
||||||
|
getMappingRegion( {
|
||||||
|
country_short: 'IT',
|
||||||
|
region: 'Lazio',
|
||||||
|
city: 'Rome',
|
||||||
|
} )
|
||||||
|
).toBe( 'Roma' );
|
||||||
|
} );
|
||||||
|
} );
|
File diff suppressed because it is too large
Load Diff
|
@ -1949,10 +1949,22 @@ importers:
|
||||||
gridicons:
|
gridicons:
|
||||||
specifier: ^3.4.0
|
specifier: ^3.4.0
|
||||||
version: 3.4.0(react@17.0.2)
|
version: 3.4.0(react@17.0.2)
|
||||||
|
string-similarity:
|
||||||
|
specifier: 4.0.4
|
||||||
|
version: 4.0.4
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@babel/core':
|
'@babel/core':
|
||||||
specifier: ^7.17.5
|
specifier: ^7.17.5
|
||||||
version: 7.17.8
|
version: 7.17.8
|
||||||
|
'@testing-library/react':
|
||||||
|
specifier: ^12.1.3
|
||||||
|
version: 12.1.4(react-dom@16.14.0)(react@17.0.2)
|
||||||
|
'@types/jest':
|
||||||
|
specifier: ^27.4.1
|
||||||
|
version: 27.4.1
|
||||||
|
'@types/string-similarity':
|
||||||
|
specifier: 4.0.0
|
||||||
|
version: 4.0.0
|
||||||
'@types/wordpress__components':
|
'@types/wordpress__components':
|
||||||
specifier: ^19.10.3
|
specifier: ^19.10.3
|
||||||
version: 19.10.5(react-dom@16.14.0)(react@17.0.2)
|
version: 19.10.5(react-dom@16.14.0)(react@17.0.2)
|
||||||
|
@ -1962,6 +1974,9 @@ importers:
|
||||||
'@woocommerce/eslint-plugin':
|
'@woocommerce/eslint-plugin':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../eslint-plugin
|
version: link:../eslint-plugin
|
||||||
|
'@woocommerce/internal-js-tests':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../internal-js-tests
|
||||||
'@woocommerce/internal-style-build':
|
'@woocommerce/internal-style-build':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../internal-style-build
|
version: link:../internal-style-build
|
||||||
|
@ -15537,6 +15552,20 @@ packages:
|
||||||
react-error-boundary: 3.1.4(react@17.0.2)
|
react-error-boundary: 3.1.4(react@17.0.2)
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@testing-library/react@12.1.4(react-dom@16.14.0)(react@17.0.2):
|
||||||
|
resolution: {integrity: sha512-jiPKOm7vyUw311Hn/HlNQ9P8/lHNtArAx0PisXyFixDDvfl8DbD6EUdbshK5eqauvBSvzZd19itqQ9j3nferJA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '*'
|
||||||
|
react-dom: '*'
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.21.0
|
||||||
|
'@testing-library/dom': 8.11.3
|
||||||
|
'@types/react-dom': 17.0.17
|
||||||
|
react: 17.0.2
|
||||||
|
react-dom: 16.14.0(react@17.0.2)
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@testing-library/react@12.1.4(react-dom@17.0.2)(react@17.0.2):
|
/@testing-library/react@12.1.4(react-dom@17.0.2)(react@17.0.2):
|
||||||
resolution: {integrity: sha512-jiPKOm7vyUw311Hn/HlNQ9P8/lHNtArAx0PisXyFixDDvfl8DbD6EUdbshK5eqauvBSvzZd19itqQ9j3nferJA==}
|
resolution: {integrity: sha512-jiPKOm7vyUw311Hn/HlNQ9P8/lHNtArAx0PisXyFixDDvfl8DbD6EUdbshK5eqauvBSvzZd19itqQ9j3nferJA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
@ -16093,6 +16122,10 @@ packages:
|
||||||
/@types/stack-utils@2.0.1:
|
/@types/stack-utils@2.0.1:
|
||||||
resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==}
|
resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==}
|
||||||
|
|
||||||
|
/@types/string-similarity@4.0.0:
|
||||||
|
resolution: {integrity: sha512-dMS4S07fbtY1AILG/RhuwmptmzK1Ql8scmAebOTJ/8iBtK/KI17NwGwKzu1uipjj8Kk+3mfPxum56kKZE93mzQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/strip-ansi@3.0.0:
|
/@types/strip-ansi@3.0.0:
|
||||||
resolution: {integrity: sha512-wVhzc+WJ/JNdV25MeaK0skxGdbdOFeqYv1sqY8yPXbsshZ0XwSbWWwfKzj836cPW+e+PpqUNvKoiac9ZqCdyRQ==}
|
resolution: {integrity: sha512-wVhzc+WJ/JNdV25MeaK0skxGdbdOFeqYv1sqY8yPXbsshZ0XwSbWWwfKzj836cPW+e+PpqUNvKoiac9ZqCdyRQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -20969,13 +21002,13 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: '>= 4.12.1'
|
eslint: '>= 4.12.1'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.18.6
|
'@babel/code-frame': 7.16.7
|
||||||
'@babel/parser': 7.21.3
|
'@babel/parser': 7.17.8
|
||||||
'@babel/traverse': 7.21.3
|
'@babel/traverse': 7.17.3
|
||||||
'@babel/types': 7.21.3
|
'@babel/types': 7.17.0
|
||||||
eslint: 7.32.0
|
eslint: 7.32.0
|
||||||
eslint-visitor-keys: 1.3.0
|
eslint-visitor-keys: 1.3.0
|
||||||
resolve: 1.22.1
|
resolve: 1.20.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -27360,7 +27393,7 @@ packages:
|
||||||
vue-template-compiler:
|
vue-template-compiler:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.18.6
|
'@babel/code-frame': 7.16.7
|
||||||
'@types/json-schema': 7.0.9
|
'@types/json-schema': 7.0.9
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
chokidar: 3.5.3
|
chokidar: 3.5.3
|
||||||
|
@ -40521,6 +40554,11 @@ packages:
|
||||||
char-regex: 1.0.2
|
char-regex: 1.0.2
|
||||||
strip-ansi: 6.0.1
|
strip-ansi: 6.0.1
|
||||||
|
|
||||||
|
/string-similarity@4.0.4:
|
||||||
|
resolution: {integrity: sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==}
|
||||||
|
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||||
|
dev: false
|
||||||
|
|
||||||
/string-template@0.2.1:
|
/string-template@0.2.1:
|
||||||
resolution: {integrity: sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==}
|
resolution: {integrity: sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
Loading…
Reference in New Issue